Media Library

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production) and WP-CLI 2.12.0, and checked on 2026-10-28. WordPress 7.1 requires PHP 7.4 at minimum, and PHP 8.3 or newer is recommended. WordPress 7.2 is in beta and planned for 2026-12-08; nothing described here is announced to change with it. Every command below was run against the official wordpress:7.1.3-php8.4-apache image (PHP 8.4, GD and Imagick) with a MariaDB 11.8 container, and every output block is real output from that site.

The Media Library holds the files that Lumen Press publishes: book covers, photos from launch events, author portraits and EPUB sample chapters. This page describes it from the site owner’s side (screens, formats, sizes, limits, alt text) and adds the few lines of configuration a site needs, in a must-use plugin. The developer APIs behind it (wp_get_attachment_image(), srcset, WP_Image_Editor, sideloading from code) are covered in Images and Media API, and front-end image delivery in general (formats, lazy loading, responsive images) in Media Optimization.

What the Media Library is

Every uploaded file becomes an attachment: a row in the posts table with post_type set to attachment, the file’s MIME type in post_mime_type, the title in post_title, the caption in post_excerpt and the description in post_content. The file itself goes to wp-content/uploads/ (in YYYY/MM/ sub-folders when "Organize my uploads into month- and year-based folders" is checked under Settings > Media, the default). Three post meta keys complete the record: _wp_attached_file (the path relative to the uploads directory), _wp_attachment_metadata (a serialized array with width, height, every generated size, original_image and EXIF-derived image_meta) and _wp_attachment_image_alt (the alternative text).

The table layout is described in Database Schema. An attachment may have a parent post (post_parent): an image uploaded while editing a post is attached to it, and one uploaded from the Library is unattached. Attachment is only bookkeeping. The same image can appear in any number of posts, and deleting the parent does not delete the file.

Uploading requires the upload_files capability, which Authors, Editors and Administrators have and Contributors do not. Editing or deleting someone else’s file requires edit_others_posts / delete_others_posts, so an Author manages only their own uploads. The roles are described in Users, Roles and Profiles.

The Media screens

Media > Library (upload.php) lists every attachment, and Media > Add Media File (media-new.php) is a drag-and-drop upload page that shows the maximum upload size. The Library has two views, switched with the icons above the list. The grid view shows thumbnails, filters by type (Images, Audio, Video, Documents, Spreadsheets, Archives, Unattached) and by month, a live search and Bulk select; clicking an item opens the Attachment details dialog, which saves changes automatically and moves between items with the arrow keys. The list view is a sortable table (File, Author, Uploaded to, comments, Date) with Screen Options for items per page, row actions (Edit, Delete Permanently, View, Copy URL, Download file) and Attach for unattached files.

The Attachment details dialog and the full Edit Media screen (post.php?post=<id>&action=edit) show the upload date and author, the post it is attached to, file name, MIME type, size and dimensions, and the editable fields: Alt Text, Title, Caption and Description. File URL is read-only, with Copy URL to clipboard and Download file next to it. Audio files add Artist and Album fields read from the file’s tags.

Deleting is permanent. Attachments have no trash unless MEDIA_TRASH is defined as true in wp-config.php (it defaults to false); with it, the Library gains a Trash filter and Move to Trash replaces Delete permanently. Deleting an attachment removes the original file, every generated size and the attachment row. Content that still refers to the file keeps a broken image, because WordPress does not track where an attachment is used.

Infinite scroll or pagination (7.1)

The grid view can load more items as the user scrolls ("infinite scrolling") or behind a Load more button. WordPress 5.8 turned infinite scrolling off by default for accessibility and performance reasons, and only a plugin using the media_library_infinite_scrolling filter could turn it back on. WordPress 7.1 reverses the default: infinite scrolling is on, and each user who can upload files gets a Disable infinite scrolling in the Media Library grid view checkbox under Users > Profile. The choice is stored in the user option infinite_scrolling (the string 'false' means disabled). The list view always paginates.

