Modern image optimisation for WordPress in 2026

Modern image optimisation for WordPress in 2026

Last verified: October 20, 2026
22 min read
Guide
Core Web Vitals
UI/UX designer

AVIF is accepted by 96.35% of tracked browser usage, according to caniuse (figure as of 12 October 2026). The 2024 Web Almanac media chapter found it on 1.0% of mobile images. That is a gap of more than 95 points between what browsers accept and what sites actually send, and it is not a browser problem. It is a pipeline problem: the upload path, the markup that selects a candidate, and the moment the browser finds out the image exists.

This guide works through those three layers in WordPress specifically: what core already does since 6.3, 6.5 and 6.7, what you still have to wire yourself, and which of the popular 2026 talking points do not survive a check against a primary source.


#How to find the LCP element in WordPress

Before you convert anything, confirm with field data, not an assumption, which element is the Largest Contentful Paint on each template. The Web Almanac 2024 measured that element as an image on 68% of mobile pages, with a median of 135 KB for the largest image. Read the other way round: on the remaining 32% the LCP is text, and there an AVIF conversion does not move the metric by a single millisecond.

Even when the LCP is an image, core cannot help in two cases: when the element is not an img tag core rendered, and when it differs by breakpoint. Both are covered, together with Image Prioritizer and Optimization Detective, in the fetchpriority section below.

Both plugins come from the core performance team, the same team that publishes on make.wordpress.org/performance, so what you install as a plugin today shows the project’s official direction.


#How to enable AVIF uploads in WordPress

WordPress 6.5 added AVIF support in February 2024. You can upload and serve .avif the same way you serve JPEG, with no plugin, provided the server’s Imagick or LibGD build can encode it. That condition is the first thing to check, and it has a page in the admin: Tools > Site Health > Info > Media Handling. Hosts vary widely here, and a theme that assumes AVIF on a box without it silently produces nothing.

What core does not do is convert anything on its own. An uploaded JPEG stays a JPEG, and its sub-sizes stay JPEG. The hook that changes this is image_editor_output_format, which has existed since WordPress 5.8 and maps a source mime type to an output mime type:

add_filter( 'image_editor_output_format', function ( $formats ) {
    $formats['image/jpeg'] = 'image/avif';
    return $formats;
} );

Three consequences worth knowing before you ship that filter:

  • The original upload is preserved untouched. Only the generated sub-sizes take the new format, which is what keeps the media library re-editable.
  • It applies to future uploads only. Existing media needs a regeneration pass.
  • Its default value is not empty. As of WordPress 6.7 core ships HEIC and HEIF mappings to JPEG in this filter, so return the array you were given rather than a fresh one.

The other core setting that shapes every upload is big_image_size_threshold, 2560 pixels since WordPress 5.3. Anything wider or taller is scaled down and the scaled copy becomes the largest available size. PNG uploads are excluded from that scaling. If your hero slot is genuinely wider than 2560, you are already serving an upscale and no amount of format work will fix the softness.

#How slow is AVIF encoding

AVIF is not free at encode time. Cloudflare’s own transformation documentation states plainly that AVIF encoding can be an order of magnitude slower than encoding to other formats, and that if an image is too large to encode quickly, Cloudflare falls back to WebP or JPEG. The same arithmetic applies on your origin. A bulk regeneration of a 40,000 item media library to AVIF is a scheduled job with a queue, not a click in a settings screen.

#How to regenerate thumbnails with WP-CLI

The filter only reaches new uploads, so an existing library needs an explicit pass. wp media regenerate walks the attachments and recreates the sub-sizes with the current configuration, including the output format you just set. Two flags save most of the grief: --only-missing, so a run that was cut off does not redo finished work, and --image_size, to limit the pass to one size when only one changed.

wp media regenerate --only-missing --yes

Bound the run as well as the command. On shared hosting, a regeneration across tens of thousands of attachments competes for the same CPU that serves real traffic, and AVIF is the format that asks for the most. Split the library by attachment ID ranges, run outside peak hours and keep the log: the list of attachments that fail is the inventory of corrupt originals nobody has looked at in years.

If the result disappoints, the way out is to rerun the pass with a different quality setting, not to restore a backup. The original was never touched.

Quality is also a global, per mime type setting rather than a per image judgment. WP_Image_Editor::$default_quality is 82, deprecated since WordPress 5.8.1 in favour of get_default_quality(), which returns 86 for WebP and that same 82 for JPEG and everything else. JPEG then passes through the jpeg_quality filter, which runs after wp_editor_set_quality and therefore has the last word. One number for the whole library is a compromise by construction: it is too high for flat illustrations and too low for skin tones and gradients.


