WordPress Images

How to Convert Images to WebP in WordPress

Core converts on upload through a single filter and ignores everything already in the library. Here is what that filter really covers, what regeneration adds, and why the uploads folder grows before it shrinks.

How to Convert Images to WebP in WordPress

You have decided WebP is the format you want. What is left is the mechanical part: what WordPress converts on its own, what it refuses to touch, and where the JPEG and PNG files you already have end up when the job is finished.

Core can convert. The image_editor_output_format filter has existed since WordPress 5.8, and since 6.7 it ships with a default value already in it: HEIC and HEIF uploads are mapped to JPEG so browsers can display them. What core will not do is reach back into wp-content/uploads and rewrite files that are already sitting there.

That gap is where most WebP migrations stall. The filter goes in, new uploads come out as WebP, and three years of existing images stay exactly as they were. Closing the gap means regeneration, and regeneration has consequences the five-step tutorials leave out.

If you are still weighing WebP against AVIF, or arguing with yourself about encoder quality, that belongs in the format comparison. From here on, assume the decision is made.

What core converts on upload

When a file lands in the media library, media_handle_upload() inserts the attachment row and then calls wp_generate_attachment_metadata(), which for images hands off to wp_create_image_subsizes() in wp-admin/includes/image.php. That function builds a fresh metadata array with an empty sizes list, then makes two decisions before it writes anything.

The first is size. If either dimension is above the value returned by the big_image_size_threshold filter, 2560 pixels by default, the image is scaled down and saved with -scaled appended to the file name. That scaled file becomes the full size WordPress serves, and the file you actually uploaded stays on disk, recorded in the attachment metadata as original_image.

The second is format. Core asks wp_get_image_editor_output_format() whether this source mime type should be written as a different one. If the answer is yes and the image is under the threshold, core saves it over the same base name with the new extension and no suffix, because the comment in the source says exactly that: pass an empty string to avoid adding a suffix to converted file names. If the image is over the threshold, the scale-down and the conversion happen in the same save, and you get name-scaled.webp.

Sub-sizes come last. _wp_make_subsizes() is handed the original file rather than the scaled one, deliberately, for quality, and every one of those saves goes through the same output format lookup. So one upload of a 4000 pixel JPEG, with a WebP mapping active, leaves you with the untouched original JPEG, a scaled WebP as the full size, and a WebP for every registered size.

Diagram of the WordPress upload pipeline showing media_handle_upload, wp_create_image_subsizes, the 2560 pixel big image threshold, the image_editor_output_format filter and _wp_make_subsizes, followed by a table of the three files left on disk: the untouched original JPEG, a scaled WebP full size and a WebP sub-size.

The filter, written correctly

wp_get_image_editor_output_format( $filename, $mime_type ) returns an array that maps a source mime type to a destination mime type. Out of the box it contains four entries, all of them mapping HEIC and HEIF variants to image/jpeg. That array is then passed through the image_editor_output_format filter. Adding your own entries to it is the entire mechanism.

add_filter(
	'image_editor_output_format',
	function ( $formats ) {
		$formats['image/jpeg'] = 'image/webp';
		$formats['image/png']  = 'image/webp';

		return $formats;
	}
);

Two details matter here. Add to the array you are handed and return it, rather than returning a fresh array of your own, or you throw away core’s HEIC and HEIF mappings and iPhone uploads stop being converted into something a browser can render. And the mapping is only honoured if the image editor on that host can actually write the target: WP_Image_Editor::get_output_format() runs your destination type through supports_mime_type() and, if it fails, quietly keeps the source format. Nothing errors, nothing is logged. You just get JPEGs and no explanation.

Check that before you blame the filter. wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) returns a plain boolean, and Tools, then Site Health, then Info, then Media Handling prints both the active editor and a field called Image format transforms, which lists the mappings actually in effect. That is the fastest way to confirm your code is loaded and doing something.

Put the filter somewhere that outlives your theme. A single-file plugin in wp-content/mu-plugins is the safest home. A child theme’s functions.php works until the day you switch themes, after which every new upload silently reverts to JPEG and nobody notices for a month.

One side effect appears as soon as a mapping is active. wp_unique_filename() starts reserving names across formats, because photo.jpg and photo.png would otherwise both want to produce photo-300x200.webp. Upload photo.png into a folder that already contains photo.jpg and you get photo-1.png. That is core protecting you from a collision, not a bug.

What the filter does not do

