Is there a problem that needs an urgent fix?Urgent fix?Get emergency help →
Blog

Practical Magento 2 guides on upgrades, security, speed and migration, written by the MageBooster team.

Serving WebP product images in Magento behind Cloudflare

Magento rejects WebP uploads and Cloudflare’s free plan ignores Accept-header negotiation. Here is the cron job and small plugin we use to serve WebP safely.
Serving WebP product images in Magento behind Cloudflare
Update

Hyvä now runs natively on Magento 2.4.9 and on Mage-OS. We have tested both. What this means for your store →

In short

Magento’s default image adapter will not accept WebP source files, and Accept-header negotiation did not work for us behind Cloudflare’s free plan. Our fix: keep JPG sources, let Magento resize them, convert every resized file to WebP with a cron job, and use a small plugin that returns the WebP URL when the file exists. It is simple, cache-safe and works in every modern browser.

Product images are usually the heaviest part of a Magento page, and often the largest element on screen. Serving them as WebP instead of JPG is one of the easiest ways to make pages lighter.

Product images on this Hyvä category page are served as WebP copies of the resized JPGs.
Product images on this Hyvä category page are served as WebP copies of the resized JPGs.

It sounds like a one-line change. On our Hyvä demo store it took three attempts. This post walks through what failed, what we run now, and the small Magento plugin that holds it together. It is part of the speed work we do on every Hyvä store, and it works on Luma too.

Attempt one: upload WebP files

The obvious idea is to upload product images as WebP in the first place. Magento’s GD image adapter, the default on most servers, rejects WebP source files with an “Unsupported image format” error.

So we kept JPG as the source format. That has a side benefit: the original files stay in a format every tool understands, and you can change the WebP quality later without touching the catalogue.

How Magento resizes product images

Magento never sends the original upload to the browser. It creates resized copies for each image role defined in the theme’s view.xml, such as the category grid, the product gallery and thumbnails.

Those copies live in pub/media/catalog/product/cache. Magento creates them on demand, or in bulk with this command:

bin/magento catalog:images:resize

We run the bulk resize after imports and theme changes, so customers never wait for an image to be generated. These resized files are the ones we convert to WebP.

Attempt two: Accept-header negotiation

The classic server-side approach uses the browser’s Accept header. Browsers that support WebP say so in that header, and nginx can serve file.jpg.webp instead of file.jpg at the same URL.

It works on a single server. Behind Cloudflare’s free plan it did not work for us, because Cloudflare does not vary its image cache on the Accept header.

The first visitor decides which version Cloudflare caches for that URL. Everyone after them gets the same file, whatever their browser supports. One URL serving two different files is a recipe for confusing bugs, so we dropped the idea.

What we run now

The working setup gives the WebP file its own URL. Caching is then trivial, because each URL always returns the same file.

  1. Product images are uploaded as JPG.
  2. Magento resizes them into the image cache folder.
  3. A cron job runs cwebp -q 80 and writes file.jpg.webp next to every resized image that does not have one yet.
  4. A plugin changes the image URL to the .webp version when that file exists on disk.

The conversion step is a short shell loop:

find pub/media/catalog/product/cache -type f -name '*.jpg' | while read -r f; do
    [ -f "$f.webp" ] || cwebp -quiet -q 80 "$f" -o "$f.webp"
done

Quality 80 gave us clearly smaller files with no difference we could see on product photos. Test a few of your own images, because the best value depends on the photography.

The Magento plugin

Magento builds product image URLs in Magento\Catalog\Model\View\Asset\Image. An afterGetUrl plugin on that class can swap the URL without changing a single template.

The plugin is declared in the module’s etc/frontend/di.xml:

<type name="Magento\Catalog\Model\View\Asset\Image">
    <plugin name="vendor_webp_image_url"
            type="Vendor\Webp\Plugin\ImageUrlPlugin"/>
</type>

The plugin itself only checks whether the WebP copy exists:

public function afterGetUrl(Image $subject, string $result): string
{
    if (is_file($subject->getPath() . '.webp')) {
        return $result . '.webp';
    }
    return $result;
}

If the cron has not converted an image yet, the plugin returns the normal JPG URL. Nothing breaks, and the next page view after conversion gets the WebP file.

All modern browsers support WebP, so we serve it to everyone and skip the old picture-element fallbacks. That keeps the markup simple.

If you prefer a ready-made module, several WebP extensions exist for Magento. Check how they behave behind your CDN before you rely on them, and make sure they work with Hyvä templates. Our extension development team can review or build this for you.

Theme images and the CDN

Product images are only part of the picture. Theme images such as homepage tiles and the login page artwork are also WebP on our demo store, at 720 pixels wide and around quality 72.

The CDN brought one more surprise. Cloudflare had cached some image URLs before the resize cache existed, and kept serving the wrong response after the files were created.

The fix is to purge the CDN cache for the media hostname after a bulk resize or conversion. We now do this as a fixed step after every image job.

Pitfalls in one list

  • WebP uploads are rejected by Magento’s GD adapter, so keep JPG sources.
  • Accept-header negotiation was ignored by the CDN cache, so give WebP its own URL.
  • Images cached at the CDN before resizing need a purge.
  • Lab tests can give fake results if your server blocks headless browsers, which we cover in our Lighthouse testing post.
  • Disk usage grows slightly, because each resized image now has a WebP twin.

FAQ

Why not convert to AVIF instead?

AVIF can be smaller still, but encoding is slower and tool support on servers is less consistent. WebP gave us a large gain with a simple, reliable pipeline, so we started there.

Does this work on Luma stores too?

Yes. The plugin works on the Magento image asset class, not on the theme, so Luma and Hyvä stores both benefit. Hyvä simply has less other weight on the page, so the image gain shows more.

What happens when a product image is replaced?

Magento creates new resized files with new paths, and the cron converts them on its next run. Until then, the plugin serves the JPG, so customers always see the correct image.

Want lighter product pages without rebuilding your catalogue? See how we approach Magento speed optimisation, or start a Hyvä project with us.

Installation guides: Magento 2.4.9 on Ubuntu Mage-OS Hyvä
Spotted a mistake on this page? Tell us and we will fix it right away.

Free Site Audit

Send your store details. We check speed, security and extensions, then reply with three quick wins within 3 working days.

Let's talk

Get a quote

Tell us about your store and we’ll reply within one working day. Prefer to chat? Say hello from WhatsApp

MageBooster Book a UnoPim demo See UnoPim live with products like yours. A new PIM or a move from Akeneo: we reply with a plan, a timeline and a quote.
✓ Official UnoPim partner✓ AI agent set up for your team✓ Connected to Magento
MageBooster Start your Hyvä project From scratch or from your current store. We reply with a project plan, a timeline and a quote.
✓ Design and structure by us✓ Hyvä-optimised server✓ Bonus modules included
Partner with us