releases.shpreview

Image processing moves to the browser

v7.1

5 featuresThis release5 featuresNew capabilitiesAI-tallied from the release notes
From the original release noteView original ↗

WordPress 7.1 ships client-side media processing – a capability that handles image compression, resizing, format conversion, rotation, and thumbnail generation directly in the user’s browser using WebAssembly, rather than on the server. The feature is enabled by default in supporting browsers.

This post outlines what’s changing, how it works, and what plugin and theme developers need to know.

What is client-side media processing?

Traditionally, when a user uploads an image in the block editor, the file is sent to the server where PHP (using GD or Imagick) generates thumbnails (various image sizes for the front end), applies format conversions, handles EXIF rotation, and scales large images. This approach is limited by PHP memory constraints, server CPU availability, and the capabilities of the server’s installed image library.

Client-side media processing moves this work to the browser. Images are processed using wasm-vips, a WebAssembly compilation of the high-performance libvips image processing library. The processed images – including all thumbnails – are then uploaded to the server, which stores them. After all client-side operations complete, a finalize step applies the wp_generate_attachment_metadata filter with context 'update' so plugins see the full sub-sizes metadata. This mirrors how the server already handlesimage uploads, where sub-sizes trigger the same 'update' pass.

Key benefits

  • Consistent, high-quality output with modern image support. All users get the same libvips powered processing regardless of whether the server has GD or Imagick, and regardless of which version is installed.
  • Faster downloads for visitors. libvips produces better-compressed output than GD or Imagick (JPEGs are reduced ~15% with MozJPEG like encoding), so the generated images served to site visitors are smaller and load faster.
  • No more PHP memory limit failures. Large image processing that would exceed PHP’s memory limit now succeeds because it runs in the browser’s memory space.
  • Reduced server load. Image processing is offloaded to the user’s device, freeing server CPU and memory for other tasks.
  • iPhone photos just work. HEIC images can be decoded in the browser and converted to JPEG before upload, even on hosts without server-side HEIC support. Note: HEIC decode relies on platform codecs and is supported in Chromium browsers (Chrome, Edge, Brave) on macOS and on Windows with HEVC support, and in Safari on macOS. The full WASM pipeline (everything beyond HEIC) is Chromium-only – see Browser compatibility and fallback below.
  • AVIF without server-side AVIF support. Hosts whose PHP image editor doesn’t support AVIF can still accept AVIF uploads when client-side processing is active. The MIME-type check is bypassed for client-decoded uploads – see the security FAQ below for details.
  • Animated GIFs become efficient video. Opaque animated GIFs can be converted in the browser to a companion MP4/WebM video that plays exactly like the original GIF, dramatically cutting the bytes visitors download, with no loss of the autoplay-loop GIF feel.
  • More resilient uploads. Sub-size uploads are independent requests, so a network hiccup mid-upload doesn’t lose the entire batch. Failed requests are retried automatically with exponential backoff, so transient network errors recover without user intervention. Uploads are paused if you go offline and resume when you come back online.