#Which browsers support JPEG XL in 2026

This is where most 2026 guides get it wrong. JPEG XL is only starting to arrive.

The first real change came on 6 October 2026. Google’s announcement reads: “Chrome is shipping decoding support for the JPEG XL (.jxl) image format starting from Chrome 155” (Chrome for Developers). Desktop Chrome users pick that up as their browser updates over the following weeks. Edge, which runs on the same engine, has not followed yet.

A week later, on 13 October 2026, Firefox 158 shipped. Its MDN release notes say: “Support for the JPEG XL image format (image/jxl) is now enabled by default.” (MDN). The default Accept header for image requests is now image/avif,image/jxl,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5, so a CDN that picks the format from that header will see JXL from Firefox users too.

caniuse puts JPEG XL at 60.19% global support as of 20 October 2026, up from 16.95% on 12 October and 15.02% when this section was first written. Of that, 43.26% is full support and 16.93% partial: Safari decodes still images only, without progressive decoding. The actual state, browser by browser:

BrowserJPEG XL status
Safari (macOS, iOS)Partial support, version 17 and later
Chrome (desktop)Supported from Chrome 155 (6 October 2026). Chromium enabled it, removed it, reintroduced it as disabled-by-default in Chrome 145, and now ships it
EdgeNo support yet, despite the shared engine
FirefoxEnabled by default from Firefox 158 (13 October 2026). Earlier versions needed Firefox Labs or the image.jxl.enabled flag
Chrome for AndroidSupported from Chrome 155 (Chrome for Android 154 still had it behind a flag)
Samsung InternetNo support (version 30)

The Web Almanac did not even measure JPEG XL usage in 2024, for the stated reason that Chromium-based browsers do not support the format. That is a more honest signal than any adoption chart: the data set that measures the whole web did not consider it a delivery format. Chrome 155 changes the inputs for the next edition, not the conclusion for a site you ship this month. Edge, Samsung Internet and WordPress core still do not handle JPEG XL, Safari support is partial, and AVIF already reaches 96.35% of browser usage. JXL still adds little as a delivery format and stays out of srcset for now.

File size does not favour JPEG XL either. Jake Archibald’s comparison (15 October 2026) found AVIF smaller in most web use cases: for lossy photos the best JPEG XL file is typically 15-25% larger than the equivalent AVIF at default encoder effort, and for screenshots and illustrations 30-90% larger. WordPress core still rejects .jxl uploads. wordpress-develop PR #12006, which adds server-side support for them under Trac ticket #52788, was still open on 23 October 2026, and even that change has the browser upload a JPEG or PNG version for the site while the .jxl file is kept only as the stored original.

What this means in practice:

  • As an archival or master format it is genuinely good. Lossless JPEG transcoding back and forth is a real feature and a real reason to keep JXL masters. Jake Archibald measured existing JPEGs shrinking by 9-20% when converted to JPEG XL without further loss. That is a storage decision, not a delivery decision, and it never touches your srcset.
  • If you do serve it, it has to sit as the first <source type="image/jxl"> in a <picture> with an AVIF source and a JPEG fallback behind it. Visitors on Edge, Samsung Internet and older browsers still take one of the fallbacks, so the JXL branch buys you nothing you did not already have to build.
  • Do not use JPEG XL as the argument for delaying AVIF. AVIF is the format with 96.35% support today.

Update, 23 October 2026. John Mueller of Google wrote on 15 October: “FWIW if you’re hoping to get images shown in Google Images & as previews in Discover, etc - JPEG-XL is currently not supported” (John Mueller, Bluesky). Google’s image documentation lists the formats Search supports in the src attribute of img: BMP, GIF, JPEG, PNG, WebP, SVG and AVIF (Google Search Central). In a <picture> with a JXL source, the <img> fallback should therefore point to an AVIF, WebP or JPEG file, and an image you want in Google Images or as a Discover preview should never exist only as .jxl. The same applies to any CMS, shop platform or static site. We settle format choice as part of WordPress speed optimisation.


#How to use srcset and sizes in WordPress

An efficient format at the wrong dimension still ruins LCP. If the mobile hero has a CSS width of 390 px and src points at a 2000 px file, the browser downloads megabytes too much, even in AVIF.

WordPress registers sizes with add_image_size(), and at render time wp_get_attachment_image_srcset() builds the candidate list. The sizes attribute tells the browser which CSS width to expect per breakpoint. Without a realistic sizes (for example (max-width: 768px) 100vw, 720px) the browser can pick an oversized candidate. The default sizes and how to register your own are in the TimThumb guide.