The order of precedence is: a callback on the filter, then the user’s choice, then the default. A site that needs one behaviour for everyone, for example pagination on a large catalogue with tens of thousands of covers, hooks the filter; an individual opt-out can also be set from the command line:

<?php
// In a must-use plugin: force pagination for every user, ignoring profile choices.
add_filter( 'media_library_infinite_scrolling', '__return_false' );
wp user meta update lp_author infinite_scrolling false
Success: Updated custom field 'infinite_scrolling'.

Allowed file types

WordPress decides what may be uploaded in two steps. First, the file’s extension must be in the list returned by get_allowed_mime_types(). Second, wp_check_filetype_and_ext() reads the file’s real content type and rejects a file whose content does not match its extension (an HTML file renamed to .jpg, for instance). A rejected file produces "Sorry, you are not allowed to upload this file type."

The list is not fixed. get_allowed_mime_types() starts from wp_get_mime_types() (98 entries in 7.1), always removes swf and exe, removes htm|html and js for users without the unfiltered_html capability, and then passes the result through the upload_mimes filter. On a single site only Administrators and Editors have unfiltered_html; on Multisite only Super Admins do, and each site’s list is further limited by Network Admin > Settings > Upload file types (the upload_filetypes network option). Defining ALLOW_UNFILTERED_UPLOADS as true lets Administrators (Super Admins on Multisite) upload any type; it is a debugging switch, not a production setting.

The following command prints the effective list for two users of the Lumen Press site, the Administrator lp_admin (user 1) and the Author lp_author (user 2):

wp eval '
$all = wp_get_mime_types();
wp_set_current_user( 1 );
echo "admin removed: ", implode( ", ", array_keys( array_diff_key( $all, get_allowed_mime_types() ) ) ), "\n";
wp_set_current_user( 2 );
$a = get_allowed_mime_types();
echo "author: ", count( $a ), " removed: ", implode( ", ", array_keys( array_diff_key( $all, $a ) ) ), "\n";
'
admin removed: swf, exe
author: 94 removed: htm|html, js, swf, exe

Without any filter, an Administrator may upload 96 types: images (jpg/jpeg/jpe, gif, png, bmp, tif/tiff, webp, avif, ico, heic, heif, heics, heifs), video (mp4/m4v, mov, webm, ogv, mkv, avi, mpeg, wmv, flv, 3gp, 3g2 and a few older Windows formats), audio (mp3/m4a/m4b, aac, wav, ogg/oga, flac, mid, wma, mka, ra), text (txt, csv, tsv, ics, rtx, css, vtt, dfxp, plus htm/html and js for unfiltered_html users), documents (pdf, rtf, the Microsoft Office, OpenDocument and Apple iWork families, oxps, xps, OneNote, WordPerfect) and archives and other files (zip, tar, gz, rar, 7z, psd, xcf, class).

The list printed in older books predates WebP (5.8), AVIF (6.5) and HEIC (6.7) and reads as if it were fixed. In a current install it is longer, it depends on the user’s capabilities, and on Multisite it depends on the network setting; get_allowed_mime_types() or a test upload is the authoritative answer. EPUB and SVG are absent by default, which matters for a publisher.

Changing the list with upload_mimes

Lumen Press publishes EPUB sample chapters and never needs bitmaps, TIFF scans, Java classes or Windows Write files. Site-wide policy like this belongs in a must-use plugin, which the theme cannot switch off. The plugin below also holds the image-size settings used later on this page:

wp-content/mu-plugins/lumen-media.php
<?php
/**
 * Plugin Name: Lumen Media Policy
 * Description: Upload types, image sizes and big-image scaling for the Lumen Press site.
 * Version:     1.0.0
 * Requires at least: 7.1
 * Requires PHP: 7.4
 * License:     GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

// Allow EPUB sample chapters; stop accepting formats the site never publishes.
function lumen_media_upload_mimes( $mimes, $user ) {
	$mimes['epub'] = 'application/epub+zip';

	unset( $mimes['bmp'], $mimes['tiff|tif'], $mimes['class'], $mimes['wri'] );

	return $mimes;
}
add_filter( 'upload_mimes', 'lumen_media_upload_mimes', 10, 2 );

// Keep a larger "full" copy of print covers (default threshold: 2560).
function lumen_media_big_image_threshold( $threshold ) {
	return 3200;
}
add_filter( 'big_image_size_threshold', 'lumen_media_big_image_threshold' );

// A 2:3 cover size used by the book templates.
function lumen_media_image_sizes() {
	add_image_size( 'lp-book-cover', 600, 900, true );
}
add_action( 'after_setup_theme', 'lumen_media_image_sizes' );

The array keys are regular-expression fragments, so a removal must use the exact key (tiff|tif, not tif). Importing an EPUB and a BMP with WP-CLI shows the filter at work; wp media import runs the same checks as the upload screen:

wp media import /var/www/html/import/the-lighthouse-keeper-chapter-1.epub \
  --title="The Lighthouse Keeper, chapter 1 (EPUB)"
wp media import /var/www/html/import/old-logo.bmp
Imported file '/var/www/html/import/the-lighthouse-keeper-chapter-1.epub' as attachment ID 5.
Success: Imported 1 of 1 items.
Warning: Unable to import file '/var/www/html/import/old-logo.bmp'. Reason: Sorry, you are not allowed to upload this file type.
Error: No items imported.

Because the content check still runs, adding an extension to the list does not make an arbitrary file acceptable: the real content type detected by PHP’s fileinfo must match the declared MIME type. When a legitimate file is rejected after its type was added, the cause is usually a different detected type (some CSV and font files are detected as text/plain or application/octet-stream). The wp_check_filetype_and_ext filter can correct that for one extension; widening it for all files defeats the check.

SVG

SVG files are not allowed by default, and the reason is security rather than an oversight: an SVG is an XML document that can carry <script> elements and event handlers, so an uploaded SVG opened directly from the uploads URL can run JavaScript in the site’s origin (stored cross-site scripting). Adding svg to upload_mimes alone is unsafe. Sites that need SVG uploads use a plugin that sanitizes each file on upload (Safe SVG is a widely used one) and restrict SVG uploads to trusted roles. Theme SVGs (icons, logos) belong in the theme’s files and do not need the Media Library.

Modern image formats

Format Since What WordPress does with it

WebP

5.8

Upload and sub-sizes in WebP when the server’s GD or Imagick supports it.

AVIF

6.5

Same as WebP. Server support is listed under Tools > Site Health > Info > Media Handling. In 7.1, client-side processing (below) decodes AVIF in the browser, so AVIF uploads work even on hosts without server support.

HEIC/HEIF

6.7

Accepted and converted to JPEG on the server when Imagick supports HEIC; the original HEIC file is kept. Without support, the uploader tells the user to convert the image first. 7.1’s client-side processing decodes HEIC in browsers whose platform has the codec (Chromium on macOS or on Windows with HEVC support, Safari on macOS) and uploads a JPEG, keeping the HEIC as a companion file.

HDR gain maps (UltraHDR JPEG)

7.1

Detected on upload by client-side processing; the original is stored unmodified and every generated size keeps its gain map. Format conversion is skipped for these files, because converting the codec would strip the gain map.

SVG

not supported

Rejected by default (see above).

The HEIC conversion depends on the PHP process that handles the upload. In the official images, the Apache image’s Imagick lists HEIC and HEIF among its formats, but the wordpress:cli image’s Imagick does not, so a wp media import of a .heic file from that container creates an attachment with no sizes and no metadata. Running WP-CLI inside the web container gives the expected result. The Apache image has no WP-CLI, so the first three lines copy the wp binary from the CLI image into it. The attachment keeps the image/heic MIME type, its main file and every size are JPEGs, and original_image names the HEIC file:

cid=$(docker create wordpress:cli-php8.4)
docker cp "$cid":/usr/local/bin/wp ./wp-cli.phar && docker rm "$cid"
docker compose cp ./wp-cli.phar wordpress:/usr/local/bin/wp
docker compose exec -u www-data wordpress \
  wp media import /var/www/html/import/author-portrait.heic --alt="Portrait of the author" --porcelain
docker compose exec -u www-data wordpress wp eval '
$m = wp_get_attachment_metadata( 11 );
echo get_post_mime_type( 11 ), " file=", $m["file"], " original=", $m["original_image"], "\n";
foreach ( $m["sizes"] as $k => $s ) { echo "  $k ", $s["file"], " ", $s["mime-type"], "\n"; }
'
11
image/heic file=2026/10/author-portrait-1.jpg original=author-portrait-1.heic
  medium author-portrait-1-300x225.jpg image/jpeg
  thumbnail author-portrait-1-150x150.jpg image/jpeg
  medium_large author-portrait-1-768x576.jpg image/jpeg
  lp-book-cover author-portrait-1-600x600.jpg image/jpeg

The conversion is driven by the image_editor_output_format filter, whose default mapping in 7.1 sends image/heic, image/heif, image/heic-sequence and image/heif-sequence to image/jpeg. The same filter can make WordPress generate WebP or AVIF files from JPEG uploads. The "full" file and every size are then written as AVIF, the post MIME type stays image/jpeg, and the uploaded JPEG is kept and named in original_image:

<?php
// In a must-use plugin: generate AVIF sub-sizes from JPEG uploads.
add_filter(
	'image_editor_output_format',
	function ( $formats ) {
		$formats['image/jpeg'] = 'image/avif';
		return $formats;
	}
);

Importing a 1200 × 800 JPEG with that filter active and printing the MIME type, main file and sizes gives the output below; original_image is launch-night-1.jpg:

image/jpeg 2026/10/launch-night-1.avif
  medium launch-night-1-300x200.avif
  large launch-night-1-1024x683.avif
  thumbnail launch-night-1-150x150.avif
  medium_large launch-night-1-768x512.avif
  lp-book-cover launch-night-1-600x800.avif

Core does not serve AVIF or WebP with a JPEG fallback through <picture>; all browsers in current use decode WebP and AVIF, and a site with older clients leaves the default in place. Core also does not convert images that were uploaded before a filter was added.

Image sizes and big-image scaling

For each uploaded image WordPress creates resized copies. Three are set under Settings > Media, and three more are registered by core without a screen:

Size Default box Crop Set by

thumbnail

150 × 150

hard (exact)

Settings > Media, "Crop thumbnail to exact dimensions"

medium

300 × 300

soft (proportional)

Settings > Media

medium_large

768 wide

soft

core (since 4.4), used in srcset

large

1024 × 1024

soft

Settings > Media

1536x1536

1536 × 1536

soft

core (since 5.3)

2048x2048

2048 × 2048

soft

core (since 5.3)

Themes and plugins add their own with add_image_size(). A size is only generated when the original is larger than its box; WordPress never upscales. wp media image-size lists what is registered on the Lumen Press site, including the lp-book-cover size from the must-use plugin:

wp media image-size
+---------------+-------+--------+------+-------+
| name          | width | height | crop | ratio |
+---------------+-------+--------+------+-------+
| full          |       |        | N/A  | N/A   |
| 2048x2048     | 2048  | 2048   | soft | N/A   |
| 1536x1536     | 1536  | 1536   | soft | N/A   |
| large         | 1024  | 1024   | soft | N/A   |
| medium_large  | 768   | 0      | soft | N/A   |
| lp-book-cover | 600   | 900    | hard | 2:3   |
| medium        | 300   | 300    | soft | N/A   |
| thumbnail     | 150   | 150    | hard | 1:1   |
+---------------+-------+--------+------+-------+

Every registered size costs disk space and upload time for every image, whether the site displays it or not. Themes that register many sizes, and plugins that each add a few, are a common reason for slow uploads and large backups.

Big-image scaling (2560 px)

Since WordPress 5.3, an uploaded image wider or taller than 2560 pixels is scaled down to fit 2560 × 2560, and the scaled copy, saved with a -scaled suffix, becomes the "full" size that content uses. The untouched original stays in the uploads folder, its file name is stored as original_image in the attachment metadata, and wp_get_original_image_path() / wp_get_original_image_url() return it. The threshold is changed with the big_image_size_threshold filter; returning false turns scaling off.

Lumen Press raised the threshold to 3200 pixels in the must-use plugin above. Importing a 4000 × 6000 cover shows the result:

wp media import /var/www/html/import/the-lighthouse-keeper-cover.jpg \
  --title="The Lighthouse Keeper cover" \
  --alt="Cover of The Lighthouse Keeper: a lighthouse on a cliff at dusk" \
  --caption="Cover art for The Lighthouse Keeper" --porcelain
wp eval '
$m = wp_get_attachment_metadata( 4 );
echo $m["file"], " ", $m["width"], "x", $m["height"], "\noriginal_image: ", $m["original_image"], "\n";
foreach ( $m["sizes"] as $k => $s ) { echo str_pad( $k, 14 ), $s["file"], "\n"; }
echo "original path: ", str_replace( ABSPATH, "", wp_get_original_image_path( 4 ) ), "\n";
'
4
2026/10/the-lighthouse-keeper-cover-scaled.jpg 2133x3200
original_image: the-lighthouse-keeper-cover.jpg
medium        the-lighthouse-keeper-cover-200x300.jpg
large         the-lighthouse-keeper-cover-683x1024.jpg
thumbnail     the-lighthouse-keeper-cover-150x150.jpg
medium_large  the-lighthouse-keeper-cover-768x1152.jpg
1536x1536     the-lighthouse-keeper-cover-1024x1536.jpg
2048x2048     the-lighthouse-keeper-cover-1365x2048.jpg
lp-book-cover the-lighthouse-keeper-cover-600x900.jpg
original path: wp-content/uploads/2026/10/the-lighthouse-keeper-cover.jpg

The REST API returns the same data under media_details (width, height, file, original_image and a sizes object with the file, dimensions, MIME type and source_url of each size), so a headless front end can build its own srcset; see REST API: Using It.

Regenerating sizes with wp media regenerate

Changing Settings > Media, adding a size with add_image_size() or switching to a theme with other sizes affects only future uploads. Existing images keep their old files until they are regenerated:

# Every image, every size (deletes and recreates the sub-size files).
wp media regenerate --yes

# One attachment, one size: after adding lp-book-cover.
wp media regenerate 4 --image_size=lp-book-cover --yes

# Only the sizes that are missing, leaving existing files untouched.
wp media regenerate --only-missing --yes
Found 2 images to regenerate.
1/2 Regenerated thumbnails for "launch-night" (ID 7).
2/2 Regenerated thumbnails for "The Lighthouse Keeper cover" (ID 4).
Success: Regenerated 2 of 2 images.
Found 1 image to regenerate.
1/1 Regenerated "lp-book-cover" thumbnail for "The Lighthouse Keeper cover" (ID 4).
Success: Regenerated 1 of 1 images.
Found 2 images to regenerate.
1/2 No thumbnail regeneration needed for "launch-night" (ID 7).
2/2 No thumbnail regeneration needed for "The Lighthouse Keeper cover" (ID 4).
Success: Regenerated 2 of 2 images.

--only-missing is the safe default on a production site: a full regeneration rewrites thousands of files, can take hours on a large library, and changes file names when crop settings change, which breaks hard-coded URLs in old content. Sizes that are no longer registered are not deleted by either form; --delete-unknown removes them.

Editing images

The Edit Image screen

From the Library, Edit Image (in the Attachment details dialog or on the Edit Media screen) opens the classic image editor: crop (drag a selection, optionally with a fixed aspect ratio or exact pixel values), rotate 90° left or right, flip vertically or horizontally, scale down proportionally, and undo or redo. Changes can be applied to all sizes, to the thumbnail only, or to all sizes except the thumbnail. Editing is non-destructive: WordPress writes new files with a suffix and keeps the previous ones, and Restore original image returns to the uploaded file. While cropping, the arrow keys move the selection by 10 px (1 px with Shift) and Ctrl+arrow resizes it.

The media editor modal in the block editor (7.1)

WordPress 7.1 replaced the inline cropping tool of the block editor with a media editor modal. It opens from the same Crop button in the Image block toolbar (and the Cover block’s background image), and combines in one window:

  • freeform cropping and cropping to an aspect ratio, with the crop magnified to fill the canvas;

  • horizontal and vertical flipping;

  • rotation with fine-grained control and snapping;

  • metadata editing (alt text and caption) in a side panel;

  • full keyboard operation: Tab through the crop area, handles, toolbar and panel, arrow keys move or resize the crop, and undo/redo shortcuts work.

The crop in the modal applies to the image as used in that block. The editor saves the result through POST /wp/v2/media/<id>/edit, which writes a new file and creates a new attachment (with an -edited file name), leaving the original attachment and every other post that uses it unchanged. Flip support was added to that endpoint in 6.9. On the Lumen Press site, a request to that endpoint for the 1200 × 800 photo with ID 7 (crop to the left half, flip horizontally) answered 201 with a new attachment, ID 8, file launch-night-edited.jpg, 600 × 800, while ID 7 stayed as it was. Crop values in the request are percentages of the source image.

Manual pixel crop controls and Restore original are not part of the modal in 7.1; they remain on the Library’s Edit Image screen. Each crop adds an attachment, so a heavily edited post leaves several -edited copies in the Library.

Client-side image processing (7.1)

Until 7.0 every image size was produced on the server by GD or Imagick, which is where large phone photos ran into memory_limit, max_execution_time and missing codecs. WordPress 7.1 moves that work into the browser when it can. @wordpress/vips, a WebAssembly build of libvips running in a Web Worker, compresses, resizes, rotates according to EXIF, converts formats and generates every registered size before upload. The server then receives finished files.

How an upload travels on that path:

  1. The block editor uploads the original (or the browser-decoded JPEG for HEIC) to POST /wp/v2/media with generate_sub_sizes set to false, which creates the attachment and fires wp_generate_attachment_metadata with the context 'create'.

  2. Each sub-size is uploaded separately to POST /wp/v2/media/<id>/sideload, at most five uploads and two image operations at a time.

  3. POST /wp/v2/media/<id>/finalize runs wp_generate_attachment_metadata again with the context 'update'. If it fails, the error is logged and the upload still succeeds.

Because every size is its own request, a dropped connection loses one file, not the batch. Failed requests are retried automatically with exponential backoff, uploads pause when the browser goes offline and resume when it reconnects, and the editor shows progress per file. This is the "upload retry" of the 7.1 release.

When it runs

The feature turns itself on only when every condition below holds; otherwise the classic server-side path is used, without an error:

Condition Detail

Secure context

wp_is_client_side_media_processing_enabled() returns true by default only over HTTPS, or for localhost and *.localhost hosts. On a plain-HTTP site the /sideload and /finalize routes are not even registered.

Browser

Chromium 137 or newer (Chrome, Edge, Opera, Brave). On block editor screens WordPress sends the header Document-Isolation-Policy: isolate-and-credentialless to get the cross-origin isolation that SharedArrayBuffer requires. Firefox and Safari use the server path (Safari still decodes HEIC itself).

Device and network

More than 2 GB of device memory, at least two CPU cores, a connection not reported as 2g or slow-2g, and no Save-Data header, because the worker is a download of about 13 MB. These thresholds are not configurable.

Content Security Policy

A site that sends a CSP must allow blob: in worker-src.

Screen

Block editor, Site Editor and block widgets screens only, for users with upload_files. Admin URLs with an action other than edit (page builders) are skipped.

Format

JPEG, PNG, GIF, WebP and AVIF through libvips, HEIC/HEIF decoded to JPEG. The list is fixed in code. Video, audio and documents upload directly. The Media Library screen and Add Media File keep using the server.

On the Lumen Press container, a request to the REST index with Host: localhost lists six /wp/v2/media routes (including /sideload and /finalize), and the same request with any other host name over plain HTTP lists four.

What still applies, and what changes for plugins

The client reads the server’s settings, so the existing filters keep working: big_image_size_threshold, image_editor_output_format, wp_editor_set_quality and jpeg_quality, image_save_progressive, image_strip_meta, image_max_bit_depth and every size registered with add_image_size(). Lumen Press’s 3200 px threshold and lp-book-cover size are therefore honoured on both paths.

Three server hooks never fire on the client path, because no server image editor runs: wp_image_editors, image_make_intermediate_size and image_memory_limit. wp_handle_upload fires once per sideloaded file, and wp_generate_attachment_metadata fires twice. A plugin that, for example, sends every new image to a CDN or an optimization service from wp_generate_attachment_metadata must be idempotent across the 'create' and 'update' passes.

A site that relies on a server-side image pipeline, or whose editors work on weak machines that pass the checks, turns the feature off:

<?php
// In a must-use plugin: keep all image processing on the server.
add_filter( 'wp_client_side_media_processing_enabled', '__return_false' );

A site with a Content Security Policy adds the worker source instead:

Content-Security-Policy: worker-src 'self' blob:;

Cross-origin isolation has a side effect on the editor screens: WordPress adds crossorigin="anonymous" to external scripts there, and a third-party resource that does not send CORS headers can stop loading in the editor. Checking window.crossOriginIsolated in the browser console tells whether the isolation is active.

Upload limits

The maximum upload size shown on Add Media File is wp_max_upload_size(), the smaller of PHP’s upload_max_filesize and post_max_size, passed through the upload_size_limit filter. Returning a larger value from the filter changes the number shown but not what PHP accepts; raising the limit means changing PHP (and the web server’s request body limit, client_max_body_size on nginx). The server side, including the nginx and .htaccess variants and what happens when each limit is exceeded, is described in Requirements and Hosting.

The official wordpress Docker image ships PHP’s defaults, upload_max_filesize = 2M and post_max_size = 8M, so a fresh container accepts nothing larger than 2 MB, less than many phone photos. Lumen Press mounts an extra ini file:

uploads.ini
; Lumen Press: allow 64 MB uploads (EPUBs, print-ready covers).
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 120

The file is mounted into the official image’s PHP configuration directory, here in the wordpress service of the Compose file from Local Development Environments, next to the must-use plugin of this page:

compose.yaml (excerpt)
services:
  wordpress:
    image: wordpress:7.1.3-php8.4-apache
    volumes:
      - wordpress:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
      - ./lumen-media.php:/var/www/html/wp-content/mu-plugins/lumen-media.php:ro
docker compose up -d
docker compose exec wordpress php -r 'echo ini_get("upload_max_filesize"), " ", ini_get("post_max_size"), "\n";'
64M 64M

After the change, size_format( wp_max_upload_size() ) reports 64 MB in a request served by Apache. With client-side processing, sub-sizes arrive as separate, smaller requests, but the original file still has to fit the limit. On Multisite, Network Admin > Settings adds a lower Max upload file size (in KB) and a per-site upload space quota on top of PHP’s limits.

Alt text

Alternative text is what screen readers announce and what appears when an image fails to load. WordPress stores it in two places, and the difference explains most "I fixed the alt text but the page did not change" reports:

  • The Alt Text field in the Library (Attachment details or Edit Media) is stored in _wp_attachment_image_alt and is the default copied into a block when the image is inserted.

  • After insertion the Image block keeps its own alt attribute in the post content. Changing the Library field later does not update posts that already use the image, and changing it in the block does not update the Library. The 7.1 media editor modal edits the Library’s alt text and caption of the attachment it works on.

Good practice, following the W3C’s alt decision tree: describe what the image conveys in its context ("Cover of The Lighthouse Keeper: a lighthouse on a cliff at dusk"), not "image of"; describe the destination when the image is a link; leave alt empty for purely decorative images (in the Image block, by leaving the field blank so that WordPress outputs alt=""); do not repeat a caption word for word. The same photo may need different alt text in different posts, which is what the block-level attribute is for. Accessibility for theme authors is covered in Theme Accessibility and Releasing and in general in Accessibility.

Images imported in bulk often arrive without alt text. wp media import --alt sets it on import, and a query finds the images still missing it so that an editor can fill them in:

wp db query "SELECT p.ID, p.post_title FROM lp_posts p
  LEFT JOIN lp_postmeta m ON m.post_id = p.ID AND m.meta_key = '_wp_attachment_image_alt'
  WHERE p.post_type = 'attachment' AND p.post_mime_type LIKE 'image/%'
    AND (m.meta_value IS NULL OR m.meta_value = '') ORDER BY p.ID;"
wp post meta update 10 _wp_attachment_image_alt "Lumen Press autumn reading evening banner"
ID	post_title
10	event-banner
Success: Updated custom field '_wp_attachment_image_alt'.

Errors and pitfalls

Symptom Cause and fix

"Sorry, you are not allowed to upload this file type."

The extension is not in get_allowed_mime_types() for that user, or the detected content type does not match. Add the type with upload_mimes (not for SVG without sanitizing), or check the Multisite Upload file types setting.

"The uploaded file exceeds the upload_max_filesize directive in php.ini." or a generic "HTTP error"

PHP or web-server limits. Raise upload_max_filesize, post_max_size and nginx’s client_max_body_size together.

"The server cannot process the image. This can happen if the server is busy or does not have enough resources to complete the task."

The server ran out of memory or time while creating sizes, typically for very large photos. Raise memory_limit, use fewer registered sizes, or rely on client-side processing in a supported browser.

HEIC upload refused with a request to convert to JPEG

The PHP that handled the upload has no HEIC-capable Imagick. Check Site Health > Info > Media Handling; with WP-CLI, run the command inside the web container.

An image is visibly smaller than the file uploaded

Big-image scaling at 2560 px. The original is still on disk; raise or disable big_image_size_threshold for new uploads.

Client-side processing never starts

Plain HTTP on a non-localhost host, a non-Chromium browser, a weak device, Save-Data, or a CSP without blob: workers. All fall back silently to the server.

Security

  • The upload check covers extension and content. Do not weaken wp_check_filetype_and_ext globally, and do not enable ALLOW_UNFILTERED_UPLOADS in production.

  • Uploads are public by URL. Anything placed in the Media Library, including "unattached" files and the originals kept by big-image scaling, can be fetched by anyone who knows or guesses the URL. Files that must stay private (manuscripts under review, contracts) do not belong there.

  • The uploads directory must never execute PHP; blocking it at the web server is part of Security Hardening, and the permissions (644 for files, 755 for directories) are in File Permissions and Filesystem.

  • Uploaded photos carry EXIF data, including GPS coordinates. Generated sub-sizes are written without most of it (image_strip_meta defaults to true), but the original file keeps it; authors should remove location data before uploading photos of private places.

  • SVG requires sanitizing (see above), and HTML and JavaScript uploads are limited to unfiltered_html users for the same reason.

  • Media blocks are output too: WordPress 7.1.4 (2026-10-28) fixed a stored XSS in the Playlist and Image blocks, so sites that embed media from the Library should run that security release or a later one (WordPress News: WordPress 7.1.4).

Where older books and tutorials are outdated

Older advice Current behaviour

A fixed list of allowed upload types, as printed in older books

The list depends on capabilities, the upload_mimes filter and Multisite settings, and now includes WebP (5.8), AVIF (6.5) and HEIC/HEIF (6.7).

Large photos must be resized before uploading

WordPress scales anything above 2560 px since 5.3 and keeps the original; since 7.1 a supported browser resizes and compresses before upload.

HEIC photos from phones must be converted first

Converted to JPEG automatically since 6.7 (server with HEIC-capable Imagick) and in the browser since 7.1.

The Library pages with a Load more button

Infinite scrolling is the default again since 7.1, with a per-user opt-out.

The full list of corrections is in Where Older Books and Tutorials Are Outdated.

References