What’s included

  • Browser-based image processing – Compression, resizing, cropping, format conversion (JPEG, PNG, WebP, AVIF, GIF), EXIF rotation, and progressive/interlaced encoding via WebAssembly in a Web Worker.
  • Thumbnail generation in the browser – All registered image sub-sizes are generated client-side and uploaded individually via a new sideload REST API endpoint. Sizes that share dimensions with built-in sizes (e.g. Twenty Eleven’s large matches medium_large) are deduplicated to a single physical file registered under all matching size names.
  • HEIC/HEIF support – iPhone photos (image/heic, image/heif) are decoded in the browser and uploaded as web-ready JPEG. The original HEIC is kept as a companion file (source_image) and removed when the attachment is deleted.
  • AVIF end-to-end uploads – AVIF can be decoded client-side, no longer requiring server-side AVIF support. High-bit-depth AVIF sources (10- or 12-bit, common for HDR photos) keep their bit depth in generated sub-sizes.
  • Gain Map HDR support – UltraHDR JPEGs embed a gain map alongside a standard SDR base image: a new, backwards-compatible way of adding HDR data to SDR images that is supported by Google (UltraHDR), Apple (Adaptive HDR), and Adobe (Camera Raw, Lightroom, and Photoshop). These files are detected on upload and preserved end-to-end: the original uploads unmodified, and every generated sub-size keeps its gain map, so thumbnails stay HDR. Format conversion via image_editor_output_format is intentionally skipped for these files, since converting to a different codec would strip the gain map.
  • Automatic format conversion – The existing image_editor_output_format filter is respected client-side, enabling automatic conversion (e.g., JPEG to WebP) during processing.
  • Animated GIF to video conversion – Opaque animated GIFs are converted in the browser to an MP4 (or WebM) using the native WebCodecs APIs and the mediabunny library. The GIF stays a single image/gif attachment; the converted video and a first-frame poster are sideloaded as companion files (media_details.animated_video / animated_video_poster). In the editor the block is optionally switched to a “GIF” variation of the Video block that autoplays, loops, and is muted – playing just like the original GIF – and the front end renders a native <video>. The swap is fully reversible via the block transform menu, transparent GIFs are left as images, and browsers without WebCodecs video encoding (e.g. Firefox) upload the original GIF unchanged.
  • A cross-origin-isolated editor – To run the WASM pipeline, the editor needs SharedArrayBuffer, which browsers only expose to cross-origin-isolated documents. WordPress enables this with Document-Isolation-Policy: isolate-and-credentialless on block editor screens for Chromium 137+. Beyond media processing, this means SharedArrayBuffer and high-resolution timers are now available to any code running in the editor, so plugins can build their own multithreaded or WASM-backed features there. Because DIP is per-document, it provides this isolation without imposing the page-wide constraints of COOP/COEP. See Cross-origin isolation impact below for what extenders should watch for.
  • Server-side hook compatibilitywp_generate_attachment_metadata fires the same way as for a server-side upload: once with context 'create' during the initial upload and again with 'update' after POST /wp/v2/media/{id}/finalize runs. Plugins that hook into it (watermarking, CDN sync, etc.) continue to work, the same way they already handle the deferred-subsize pass on big-image uploads.
  • Upload progress feedback – A snackbar in the editor tracks batch upload progress, with a spinner while uploads run and a brief checkmark on completion. It works on both the client-side and server-side upload paths, and announces start and completion via wp.a11y.speak() for screen reader users.
  • Smart fallback – Browsers that don’t support the required features automatically fall back to server-side processing with no user-facing change.
  • Image quality filters honored – The standard wp_editor_set_quality and jpeg_quality filters flow through to client-side sub-size generation via a size-aware image_quality field in the upload response, so existing quality-tuning code works unchanged.
  • Server-side import of external images – The Image block’s “Upload to Media Library” action and the pre-publish “External media” panel now send the image URL to the server, which downloads and sideloads it – avoiding browser CORS failures entirely (the old client-side fetch also could not work in the cross-origin-isolated editor).

Technical overview

  • @wordpress/upload-media – Manages the upload queue, concurrency (max 5 uploads, max 2 image processing operations), and orchestrates the pipeline.
  • @wordpress/vips – Wraps wasm-vips in a Web Worker for non-blocking image processing. The WASM bundle is loaded lazily on first use and bundles vips.wasm and vips-heif.wasm (the latter is needed for AVIF decoding).
  • @wordpress/media-utils – Handles HTTP transport to the WordPress REST API.
  • @wordpress/video-conversion – Wraps the mediabunny library in a Web Worker (mirroring the @wordpress/vips pattern) to convert animated GIFs to MP4/WebM off the main thread, gated on WebCodecs ImageDecoder/VideoEncoder availability and run with a concurrency limit of 1.

On the PHP side:

  • wp_is_client_side_media_processing_enabled() – Feature gate, filterable via wp_client_side_media_processing_enabled.
  • Cross-origin isolationwp_start_cross_origin_isolation_output_buffer() sends Document-Isolation-Policy on load-post.php, load-post-new.php, load-site-editor.php, and load-widgets.php for Chromium 137+. Only active when client side media is enabled.
  • REST API extensions – New generate_sub_sizes and convert_format parameters, sideload endpoint (POST /wp/v2/media/{id}/sideload), finalize endpoint (POST /wp/v2/media/{id}/finalize), replace_file flag for HEIC companion uploads, and new response fields (exif_orientation, missing_image_sizes, filename, filesize).

For the full architecture deep-dive, see the client-side media processing architecture documentation.

What plugin developers need to know

Disabling client-side processing

If your plugin needs to disable client-side media processing, use the wp_client_side_media_processing_enabled filter:

add_filter( 'wp_client_side_media_processing_enabled', '__return_false' );

Server-side hooks still fire

A common concern: if client-side processing bypasses server-side image generation, do plugins that hook into wp_generate_attachment_metadata stop working? No – the filter fires the same way it does during a server-side upload, just with the work shifted around. WordPress fires it once with context 'create' during the initial upload (before the sub-sizes are created), and again with 'update' after the finalize endpoint runs (once all client-side sub-size sideloads are complete). Plugins for watermarking, CDN sync, custom metadata processing, and similar use cases continue to work without modification – write them idempotently so they handle both passes correctly. This double-fire pattern matches how WordPress already handles big-image uploads on the server, where sub-size generation is deferred and triggers a second 'update' pass.

If finalize fails, the error is logged but the upload still succeeds – the call is best-effort so a plugin failure can’t block the user’s upload.

Existing filters still work

Client-side processing reads settings from the server and respects:

  • big_image_size_threshold – Maximum image dimension before scaling.
  • image_editor_output_format – Automatic format conversion.
  • image_save_progressive – Progressive/interlaced encoding.
  • wp_image_maybe_exif_rotate – EXIF rotation.
  • wp_editor_set_quality (and jpeg_quality for JPEG output) – Encode quality, resolved per registered size.

There is no client_side_supported_mime_types filter; the supported set (image/jpeg, image/png, image/gif, image/webp, image/avif) is fixed at CLIENT_SIDE_SUPPORTED_MIME_TYPES.

Controlling image quality

Client-side encoding honors the same PHP filters that control server-side quality: wp_editor_set_quality and, for JPEG output, jpeg_quality. The server resolves the filters per registered size and reports the result in the upload response’s size-aware image_quality field, which the client applies during sub-size resize and transcode. The same code that tunes server-side quality works unchanged:

/*
 * Size-aware: drop JPEG thumbnails (300px wide or less) to quality 60,
 * leave larger sizes untouched.
 */
add_filter(
	'wp_editor_set_quality',
	function ( $quality, $mime_type, $size ) {
		if ( 'image/jpeg' === $mime_type && isset( $size['width'] ) && $size['width'] <= 300 ) {
			return 60;
		}
		return $quality;
	},
	10,
	3
);

When the server doesn’t report the field, the client falls back to a default of 0.82.

Cross-origin isolation impact

When client-side media is active, WordPress sends Document-Isolation-Policy: isolate-and-credentialless on block editor screens for Chromium 137+. Since DIP is per-document, it doesn’t impose the page-wide constraints of COEP/COOP. Notable behavior:

  • External scripts loaded across origins automatically get a crossorigin="anonymous" attribute via the server-side wp_add_crossorigin_attributes() output buffer and a client-side MutationObserver. <img> is excluded so external image previews aren’t affected.
  • DIP is skipped on admin pages with an action other than edit, which keeps third-party page builders that rely on same-origin iframe access functional.
  • External images are imported server-side. “Upload to Media Library” and the pre-publish “External media” panel POST the image URL to the media endpoint (a new url parameter) and the server downloads and sideloads it. Plugins importing remote media should do the same rather than fetch()ing image bytes in the browser – a cross-origin fetch is subject to CORS and fails in a credentialless isolated document.

Content Security Policy (CSP)

If your plugin sets a Content Security Policy, ensure the worker-src directive includes blob::

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

Without this, the WASM processing worker cannot be created and processing falls back to server-side.

Server specific hooks don’t fire

Because a server side editor is not used, wp_image_editors, image_memory_limit and image_make_intermediate_size never fire. A complete accounting of media hooks before and after this change is available in the handbook.

What theme developers need to know

Client-side media processing is transparent to themes. Existing filters (big_image_size_threshold, image_editor_output_format, etc.) continue to work without modification. Image sizes registered via add_image_size() are automatically generated client-side, and sizes that share dimensions with built-in sizes are deduplicated to a single physical file.

Browser compatibility and fallback

Client-side processing depends on Document-Isolation-Policy to enable SharedArrayBuffer, which is currently only available in Chromium-based browsers.

Browser

Minimum Version

Status

Chrome

137+

Full support via Document-Isolation-Policy

Edge

137+

Full support via Document-Isolation-Policy

Firefox

*Not supported (no Document-Isolation-Policy) – falls back to server-side

Safari

*Not supported (no Document-Isolation-Policy) – falls back to server-side. In-browser HEIC decode still works, since it does not require Document-Isolation-Policy.

Chrome and Edge have supported Document-Isolation-Policy since version 137 (released in mid-2025). As of this post’s publication, current stable Chrome and Edge are well past that, so the overwhelming majority of Chromium users already meet the requirement. Chrome on Android supports the feature from version 146. Document-Isolation-Policy is not yet tracked on caniuse; the most reliable place to check current and future browser support is the Chrome Platform Status entry.

  • On unsupported browsers WordPress falls back to server-side processing automatically. Users see no difference in behavior. A plugin is available (wordpress.org version coming soon, available now on GitHub) to enable the client-side media feature in Firefox/Safari using COEP/COOP headers. These are not used by default because they create compatibility issues with embeds and other third party resources.

Feature detection and limitations

Beyond browser support, the client checks several runtime conditions before activating the WASM pipeline. Failing any check causes a transparent fallback to server-side processing – there is no user-facing change.

Check

Threshold

Why

Device memory

> 2 GB

WASM image processing can OOM on very low-memory devices.

CPU cores

≥ 2

WASM image processing benefits from at least one core for the worker plus one for the UI thread.

Network

not 2g/slow-2g, no Save-Data header

The ~13 MB worker download is gated to faster connections; 3g is allowed.

CSP blob: workers

must succeed

The worker is created from a blob URL; strict worker-src policies block it.

Frequently asked questions

Isn’t this just a bandwidth optimization?

Not exactly. The client uploads the original plus every sub-size, so total bytes over the wire actually go up during uploads compared to the server-side path (which receives only the original). Bandwidth is saved when serving the images since encoding is better and modern images are always supported. For uploads the real win is server CPU and memory relief: hosts no longer pay the GD/Imagick cost of generating sub-sizes on upload, which is one of the most common causes of PHP timeouts and memory-limit failures on shared hosting. See “Key benefits” above.

Doesn’t the “never trust the client” rule apply here?

Client-side processing is a performance optimization, not a trust boundary. The server still validates every uploaded file – MIME type, dimensions, capability checks, sanitization – and runs the same wp_generate_attachment_metadata filter chain. If the browser can’t or won’t process the file, WordPress falls back to server-side processing transparently.

What happens if the browser can’t process the image?

Server-side processing runs as before. The fallback is automatic and transparent to the user – no UI change, no error. The exact gating (browser features, device memory, CPU cores, network class, CSP) is described in “Feature detection and limitations” above.

Will my plugin’s wp_generate_attachment_metadata hooks still run?

Yes. The filter fires the same way as during a server-side upload: once with context 'create' during the initial upload, and again with 'update' after the finalize endpoint runs (once all client-side sub-size sideloads complete). Watermarking, CDN sync, custom metadata processing, and similar plugins should keep working without modification, but should be checked to make sure they handle both passes. See “Server-side hooks still fire” above.

Does this change the format my users upload?

Only if image_editor_output_format says so – the existing filter is honored client-side. There are two new behaviors. HEIC inputs are converted to JPEG before upload and the original is kept as a companion file. And opaque animated GIFs are converted to a companion video (see below). AVIF inputs upload as AVIF, even on hosts whose server-side image editor lacks AVIF support. HDR images using gain maps upload unmodified, so their HDR gain maps survive – including in every generated sub-size.

What happens to animated GIFs?

Opaque animated GIFs are converted in the browser to a companion MP4/WebM video, and the editor offers a transform to convert the block to a “GIF” variation of the Video block that autoplays, loops, and is muted – so it behaves exactly like the original GIF while downloading far less data. Important details for extenders:

  • The attachment is still a GIF. It stays a single image/gif attachment in the media library; the video and a first-frame poster are companion files recorded in media_details.animated_video / animated_video_poster, removed automatically when the GIF is deleted.
  • The front end is a real video block. The swap is a block switch in the editor, not a render-time filter, so the published markup is a native <video autoplay loop muted playsinline poster> – nothing GIF-specific to filter.
  • It’s reversible, transparent GIFs are left as images, and only standalone Image blocks are converted (GIFs inside a Gallery, Media & Text, or Cover are untouched).
  • Browser support. Conversion needs WebCodecs video encoding (ImageDecoder + VideoEncoder). Browsers without it – notably Firefox – upload the original GIF unchanged, with no error. There is no separate opt-out filter; disabling client-side media processing also disables GIF conversion.

Why aren’t Firefox and Safari supported?

They don’t ship Document-Isolation-Policy, which is what enables SharedArrayBuffer (required for the WASM pipeline). Users on those browsers get the existing server-side path – no regression. The HEIC canvas fallback still works in Safari for HEIC inputs. A plugin is available (wordpress.org version coming soon, available now on GitHub) to enable the client-side media feature in Firefox/Safari using COEP/COOP headers.

Testing and feedback

We encourage plugin and theme developers to test client-side media processing with their products. In particular:

  • Verify that uploads work with your plugin’s custom image sizes and format settings – including sizes that share dimensions with built-in sizes.
  • Test HEIC uploads if you target sites with iPhone-using authors.
  • Test AVIF uploads on hosts whose image editor lacks AVIF support.
  • Test gain-mapped HDR photos (UltraHDR JPEGs) if your plugin transforms images – sub-sizes remain UltraHDR JPEGs with their gain maps, and format conversion is intentionally skipped for them.
  • Test animated GIF uploads – confirm the block converts to a looping muted video, the round-trip back to a GIF works, and any plugin that post-processes attachments handles the companion video/poster correctly.
  • Check that cross-origin isolation doesn’t break any external resources or embeds your plugin loads in the editor.
  • Test with wp_client_side_media_processing_enabled returning false to ensure your fallback path works.

Please report any issues on the Gutenberg GitHub repository. Related tracking issues:

For detailed developer documentation, see:

Props to @swissspidy, @andrewserong, and the many other contributors who worked on this feature. Thanks to @wildworks and @andrewserong for reviewing this post.

#7-1, #dev-notes, #dev-notes-7-1

Fetched July 22, 2026

Image processing moves to the browser (v7.1) — WordPress… — releases.sh