A short checklist:

  • Register only the sizes the theme uses (avoid dozens of dead crops).
  • Regenerate thumbnails after a change (wp media regenerate, see above).
  • On the hero: confirm in DevTools Network which file width was chosen, not just the Content-Type.
  • In Gutenberg blocks: keep images in line with the theme’s content width; 5000 px uploads “because we will crop later” increase storage and generation time.

Useful documentation: the wp_get_attachment_image_srcset reference in the Developer Handbook and the image material on web.dev explain the same idea from two sides (CMS and browser).


#How WordPress sets fetchpriority on the LCP image

The advice to add loading="eager" and fetchpriority="high" by hand is three years out of date for content images. WordPress 6.3 added wp_get_loading_optimization_attributes(), which computes both attributes for images that pass through core’s rendering path. Its documented behaviour:

  • The first qualifying image gets fetchpriority="high". Qualifying means a minimum area of 50,000 pixels (width multiplied by height), adjustable through the wp_min_priority_img_pixels filter.
  • loading="lazy" is omitted for images near the top of the document. The threshold filter is wp_omit_loading_attr_threshold, whose default rose from 1 to 3 in 6.3.
  • The function never emits fetchpriority="high" and loading="lazy" together, because they are contradictory instructions.

WordPress 6.7 then added sizes="auto" for lazy-loaded images, via wp_img_tag_add_auto_sizes(). This is the fix for the oldest srcset bug in WordPress: the generated sizes attribute assumed the image filled the viewport, so a 300 pixel sidebar thumbnail could pull a 1024 pixel candidate on mobile. Because a lazy image does not load until layout is known, the browser can now use the real rendered width. Core prepends auto only when the image already carries loading="lazy", and enqueues a small CSS rule (wp_enqueue_img_auto_sizes_contain_css_fix()) to avoid a layout side effect.

So if core covers the common case, what is left to do? Two failure modes:

  1. The LCP element is not an img tag core rendered. A CSS background image, a video poster, a page builder that writes markup outside wp_filter_content_tags(). Core cannot prioritise what it cannot see, so the hero arrives after the stylesheet that references it.
  2. The LCP element differs by breakpoint. A desktop hero photo and a mobile card image are different elements. A static guess is right for one of them.

Both are what Image Prioritizer exists for. It depends on Optimization Detective, which collects URL Metrics from real visitors per breakpoint and therefore knows which element actually was the LCP rather than guessing from document order. With that data Image Prioritizer adds a high-priority preload for the LCP image, as both an HTML LINK element and an HTTP Link header, and the preload also covers CSS backgrounds (inline or from a stylesheet) and video posters. It sets fetchpriority=high on an img only when that element is the LCP at every breakpoint, sets fetchpriority=low on images that are inside the initial viewport but hidden (carousel slides two and three, the classic case), and lazy-loads background images declared in inline style attributes. It has no settings screen and only acts once visitor metrics have been collected.


#How to set image compression quality in WordPress

The claim that an encoder detects a face, compresses the sky aggressively and delivers a 70% saving is the kind of number that gets repeated without a source. Treat any such figure as unverified until you have measured it on your own library.

What is real and checkable:

  • Per-image quality beats a global quality number. The two quality filters take different arguments, and mixing them up is how a per format rule ends up applying to everything. wp_editor_set_quality receives the default quality, the mime type and the image dimensions, so it can vary quality by format and by size. jpeg_quality receives only the quality and a context string such as image_resize. A photo-heavy portfolio and a UI screenshot library do not want the same setting.
  • AVIF degrades differently from JPEG. JPEG fails visibly, with blocking and ringing that you can spot in a thumbnail. AVIF at low quality smooths fine texture instead: grain, fabric, foliage and hair lose detail while edges stay clean. That failure mode is harder to catch by eye at small size, so the check has to be at full resolution on the actual template.
  • The only two measurements that count are the delivered byte count of the LCP resource, and the visual result on the template it ships in. A plugin dashboard reporting “saved 68%” is reporting on files, not on what a browser downloaded after the edge finished negotiating format.

If you run a conversion pass, keep a small fixed set of representative images from the real library: one face, one product on white, one screenshot with small text, one wide landscape. Encode those at each candidate setting and look at them. That is a twenty minute job that prevents shipping a library-wide setting that destroys product photography.


#Cloudflare image resizing for WordPress

Edge transformation takes resizing and format negotiation off the PHP origin. On Cloudflare that means a URL of the form https://<ZONE>/cdn-cgi/image/<OPTIONS>/<SOURCE-IMAGE>, where the source is an absolute path on the origin or an absolute URL, and the options are comma separated. The one that matters here is format=auto, which serves the most efficient format the requesting browser accepts.