It does not touch anything already in the library. The mapping is consulted at the moment the image editor saves a file, and nothing saves an existing attachment’s files again until you ask it to.

It does not delete. Conversion writes new files. The JPEG you uploaded stays on disk as the original, which is the behaviour you want, because every sub-size is generated from that file rather than from a compressed copy.

It does not update the attachment’s post_mime_type either. That column is written when the attachment row is inserted, which happens before the metadata generation that performs the conversion, and nothing in the conversion path rewrites it. So it describes the file that arrived rather than the file now on disk, and the media library’s file type filter is no use as a progress bar for this migration.

The subtlest limitation is the one that wastes an afternoon. Regenerating only the missing sizes achieves nothing here. wp_get_missing_image_subsizes() compares the registered sizes against the sizes array in the attachment metadata, and _wp_make_subsizes() then drops any size whose name already appears there. Core’s own comment is explicit: it only checks the size name, so it does not override existing images even when the dimensions do not match. A medium entry pointing at a JPEG counts as present, and a WebP one will never take its place.

Regeneration is the other half of the job

To convert what is already there, you run the upload pipeline again. A full regeneration calls wp_generate_attachment_metadata() on the path returned by wp_get_original_image_path(), which rebuilds the metadata from scratch with an empty sizes list, so nothing is skipped and nothing is generated from an already compressed copy. What regeneration fixes, and what it leaves untouched, is covered in more detail in the guide to regenerating thumbnails.

WP-CLI is the honest way to do this at volume, because it tells you what it is doing to each attachment and you can stop it.

# Convert a small, deliberately awkward sample first.
wp media regenerate 1041 1042 1043

# Then the rest, keeping the old thumbnails on disk.
wp media regenerate --skip-delete --yes

By default wp media regenerate deletes the old thumbnails before writing new ones, and --skip-delete turns that off. Its own help text is blunt about why you might want to: if your thumbnails are linked from sources outside your control, it is likely best to leave them around. Deleting is tidier, but it only removes what the attachment metadata knows about. Sizes registered by a theme you have since removed, or files written by a plugin outside the sizes array, are invisible to it. That is what the separate --delete-unknown flag exists for, and it is a separate decision on a separate day.

Note what none of the flags touch: the previous full size. If an attachment had a -scaled.jpg, regeneration writes a new -scaled.webp and repoints the metadata at it. The function that does the repointing only updates the metadata array and the _wp_attached_file value. It leaves the old JPEG exactly where it was. Nothing references it any more, and nothing is going to remove it for you.

The folder gets bigger before it gets smaller

A default install registers four sub-sizes, thumbnail, medium, medium_large and large, and most themes add a couple more. Take an attachment with six registered sizes that arrived above the threshold: the original JPEG, a -scaled.jpg and six sub-sizes, so eight files. Regenerate it with --skip-delete and you add a -scaled.webp plus six WebP sub-sizes, which makes fifteen. The library has not shrunk. It has roughly doubled, and it stays that way until you clean up, so check free disk space before you start rather than during.

The bloat is worth accepting for a while. Keeping the old files means a rollback is a metadata restore instead of a re-encode, and it means anything that hardcoded a thumbnail URL, an email template, a cached page, an external scraper, does not break the same afternoon you flip the switch. The tidying is a separate task done later, once the site has been watched for a week or two. Working out which files nothing references any more, and deleting them in an order that does not leave the database pointing at nothing, is the subject of cleaning up the media library.

Table comparing file counts for one attachment before conversion, after regeneration with deletion skipped and after cleanup: eight files, then fifteen, then eight again, with the core defaults of four registered sub-sizes and a 2560 pixel threshold.

Serving WebP, with or without a fallback

WordPress does not do content negotiation. Nothing in core inspects the browser’s Accept header to choose between two files. wp_filter_content_tags() rewrites img tags on output to add srcset and sizes, plus the loading, decoding and fetchpriority attributes, but every URL in that srcset names one specific file with one specific extension. If that file is WebP, every visitor gets WebP.

Which is usually fine. Every browser that sends meaningful traffic reads WebP, and Safari, the last major holdout, caught up years ago. If your analytics shows no long tail of very old devices, converting in place and serving one format to everyone is the simplest correct answer, and the only one that genuinely reduces storage. A fallback is a real cost, so make sure you are buying something with it.

If you do need one, there are three shapes, and you should choose on operational grounds rather than elegance.

The picture element keeps both files and changes the markup, so the browser picks:

<picture>
  <source srcset="https://cdn.wp-image-editor.com/wp-content/uploads/2026/03/hero.webp" type="image/webp">
  <img src="https://cdn.wp-image-editor.com/wp-content/uploads/2026/03/hero.jpg" alt="Studio interior" width="1200" height="800">
</picture>

It is visible in view source, it caches like any other HTML, and when something goes wrong you can see it. The cost is that a plugin has to filter your output HTML, and page builders, lazy loading scripts and core’s own srcset generation are all editing the same tags. A wrapper that mishandles the sizes attribute will hand the wrong candidate to everybody, quietly.

The server rewrite keeps both files and leaves the HTML alone. An Apache or nginx rule checks whether the request advertises WebP support and whether a matching .webp file exists next to the JPEG, and serves that instead. The markup never changes, which is why this approach survives page builders. It has one non-negotiable requirement: the response must send Vary: Accept. Miss it and the first visitor to warm a proxy or CDN cache decides what everyone behind that cache receives, which is exactly how a WebP file ends up in front of a browser that cannot decode it.

The CDN moves the whole problem to the edge. The origin keeps one format, the CDN derives and caches modern formats per request. Least to maintain locally, at the cost of putting your image pipeline inside a vendor’s product and trusting its cache key.

The first two both mean keeping both formats indefinitely. Decide that consciously, because permanent duplication is the opposite of what most people expect this project to achieve.

Table comparing four ways to serve WebP, converting in place, the picture element, a server rewrite and a CDN, by whether each edits your HTML, keeps both formats and needs a Vary Accept header, with two panels contrasting the markup approach and the HTTP approach.

Both halves of the job, the conversion and the delivery, can be prepared before you touch the server.

Drop your images below and they are converted in your browser, with the size and the saving for each one, including the cases where WebP comes out larger, which happens more often than the usual advice admits. Underneath sit the four delivery patterns, the picture element, the Apache rewrite with the Vary header that everybody forgets, the Nginx map, and the WordPress filters, each with a note on when it is the right one.

WebP converter and delivery

Convert images to WebP, see what each one really saves, and take the delivery block that fits your server. Every file is read, drawn on a canvas and handed back by this browser alone: nothing is uploaded and no request leaves the tab.

Images
Drop images here
or press Enter to pick some. Up to 100 files at a time, JPEG, PNG, GIF or WebP.

A browser reaches lossless WebP through the top of the quality scale, so quality 100 and the lossless box are the same encoder setting. A target width only ever scales an image down.

Nothing converted yet
FileSourceWebPChangeDownload

Nothing packed yet.

Serving the files


  
What goes wrong in practice
  • Vary: Accept is missing. The server hands the same URL a WebP file to one visitor and a JPEG to the next. Without that header every shared cache, CDN and reverse proxy keeps whichever copy it saw first and serves it to everybody. That is the single failure that breaks sites, and it looks like a random broken image nobody can reproduce.
  • WebP goes to a client that cannot read it. Negotiation trusts the Accept header. Crawlers, link preview bots, older email clients and image proxies often send a bare Accept, or one that lists nothing useful. Rewrite only when the header really names image/webp, never on a guess.
  • A cache plugin does not see the negotiation. Full page caches and CDN rules that were written before WebP existed store one variant per URL. Either teach the cache about the Accept header, or use the picture element, which needs no negotiation at all.
  • The -scaled file is forgotten. WordPress makes name-scaled.jpg for large uploads and serves that as the full size, so a rule that only looks at name.jpg misses the file people actually load. Convert every entry in the metadata, the scaled original included.
WunderPaint
The Dynamic Design and Automation Studio
WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

Quality, briefly

Core’s default WebP quality is 86, returned by WP_Image_Editor::get_default_quality(), against 82 for everything else including JPEG. Both pass through the wp_editor_set_quality filter, which since WordPress 6.8 receives the default, the mime type and the dimensions being written, so you can be stricter on large sizes than on thumbnails if you want to. Before you touch the number, remember what you are doing: converting an existing JPEG is a second lossy pass over an already lossy file, and the artefacts in the source get re-encoded along with the picture. Where the useful range sits, and what a badly chosen number looks like on real photographs, is the subject of the format comparison linked at the top.

When converting the library is not worth it

Format is the smaller half of most image weight problems. If a page ships an image four times wider than the slot it lands in, re-encoding it saves a fraction of the weight while resizing it properly saves most of it, and once the dimensions are right the format saving shrinks along with the file. Check which sizes are registered and whether srcset is even offering a candidate near the width the image is displayed at. How one upload becomes many files covers where those numbers come from and how to change them.

A few other cases where the batch job is a poor trade. Libraries of flat colour screenshots, diagrams and line art, where lossless PNG is already competitive and lossy WebP visibly smears the text. Icons and very small images, where the difference disappears into request overhead. Small sites, where a few dozen images do not justify a migration that touches every file you own. And sites that are slow for reasons unrelated to images, where converting them will not move the number you are watching.

A safe order of operations

The failure mode of this job is rarely dramatic. It is discovering three weeks later that a page nobody checks has been serving broken images since the batch ran. Slow it down.

  1. Take a real backup of wp-content/uploads and the database. The _wp_attachment_metadata rows in postmeta are the only authoritative map from an attachment to its files. Losing them hurts more than losing the images.
  2. Confirm the editor can write WebP at all, in Site Health under Info and then Media Handling, where the active editor and the image format transforms are both listed.
  3. Add the filter, upload one test image, and look at the folder on disk. You should see an untouched original plus converted sub-sizes.
  4. Regenerate ten to twenty existing attachments, chosen awkwardly on purpose: a photograph, a logo PNG with transparency, a screenshot full of small text, and the largest file in the library.
  5. Compare one real page before and after in the browser network panel, at the same viewport width. Look at total image bytes, then open the images at full size and look for banding in skies and mush in dark areas.
  6. Run the rest in batches, off peak. Regeneration decodes every original at full resolution, so memory is what gives out first on shared hosting, usually as a failed attachment rather than a visible error.
  7. Leave the old files alone for a couple of weeks. Clean up only once you are sure nothing is asking for them.

If you would rather not run this from a terminal, WunderPaint Pro’s Image Processor does the same batch from inside the admin: select attachments or paste IDs, choose WebP with a quality preset and an optional resize, and process them in one pass, either overwriting in place with versioning or writing new attachments.

Symptom and cause

New uploads are still JPEG. Either the filter is not loading, or the editor on that host cannot write WebP and core silently kept the source format. Check the Media Handling section of Site Health: it prints the format transforms that are actually in effect.

Regeneration finished but the thumbnails are still .jpg. You asked for missing sizes only. Core matches existing sub-sizes by name alone, so a JPEG medium counts as present and is never rewritten. Run a full regeneration instead.

The uploads folder doubled. That is expected. New WebP sub-sizes were written, the old ones were kept, and the previous -scaled file is now unreferenced. It shrinks at cleanup time, not at conversion time.

A few visitors see broken images, most see nothing wrong. A server rewrite or CDN is serving WebP from a cache entry that was populated for a different browser. The response is missing Vary: Accept.

Converted photos look blotchy in skies and skin tones. You are re-encoding an already lossy JPEG, so the source artefacts are being compressed a second time. Raise the quality value rather than the resolution, and compare against the original file rather than against the old thumbnail.

A new upload came out as photo-1.png. A file called photo.jpg was already in that month’s folder. With a mapping active, wp_unique_filename() reserves names across every format that could produce the same sub-size names.

After the conversion

The technical part of this job is four lines of PHP. The part that decides whether it goes well is bookkeeping: knowing that the attachment metadata is the only real map from a post to its files, that regeneration rewrites that map while leaving the previous files exactly where they were, and that nothing in WordPress will volunteer a list of what is now unreferenced.

So treat it as two jobs with a gap in the middle. Convert forward first, so that every image added from today onwards is handled without anyone thinking about it, then work through the existing library in batches at whatever pace you can supervise. That turns a migration which has to succeed all at once into a finite pile of work you can stop halfway through without breaking anything.

And keep the expectation honest. WebP produces noticeably smaller files on photographs and much less of a difference on flat graphics, which makes it a solid, unglamorous improvement rather than a fix. If the library is full of oversized originals being scaled down in the browser, sort out the dimensions first and the format second. In that order the savings compound. In the other order they mostly disappoint.

How to Convert Images to WebP in WordPress

Table of Contents

Learn it by building something

Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

One mail a week, and then it ends.
Unsubscribe in one click.

Photo Editing

Perspective Correction: Four Corners and One Matrix Put It Straight

A photo taken at an angle is a projective map, not an affine one, which is why cropping and rotating cannot repair it. Four corners supply the eight numbers that can, and the geometry of the vanishing points can even hand back the object's true aspect ratio.

WordPress Images

How to Edit Images in WordPress With the Built In Editor

WordPress has a built in image editor most people never open. Here is what every control does, exactly which files a save writes to disk, where the backup of your original lives, and why the Apply changes to radio buttons vanished.

Design Fundamentals

How to Choose a Color Palette Without Learning Color Theory

Palette generators hand you five swatches of equal weight, which is the one thing a real palette never is. Here is the ten minute version that works.

SEO & Structured Data

Permalink Structure Simulator: What That One Field Decides

The permalink field decides thirteen URL shapes at once, and most of them fail quietly when you change it. What each structure tag costs, what verbose page rules actually do, which collisions core resolves first, and which old URLs its canonical redirect will never rescue.

WordPress Images

Best Image Format for WordPress: WebP, AVIF, JPEG or PNG

Two thirds of this site's media library is PNG, and nobody decided that. What WordPress actually does with an uploaded file, what its default encoder quality really is, and the three filters that change the format and the size of every sub-size it writes.

Troubleshooting

HTTP Error When Uploading Images to WordPress: The Real Causes

The red bar reading HTTP error is not a verdict on your image. It is the uploader reporting that the POST to async-upload.php came back unusable, and six unrelated failures produce exactly that. Here is how to tell them apart in about ten seconds.

Download the free WunderPaint Plugin for WordPress

The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

The Image Editor & Design Studio

Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

Free

Chaos Art

Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

Pro

Particle Strokes

Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

Pro

City Diorama

Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.

Free

Papercut Art

Layered paper pictures with real depth - a photo sliced along its actual depth into up to twenty layers, parametric landscapes, animals and clouds you can shape, letters with real counters, and a look that runs on three dials.

Pro

3D Earth Studio

A hyperrealistic globe - day and night with city lights, live clouds, atmosphere halo, country borders and highlights, click-to-place markers with flight-route arcs, satellite orbits, seamless rotation video and a live website embed.

Free

Mystic Studio

Turn a birth date into wall art - a real natal chart with houses and aspects, the moon of that night, zodiac and Chinese zodiac posters, numerology cards and a synastry wheel for two, in eight artful themes.

Free

Marble Bath

Marble paper on a virtual water bath - drop, rake and comb real Ebru patterns with gestures, flowers and classic recipes, razor-sharp at any size and re-editable as a layer.

Free

Day Ring

Turn a day into a beautiful circular schedule - colour-coded time blocks as arcs around a 24-hour clock, with concentric rings for overlaps, emoji, templates and a legend.

Free

Code Shot

Turn code into a gorgeous, share-ready image - syntax highlighting, editor themes, window frames and diff highlighting - then drop it into your design as a re-editable layer.

Pro

3D Solar System Studio

Build a date-accurate 3D solar system - real planet positions for any date, photoreal textures and one slider from artistic to true scale - then drop it into your design as an editable layer.

Pro

3D Molecule Studio

Build a real 3D molecule - from a curated library, the periodic table or a SMILES string - then style it, measure it and drop it into your design as an editable layer.

Pro

3D Textile Studio

Drop your design onto cloth that behaves like the material you pick: silk falls soft, felt holds its shape, flag fabric snaps in the wind. Hang it, blow it and drape it, then lay the finished drape back into your document as a picture.

Pro

3D Particle Studio

Point the engine at any layer and it becomes a cloud of particles that keeps its colours, flowing through a sphere, a galaxy or your own outline. Keep the frame you like as a still, or embed the running engine so it keeps moving on your page.

Free

Origami

Put your own picture on the paper and watch that very sheet fold itself into a crane or a box. Every step is a station you can stop at and turn around in 3D, which is exactly where printed diagrams leave you alone.

Pro

3D Flip Studio

A hardcover you can leaf through, a limp magazine, a strewn pile of sheets, a sticker peeling off its backing. The curl is real geometry, so the print never slides across the paper.

Free

Handwriting Fonts

Draw the alphabet here or fill in a printed sheet and photograph it. What comes out is a genuine font family, installed into your site and available in every picker.

Pro

Step Guides

Turn any picture into an instruction. Every mark is pinned to a place in the image, so arrows still point at the right thing after the callout has been dragged somewhere else.

Free

Text Art

One studio, sixteen art types: ASCII and emoji art, brick, dice, cube, sticky-note, LED, ceramic and keycap mosaics, word portraits, text flows, silhouettes, element tiles and more.