Images account for over 50% of total page weight on the average WordPress site. They are the single biggest contributor to slow load times, poor Core Web Vitals scores, and lost search rankings. The good news: almost all of it is fixable without touching your theme code or paying for expensive CDN plans.
This checklist covers every layer of WordPress image optimization in the order you should address them.
Phase 1: Before You Upload
The most effective optimization happens before an image ever touches your media library. WordPress generates multiple derivative sizes from every upload — if the source is bloated, every thumbnail inherits that bloat.
✅ Choose the right format
Pick your format based on the image content, not habit:
- Photos and hero images → WebP or AVIF. Avoid JPEG as a source — use it only as a fallback. WebP is 25–35% smaller than JPEG at equivalent quality; AVIF is 30–50% smaller.
- Images with transparency → WebP (not PNG). PNG files are enormous as source images and WordPress will re-generate smaller versions from that large original.
- Logos, icons, simple graphics → SVG where possible. SVG is infinitely scalable and typically a few kilobytes.
- Animations → MP4 video via
<video autoplay muted loop playsinline>. Even a 3-second GIF can be 5–15MB; the same content as MP4 is under 500KB.
Use ZeroPNG's Image Converter to convert any image to WebP or AVIF before uploading — free, private, no upload required.
✅ Resize to the actual display dimension first
WordPress's largest built-in size is 1024px wide. If your theme's content area is 900px wide, uploading a 5000px photo wastes storage and forces WordPress to create thumbnails from a massive original. A good rule:
- Full-width hero images → 1920px wide maximum
- Blog post featured images → 1200px wide maximum
- In-content images → match the theme's content column width (usually 700–960px)
- Thumbnails / archive cards → match the registered thumbnail size exactly
Resize first with ZeroPNG's Image Cropper or the max-width setting in the main compressor before converting.
✅ Compress before uploading
WordPress applies a default quality of 82 for JPEG re-encoding. That is a reasonable baseline, but it starts from whatever file you upload. Compress the source to 80–85% quality before uploading:
- Photos: target under 200KB for full-width, under 80KB for thumbnails
- Featured images: target under 100KB
- In-content images: target under 60KB
Drag your images into ZeroPNG, set quality to 80, select WebP output, and download. The resulting file is what WordPress will use as the source for all derivative sizes.
✅ Strip EXIF metadata
JPEG files from phones and cameras carry hidden metadata: GPS coordinates, camera model, lens data, shooting settings, and sometimes thumbnails of the original image embedded in the file. This adds 30–200KB of dead weight and leaks location data. WordPress does not strip EXIF by default.
Run images through ZeroPNG's EXIF Remover before uploading, especially photos from mobile devices. The compression step above will strip most EXIF automatically if you use the WebP/AVIF output format.
Phase 2: WordPress Core Settings
✅ Understand the Big Image Scale Threshold
Since WordPress 5.3, any image wider than 2560px is automatically scaled down to 2560px on upload. This is called the Big Image Scale Threshold. It is on by default and is a good safety net, but it does not replace deliberate pre-upload resizing — WordPress still stores the 2560px version as the "full" size.
To disable it entirely (if you manage dimensions yourself):
// Add to functions.php
add_filter( 'big_image_size_threshold', '__return_false' );
✅ Audit and remove unneeded image sizes
Every uploaded image generates multiple copies. Run this snippet in a plugin or
functions.php to see all registered sizes on your site:
add_action( 'init', function() {
if ( current_user_can( 'manage_options' ) && isset( $_GET['show_image_sizes'] ) ) {
$sizes = wp_get_registered_image_subsizes();
echo '<pre>' . print_r( $sizes, true ) . '</pre>';
exit;
}
});
Visit yoursite.com/?show_image_sizes=1 to see the list. Then deregister any size your theme or
templates never use:
// Add to functions.php — deregister sizes you don't use
add_filter( 'intermediate_image_sizes', function( $sizes ) {
return array_diff( $sizes, [ 'medium_large', '1536x1536', '2048x2048' ] );
});
Fewer registered sizes means less storage used and faster uploads.
✅ Set the correct JPEG quality filter
WordPress's default quality of 82 is a reasonable middle ground. If your source images are already compressed to WebP, this setting mostly applies to fallback JPEG generation. You can tighten it:
// Lower to 75 for smaller files with no visible quality loss
add_filter( 'jpeg_quality', fn() => 75 );
add_filter( 'wp_editor_set_quality', fn() => 75 );
Don't go below 70 — artifacts become visible in gradient-heavy photos.
✅ Enable the WebP uploads setting
Since WordPress 6.1, WebP uploads are fully supported. There is no setting to toggle — simply upload WebP files and WordPress will handle them natively, generating thumbnails in WebP. Verify your server's PHP-GD or Imagick extension has WebP support:
// Check WebP support — paste in a temporary PHP file
echo gd_info()['WebP Support'] ? 'WebP: YES' : 'WebP: NO';
If WebP support is missing, contact your host or enable the imagick PHP extension.
Phase 3: Plugin Setup
✅ Pick one compression plugin and configure it correctly
Do not install multiple compression plugins — they will re-compress each other's output and degrade quality. Pick one:
- Imagify — converts to WebP/AVIF automatically, has a generous free tier (25MB/month), simple UI. Best for most users.
- ShortPixel — lossless and lossy modes, AVIF support, good bulk optimization UI. Credits- based (100 images/month free).
- Smush — free tier is lossless only (no WebP conversion without Pro). Use only if you pre- compress before uploading.
- EWWW Image Optimizer — free, self-hosted compression using server-side tools. Slower but no API credits.
Key settings to configure in any of these plugins:
- Enable automatic optimization on upload
- Enable WebP conversion and delivery (usually via a
<picture>tag or.htaccessrewrite rule) - Enable bulk optimization to process your existing media library
- Set compression to lossy mode — lossless on photos saves almost nothing
- Enable optimization for all registered image sizes, not just the original
✅ Configure lazy loading
Since WordPress 5.5, the loading="lazy" attribute is automatically added to all
<img> tags except the first image in the content. This is correct behavior — do not
override it globally.
The one critical exception: never lazy-load your LCP image (usually the hero or featured
image). Check your theme template and ensure the hero <img> tag has
loading="eager" or no loading attribute at all:
<!-- Hero / LCP image — do NOT lazy load -->
<img src="hero.webp"
alt="Hero banner"
width="1920" height="1080"
fetchpriority="high"
loading="eager">
<!-- Everything else — lazy load is fine -->
<img src="content-photo.webp"
alt="Article photo"
width="800" height="533"
loading="lazy">
✅ Add a CDN for image delivery
Even on fast hosting, a CDN cuts image delivery time by 30–60% for visitors outside your server's region. Options ordered by cost:
- Cloudflare (free) — proxy your domain through Cloudflare. Free tier caches images globally. Enable "Polish" in the Speed settings to get automatic WebP conversion and compression at the edge.
- BunnyCDN ($1/month) — cheap, fast, simple. Pull zone from your WordPress host.
- Cloudinary (free tier) — image CDN with on-the-fly resizing, format conversion, and smart cropping via URL parameters. Has a WordPress plugin.
- Jetpack Site Accelerator (free) — serves images and static files from WordPress.com's CDN. Zero configuration if you already use Jetpack.
Phase 4: Theme and Template Fixes
✅ Use wp_get_attachment_image() instead of hardcoded img tags
The correct way to output images in WordPress templates is via wp_get_attachment_image() or the
block editor's image block. These functions automatically generate srcset and
sizes attributes, add loading="lazy", and include width /
height:
<?php
// Output a responsive image with correct srcset
echo wp_get_attachment_image(
get_post_thumbnail_id(),
'large', // registered size
false, // not an icon
[
'fetchpriority' => 'high', // add for LCP images
'loading' => 'eager', // override lazy loading for hero
'class' => 'hero-img',
]
);
?>
Avoid <img src="<?php echo get_the_post_thumbnail_url(); ?>"> — it outputs a single
URL with no srcset, no lazy loading, and no dimensions.
✅ Register a dedicated image size for every use case
If your theme displays featured images at 800×450 in blog listings and 1200×675 on single posts, register both sizes. Using the "large" default size (1024px) for an 800px slot means serving 28% more pixels than needed:
// In functions.php
add_action( 'after_setup_theme', function() {
add_image_size( 'blog-card', 800, 450, true ); // hard crop
add_image_size( 'hero-full', 1920, 1080, true );
add_image_size( 'blog-hero', 1200, 675, true );
});
After adding new sizes, run the "Regenerate Thumbnails" plugin to create the new sizes for existing uploads.
✅ Always include width and height attributes
Missing dimensions cause Cumulative Layout Shift (CLS) — the page jumps as images load because the browser didn't know how much space to reserve. WordPress's built-in functions add these automatically, but custom templates often forget them:
<!-- Wrong: missing dimensions cause layout shift -->
<img src="photo.webp" alt="Photo">
<!-- Correct: browser reserves exact space -->
<img src="photo.webp" alt="Photo" width="800" height="533">
<!-- With CSS for responsive scaling -->
<style>
img { max-width: 100%; height: auto; }
</style>
✅ Preload your hero image
The LCP element on most WordPress pages is the featured image on single posts or the hero on the homepage. Tell the browser to fetch it immediately:
// Add to functions.php — preloads the featured image on single posts
add_action( 'wp_head', function() {
if ( is_singular() && has_post_thumbnail() ) {
$thumb_id = get_post_thumbnail_id();
$thumb_src = wp_get_attachment_image_src( $thumb_id, 'large' );
if ( $thumb_src ) {
echo '<link rel="preload" as="image" href="' . esc_url( $thumb_src[0] ) . '" fetchpriority="high">' . "\n";
}
}
}, 1 );
Phase 5: Core Web Vitals Check
✅ Run PageSpeed Insights on your key pages
After implementing the above, test these pages specifically:
- Homepage
- A single blog post with a featured image
- Your most important landing page
- A WooCommerce product page (if applicable)
Visit pagespeed.web.dev for each. Focus on these image-specific diagnostics:
- Largest Contentful Paint (LCP) — target under 2.5s. If the LCP element is an image, it will be named in the report.
- "Serve images in next-gen formats" — any JPEG/PNG listed here should be converted to WebP or AVIF.
- "Properly size images" — any image where the delivered size exceeds the display size by more than 25KB should be resized.
- "Defer offscreen images" — confirms lazy loading is working.
- "Image elements do not have explicit width and height" — fix these to prevent CLS.
✅ Check your Largest Contentful Paint image specifically
In Chrome DevTools → Performance tab → record a page load → click the LCP marker. Verify:
- It is an
<img>tag, not a CSSbackground-image(browsers discover background images later) - It has
fetchpriority="high" - It does not have
loading="lazy" - The file size is under 150KB
- It is served in WebP or AVIF format
Read the full guide on fixing LCP issues caused by images for a deeper dive.
Phase 6: WooCommerce-Specific Checklist
If your site sells products, product images have extra considerations.
✅ Configure WooCommerce image sizes correctly
WooCommerce registers its own image sizes: woocommerce_thumbnail,
woocommerce_single, and woocommerce_gallery_thumbnail. Go to WooCommerce →
Settings → Products → Images and set sizes that match your theme's actual layout. Oversized
thumbnails in shop grids are one of the most common WooCommerce performance problems.
✅ Use a gallery plugin with lazy loading
Product galleries with 10+ images are a common cause of poor mobile performance. Ensure your gallery plugin lazy-loads all images except the first (which should be eager). Most modern gallery plugins do this by default — verify in the browser's network tab that images load on scroll, not all at once.
✅ Compress product photos before uploading
Product photography from a studio or DSLR often arrives as 20–50MB TIFFs or RAW exports. Never upload these directly to WordPress. Export to JPEG first, then convert to WebP and compress with ZeroPNG before uploading. A product image displayed at 600×600px needs no more than 80KB as a WebP file.
Phase 7: Ongoing Maintenance
✅ Audit your media library quarterly
WordPress media libraries grow large quickly and often contain images never used on any page or post. Install the Media Cleaner plugin to identify orphaned images (uploaded but not referenced anywhere). Review the list carefully before deleting — some images may be referenced in page builders or custom fields that the plugin cannot detect.
✅ Monitor with GTmetrix or WebPageTest
Set up a monthly performance test on your homepage and top landing pages. Both tools flag image-specific issues and track scores over time so you can catch regressions introduced by theme updates or new plugin installations.
✅ Re-run bulk optimization after theme updates
Theme updates sometimes register new image sizes or change existing ones. After any significant theme update, run "Regenerate Thumbnails" to rebuild all derivative sizes, then trigger a bulk optimization pass in your compression plugin to re-compress the new sizes.
The Complete Checklist at a Glance
Before Upload
- Convert to WebP or AVIF
- Resize to the maximum display dimension
- Compress to under target file size
- Strip EXIF metadata
WordPress Core
- Understand and optionally disable the Big Image Scale Threshold
- Audit and remove unneeded image sizes
- Set JPEG quality filter to 75
- Verify WebP support in PHP-GD or Imagick
Plugins
- Install one compression plugin with lossy + WebP conversion enabled
- Verify lazy loading is on (and off for the hero/LCP image)
- Enable a CDN for image delivery
Theme and Templates
- Use
wp_get_attachment_image()in all templates - Register a dedicated size for every use case
- Always include width and height on img tags
- Preload the hero/LCP image in wp_head
Core Web Vitals
- Run PageSpeed Insights on homepage, single post, landing pages
- Verify LCP element is an img tag with fetchpriority="high"
- Fix all "Serve next-gen formats" and "Properly size images" warnings
Ongoing
- Quarterly media library audit to remove orphaned images
- Monthly GTmetrix or WebPageTest run
- Bulk re-optimize after theme updates
Compress Images Before Uploading to WordPress
Process your images in bulk before they ever touch your media library. No plugin, no account, no upload — everything runs in your browser.
Compress Images Free