Three things the documentation says that change how you plan the work:

  • It must be enabled per zone in the dashboard before any transformation URL resolves on that hostname. This is the single most common reason a correctly written URL returns the unresized original.
  • Source images are restricted to the same zone by default. Accepted sources default to an allowed-origins list, and Cloudflare documents that separately from the URL format: a separate media subdomain or an S3 bucket has to be added to that list, or the zone switched to any origin.
  • format=auto can decline AVIF. Because AVIF encoding is an order of magnitude slower, Cloudflare falls back to WebP or JPEG when an image is too large to encode quickly. Your hero may therefore be AVIF in testing and WebP in production, depending on the source dimensions.

Edge resizing does not remove the need for correct srcset and sizes (how WordPress builds them from add_image_size() is covered in our guide to TimThumb and native image handling). The browser still picks a candidate before it makes a request, so wrong markup requests a wrong width and the edge dutifully generates exactly that wrong width. Edge resizing replaces the fixed WordPress size set with arbitrary widths; it does not replace the selection logic.


#How to serve AVIF with a JPEG fallback

Core writes a single src and a srcset. It does not write <picture>, which means core alone cannot serve AVIF to browsers that accept it and JPEG to those that do not, from the same markup. You get one format per generated size.

There are two workable answers.

Content negotiation at the edge or the server. The CDN reads the Accept header and serves AVIF, WebP or JPEG from one URL. This keeps the HTML simple, and it is what format=auto does. The cost is that HTML and image caching now depend on request headers, so cache keys have to account for it.

Markup-level fallback. The Modern Image Formats plugin, part of the Performance Lab programme, generates modern-format sub-sizes on upload and, since version 2.0.0, can output <picture> elements with fallbacks. Its default choice is AVIF when the hosting server supports it and WebP otherwise, which is the right default for a plugin that cannot know your stack. Its settings live under Settings > Media, including whether to keep fallback originals. As with the core filter, it applies to new uploads: existing media needs regeneration.

Performance Lab is a staging area for features intended to land in core, so its image plugins are worth treating as the current direction of travel rather than as third-party add-ons.


#Image optimization FAQ for WordPress

  1. Can I delete my old JPEGs? Not if anything still points at them. Beyond the browser, JPEG remains the safe format for email clients, Open Graph and Twitter card images, RSS consumers, and any partner feed. Modern Image Formats keeps the original upload, but by default it generates JPEG and PNG sub-sizes only in the modern format. If a feed or a social card needs JPEG sub-sizes, tick Output fallback images under Settings > Media: the plugin then generates the original format next to the modern one for every sub-size, at the cost of more disk and more time per upload.

  2. Is SVG still relevant for icons? Yes, and for a reason that has nothing to do with compression: SVG is resolution independent, so one file covers every display density with no srcset. WordPress core does not allow SVG uploads by default because SVG can carry script. If you enable it, sanitise on upload rather than trusting the uploader.

  3. Should I use progressive or interlaced JPEG? It is a minor lever next to format and width. Progressive JPEG improves perceived loading on slow connections, but for an LCP image the browser is waiting on the full decode anyway, and for below-fold images the user is not looking. Spend the effort on sizes correctness first.

  4. My host cannot encode AVIF. What now? Use WebP as the output format through image_editor_output_format or the Modern Image Formats setting, and check Site Health after any host migration. WebP support is far more widely available in older Imagick and LibGD builds, and the difference between JPEG and WebP is much larger than the difference between WebP and AVIF.

  5. Does AVIF help if my LCP is text? No, and that is worth checking before any of this work is scheduled. Run the page through field data and confirm which element is actually the LCP. Images are the LCP on 68% of mobile pages, which also means they are not on the other 32%.


#How to optimise images in WordPress step by step

A flow that scales without operational chaos:

  1. Upload: master photo in sRGB, maximum width in line with the theme’s largest breakpoint (for example 1920 or 2560 px, not 8000 px straight from the camera; core scales down anything above 2560 px anyway).
  2. Generation: core creates intermediate sizes in the upload’s format; image_editor_output_format, Modern Image Formats or the CDN gives you AVIF or WebP.
  3. Rendering: wp_get_attachment_image() (or the Image block) with correct srcset/sizes.
  4. Hero: let core set fetchpriority="high" and skip lazy; preload only when the hero is a background or sits outside core’s markup.
  5. Everything else: lazy; no preload.
  6. Monitoring: PageSpeed/CrUX on the real URL, filtered on “LCP element” and image weight.

