WordPress powers a large share of the web, and unoptimised images are the most common reason a WordPress site fails its Core Web Vitals. The frustrating part is that WordPress looks like it is helping. It generates half a dozen sizes of every upload and wires them into srcset automatically. What it does not do is make any of those files smaller.
This guide covers what WordPress actually does on upload, what it leaves to you, and how to fix a media library that has been filling up with 4 MB phone photos for three years.
Why WordPress images slow down your site
When you upload a photo straight from a phone or a camera, it is often 3 to 8 MB with no compression applied. WordPress generates several thumbnail sizes from it, but it does not compress or convert the originals.
A page with five to ten unoptimised images can easily total 15 to 20 MB in image data alone. A fully optimised equivalent can come in under 1 MB. That difference is the gap between a site that loads in one second and one that takes eight.
What WordPress does and does not do on upload
Worth being precise about, because a lot of advice assumes WordPress is doing more than it is.
| WordPress does this | WordPress does not do this |
|---|---|
| Generates thumbnail, medium, medium_large, large and any theme sizes | Compress the original you uploaded |
Adds srcset and sizes to editor-inserted images | Convert anything to WebP or AVIF |
Adds loading="lazy" since version 5.5 | Exclude your hero image from lazy loading |
| Scales uploads over 2560 px down to a "scaled" version | Delete the oversized original, which stays on disk |
| Accepts WebP uploads since 5.8 and AVIF since 6.5 | Produce either from a JPEG you uploaded |
WordPress default image sizes
When you upload an image, WordPress automatically generates multiple sizes:
- Thumbnail: 150x150 px (cropped)
- Medium: up to 300x300 px
- Medium Large: up to 768 px wide
- Large: up to 1024 px wide
- Full: original upload size
Your theme may register more. A theme with six custom sizes turns every upload into eight files on disk. None of them are compressed if the original was not, and every one of them is generated again if you ever change the dimensions.
Step 1: Resize before you upload
This is the step that does the most and costs nothing. Never upload images larger than your theme will display. If your content area is 1140 px wide, there is no reason to upload a 4000-wide original. Resize to a maximum of around 1440 px wide before uploading and you eliminate most of the problem without any plugin at all.
You can do this in your browser with our image resizer, which processes the file on your own device.
If you want WordPress to be stricter about it, lower the threshold at which it scales uploads:
add_filter( 'big_image_size_threshold', function() {
return 1600;
} );
Step 2: Choose the right format
WordPress supports WebP uploads natively from version 5.8 and AVIF from 6.5. Converting your JPEGs to WebP before uploading typically saves 25 to 35 percent. Our image converter does it locally, or a plugin can convert automatically on upload.
- JPEG: for photographs, quality 80 to 85
- PNG: for graphics with transparency only
- WebP: best general choice for new uploads, smaller than JPEG and supported everywhere that matters
- AVIF: smallest files for photographs, though encoding is slower and some older tooling still chokes on it
- SVG: for logos and icons. WordPress blocks SVG uploads by default for good security reasons, so only enable it through a plugin that sanitises the file
How WebP actually reaches the browser
This is the part that is usually skipped, and it is why people install a conversion plugin, see no improvement, and conclude the plugin does not work. Converting an image to WebP does nothing on its own. Something has to serve the WebP file instead of the JPEG. There are three ways, and they behave differently.
| Method | How it works | Watch out for |
|---|---|---|
<picture> rewriting | The plugin rewrites your HTML to offer WebP with a JPEG fallback | Can conflict with page builders and themes that generate their own markup |
| Server rewrite rules | .htaccess or Nginx rules serve the .webp file when the browser advertises support | Needs correct Vary: Accept headers or a cache or CDN will serve WebP to a browser that cannot read it |
| CDN negotiation | The CDN converts and serves the best format per request | You are dependent on the CDN, and origin files stay unoptimised |
Vary: Accept header after enabling server-side WebP rewriting. Getting this wrong is the classic cause of "some visitors see broken images" reports that appear days after the change.Step 3: Optimisation plugins, and what each is bad at
These plugins compress on upload and can bulk-process your existing library. All of them work. They differ in where the compression happens and what happens if you stop paying.
| Plugin | Processing | Strength | Weakness |
|---|---|---|---|
| Smush | Cloud | Generous free tier, simple, very widely used | Free tier compression is conservative; the more useful features sit behind Pro |
| ShortPixel | Cloud | Strong compression with lossy, glossy and lossless modes; WebP and AVIF; keeps backups of originals | Credit-based, so a big one-off bulk run costs more than a steady monthly trickle |
| Imagify | Cloud | Clean interface, predictable monthly quota, from the WP Rocket team | Quota is measured in megabytes, which is harder to reason about than a per-image count |
| EWWW Image Optimizer | Local or cloud | Can compress on your own server with no external API, which matters if you cannot send files to a third party | Local mode needs binaries your host may not allow, and is slower |
Two things to check before you commit to any of them. First, whether it keeps a backup of your originals, because lossy compression is not reversible and you will eventually want to redo it at a different setting. Second, what happens to your WebP files if you deactivate the plugin: with <picture> rewriting, the rewriting stops and you silently go back to serving JPEG.
Fixing the library you already have
New uploads are the easy half. The back catalogue is where the weight actually is.
- Back up first. Lossy compression cannot be undone. Take a real backup of
wp-content/uploadsbefore any bulk run. - Run the bulk optimiser in whichever plugin you chose. Expect it to take hours on a large library, and run it when traffic is low, because it is CPU and API heavy.
- Regenerate thumbnails if you changed any sizes. Changing a registered size does not touch existing images. A plugin such as Regenerate Thumbnails, or WP-CLI, rebuilds them.
- Delete sizes you do not use. Old themes leave registered sizes behind, and every one of them is a file per upload.
wp media regenerate --yes
Step 4: Lazy loading, with one exception
WordPress added native lazy loading in version 5.5, and images added through the block editor get loading="lazy" automatically. For images in widgets, custom templates or older theme code, add it manually:
<img src="photo.jpg" alt="..." loading="lazy" width="800" height="600">
The exception matters more than the rule. Your hero image must not be lazy-loaded. It is almost always the Largest Contentful Paint element, and deferring it delays the exact thing Google measures. Modern WordPress tries to skip the first image on a page, but themes and page builders defeat this constantly, so verify rather than assume.
<img src="hero.jpg" alt="..." fetchpriority="high" width="1600" height="900">
Step 5: Set width and height on everything
Always specify width and height. The browser uses the ratio to reserve space before the file arrives, which prevents Cumulative Layout Shift, the jarring reflow when images load and push content down the page. WordPress sets these for editor-inserted images. Custom theme code is where they go missing.
Step 6: Put a CDN in front of it
A CDN serves your images from a location near the visitor instead of from your origin server. For an audience spread across countries this is often a larger real-world win than another compression pass, because latency dominates on a first visit.
Image-aware CDNs go further and convert formats per request, so the same original is delivered as AVIF to a current browser and JPEG to an old one without you generating either. Bunny.net and Cloudflare both do this. If you are already on a managed WordPress host, check what is included before adding another service, since many bundle a CDN already.
Check that any of this worked
Do not trust the plugin's own savings number. It reports what it did to files, not what visitors receive.
- Run the page through PageSpeed Insights before and after, and compare LCP specifically.
- Open DevTools, Network tab, filter to Img, and hard-reload. Check the actual Type column: it should say
webporavif, notjpeg. This is where you find out whether delivery is really happening. - Test in a browser you did not configure, or a private window, so a cached page does not flatter the result.
- Check a page as a logged-out visitor. Caching plugins often bypass the cache for administrators, so what you see while logged in is not what the public gets.
A realistic order of work
- Resize and convert before uploading. Free, and the biggest single win.
- Fix the hero image: no lazy loading, add
fetchpriority="high". - Add
widthandheighteverywhere they are missing. - Only then decide whether the volume justifies a plugin.
- Bulk-optimise the back catalogue, after a backup.
- Add a CDN if your audience is geographically spread.
- Measure with real page loads, not plugin dashboards.
Steps one to three cost nothing and are usually worth more than the rest combined. Our image SEO guide covers the underlying decisions in more detail, whatever platform you are on.