On shared hosting without Imagick AVIF, conversion at the CDN edge is often cheaper than switching servers for a single codec.


#Common image optimisation mistakes in WordPress

  • PNG for photographs. PNG is for UI and transparency. A photo in PNG bloats disk and transfer.
  • Quality 100 in JPEG/AVIF. The difference from a moderate level is rarely visible, but it shows in the weight.
  • 1:1 and 16:9 crops “just in case” that are never used. Every upload multiplies I/O and backups.
  • Lazy on the logo and hero. Delays LCP, and CLS too if dimensions are missing from the markup.
  • Two optimisation plugins at once. One rewrites URLs, the other serves WebP; the result is a 404 or double processing.
  • Ignoring width and height. Without intrinsic dimensions the layout jumps when the image arrives.

#Image accessibility requirements for WordPress sites

Performance guides usually leave this out, and for many sites it is now a legal requirement. The European Accessibility Act (Directive (EU) 2019/882) has applied since 28 June 2025 to e-commerce services offered to consumers in the EU. It leads back to the harmonised standard EN 301 549 and, through it, to the WCAG 2.1 criteria at level AA. If the project is a shop selling into the EU, image work has two criteria to meet besides being light.

The first is non-text content (1.1.1). An empty alt is correct and desirable for a decorative image, and a defect for an image that carries information. WordPress stores that field in the media library, so an audit is a query on postmeta for _wp_attachment_image_alt, not a template by template review.

The second bites directly into this topic: images of text (1.4.5). A promotional banner with the headline burned into the pixels fails the criterion, cannot be translated or selected, and is also the worst possible candidate for AVIF, because the fine-texture smoothing that format applies hits small type hardest. HTML text over the image solves the legal problem and the compression problem at once. The same goes for UI screenshots with tiny lettering: if they are illustrative, crop and enlarge them before encoding instead of uploading the whole screen and trusting the encoder.


#Image optimisation checklist before publishing

  • How much does the hero weigh in the typical mobile variant, compared with the 135 KB median for the largest image in the Web Almanac 2024?
  • Does Network show AVIF or WebP on the actual document, not just on the plugin’s test page?
  • Does sizes match the real CSS width of the content or hero?
  • Does only one image carry fetchpriority="high"?
  • Does the CDN (if there is one) answer 200 with cache-hit after the second request?
  • Is the hero’s CLS stable (dimensions or aspect-ratio in CSS)?
  • Do informative images have alt text, and is text in banners HTML rather than pixels?

If an item fails, fix that item before buying more tooling.


#Conclusion

The formats are settled. AVIF has 96.35% browser support and native WordPress support since 6.5. JPEG XL has neither: Chrome 155 started decoding it on 6 October 2026 and Firefox 158 on 13 October, but Edge, Samsung Internet and WordPress core have not, so it stays out of srcset for now. WebP is the fallback that keeps working everywhere. Nothing in that list is a decision that still needs research.

What is unsettled on almost every site is everything between the upload and the paint: whether sub-sizes are being generated in a modern format at all, whether sizes describes the real layout, whether the LCP element is one core can see, whether the edge is quietly serving WebP because the source was too large to encode, and whether alt text and images of text pass the accessibility check. That is the list to walk, in that order, with a measurement at each step. The 1% adoption figure in the Web Almanac is what happens when a site stops at “we installed an image plugin”.

If images are dragging down your Core Web Vitals, the useful first artefact is a short inventory: the affected templates, where each image comes from, and the current LCP element and timing per template. We work through exactly that list as part of WordPress speed optimization.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Frequently asked questions

Practical answers to apply the topic in real execution.

Is WebP outdated in 2026?#
No. WebP is the correct output format on any host whose Imagick or LibGD build cannot encode AVIF, and the Modern Image Formats plugin falls back to it automatically for exactly that reason. AVIF compresses better at the same visual quality, but it also encodes far more slowly, which matters on shared hosting and at the CDN edge.
How does image optimisation affect LCP?#
An image is the Largest Contentful Paint element on 68% of mobile pages according to the 2024 Web Almanac, where the median largest image on mobile is 135 KB. Format alone does not fix LCP: if the browser discovers the image late, or picks a wider srcset candidate than the layout needs, the smaller file still arrives too late to help.
Should I use lazy loading for all images?#
No, and since WordPress 6.3 you usually should not do it by hand. wp_get_loading_optimization_attributes() skips loading="lazy" on the first images in the document, controlled by wp_omit_loading_attr_threshold whose default is 3, and marks the first qualifying image fetchpriority="high". The case that still breaks is an LCP image core cannot see, such as a CSS background.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles