Platform Guide 26 min read

WordPress Image Performance Complete Guide

Master image optimization in WordPress. Learn core features, plugins, WebP/AVIF support, responsive images, WooCommerce optimization, and performance best practices.

By ImageGuide Team · Published January 19, 2026 · Updated January 19, 2026
wordpresswoocommercegutenbergimage optimizationplugins

WordPress powers over 40% of the web, and images are often the largest performance bottleneck. This guide covers everything from WordPress core features to advanced optimization techniques.

WordPress Core Image Features

Built-in Image Handling

WordPress automatically:

  • Generates multiple image sizes on upload
  • Creates responsive srcset for content images
  • Adds lazy loading (since WordPress 5.5)
  • Supports WebP uploads (since WordPress 5.8)

Default Image Sizes

SizeDimensionsUse Case
Thumbnail150×150Admin, widgets
Medium300×300Content flow
Medium Large768×autoResponsive
Large1024×1024Full-width content
FullOriginalDirect use

Customizing Image Sizes

// functions.php

// Modify default sizes
update_option('thumbnail_size_w', 150);
update_option('thumbnail_size_h', 150);
update_option('thumbnail_crop', 1);

update_option('medium_size_w', 400);
update_option('medium_size_h', 400);

update_option('large_size_w', 1200);
update_option('large_size_h', 1200);

// Add custom sizes
add_image_size('hero', 1920, 800, true);
add_image_size('card', 600, 400, true);
add_image_size('gallery-thumb', 300, 300, true);

// Make sizes available in editor
add_filter('image_size_names_choose', function($sizes) {
    return array_merge($sizes, [
        'hero' => 'Hero Banner',
        'card' => 'Card Image',
        'gallery-thumb' => 'Gallery Thumbnail'
    ]);
});

WebP and AVIF Support

WordPress WebP Support

Since WordPress 5.8, WebP is fully supported:

// Check WebP support
function check_webp_support() {
    $mime_types = get_allowed_mime_types();
    return isset($mime_types['webp']);
}

// WordPress 5.8+ automatically supports WebP
// No additional configuration needed for basic support

Enabling AVIF Support

// functions.php
add_filter('upload_mimes', function($mimes) {
    $mimes['avif'] = 'image/avif';
    return $mimes;
});

add_filter('wp_check_filetype_and_ext', function($data, $file, $filename, $mimes) {
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    if ($ext === 'avif') {
        $data['ext'] = 'avif';
        $data['type'] = 'image/avif';
    }
    return $data;
}, 10, 4);

WebP Conversion on Upload

// Auto-convert uploads to WebP (requires GD or Imagick)
add_filter('wp_generate_attachment_metadata', function($metadata, $attachment_id) {
    $file = get_attached_file($attachment_id);
    $info = pathinfo($file);

    // Only process JPEG/PNG
    if (!in_array(strtolower($info['extension']), ['jpg', 'jpeg', 'png'])) {
        return $metadata;
    }

    // Generate WebP version
    $webp_path = $info['dirname'] . '/' . $info['filename'] . '.webp';

    if (extension_loaded('imagick')) {
        $image = new Imagick($file);
        $image->setImageFormat('webp');
        $image->setImageCompressionQuality(80);
        $image->writeImage($webp_path);
        $image->destroy();
    } elseif (function_exists('imagewebp')) {
        $source = imagecreatefromstring(file_get_contents($file));
        imagewebp($source, $webp_path, 80);
        imagedestroy($source);
    }

    return $metadata;
}, 10, 2);

Responsive Images

WordPress Default Srcset

WordPress automatically adds srcset to content images:

<!-- WordPress output -->
<img
  src="image-1024x768.jpg"
  srcset="image-300x225.jpg 300w,
          image-768x576.jpg 768w,
          image-1024x768.jpg 1024w,
          image-1536x1152.jpg 1536w"
  sizes="(max-width: 1024px) 100vw, 1024px"
  alt="Description"
>

Customizing Srcset

// Modify srcset sizes
add_filter('wp_calculate_image_srcset', function($sources, $size_array, $image_src, $image_meta, $attachment_id) {
    // Remove unwanted sizes
    foreach ($sources as $width => $source) {
        if ($width > 2000) {
            unset($sources[$width]);
        }
    }
    return $sources;
}, 10, 5);

// Customize sizes attribute
add_filter('wp_calculate_image_sizes', function($sizes, $size, $image_src, $image_meta, $attachment_id) {
    // Custom sizes for full-width images
    if ($size[0] >= 1200) {
        return '(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px';
    }
    return $sizes;
}, 10, 5);

Theme Implementation

// In theme template
$image_id = get_post_thumbnail_id();

echo wp_get_attachment_image($image_id, 'large', false, [
    'class' => 'hero-image',
    'loading' => 'eager',
    'fetchpriority' => 'high',
    'sizes' => '100vw'
]);

Lazy Loading

Native Lazy Loading

Since WordPress 5.5, lazy loading is automatic:

// Disable lazy loading for specific images
add_filter('wp_img_tag_add_loading_attr', function($value, $image, $context) {
    // Disable for featured images in hero sections
    if (strpos($image, 'hero-image') !== false) {
        return false;
    }
    return $value;
}, 10, 3);

// Disable lazy loading for first N images
add_filter('wp_lazy_loading_enabled', function($default, $tag_name, $context) {
    static $count = 0;
    if ($tag_name === 'img') {
        $count++;
        if ($count <= 2) {
            return false; // Don't lazy load first 2 images
        }
    }
    return $default;
}, 10, 3);

Skip Lazy Loading for LCP

// Add fetchpriority to first image
add_filter('wp_content_img_tag', function($filtered_image, $context, $attachment_id) {
    static $first_image = true;

    if ($first_image && $context === 'the_content') {
        $first_image = false;
        $filtered_image = str_replace(
            'loading="lazy"',
            'fetchpriority="high"',
            $filtered_image
        );
    }

    return $filtered_image;
}, 10, 3);
// In template
if (has_post_thumbnail()) {
    the_post_thumbnail('large', [
        'class' => 'featured-image',
        'loading' => 'eager'
    ]);
}
function responsive_featured_image($post_id = null, $loading = 'lazy') {
    $post_id = $post_id ?: get_the_ID();
    $image_id = get_post_thumbnail_id($post_id);

    if (!$image_id) return '';

    $sizes = [
        ['width' => 400, 'media' => '(max-width: 480px)'],
        ['width' => 800, 'media' => '(max-width: 768px)'],
        ['width' => 1200, 'media' => '(max-width: 1200px)'],
        ['width' => 1600, 'media' => '(min-width: 1201px)'],
    ];

    $sources = '';
    foreach ($sizes as $size) {
        $image = wp_get_attachment_image_src($image_id, [$size['width'], 0]);
        if ($image) {
            $sources .= sprintf(
                '<source media="%s" srcset="%s">',
                esc_attr($size['media']),
                esc_url($image[0])
            );
        }
    }

    $default = wp_get_attachment_image_src($image_id, 'large');
    $alt = get_post_meta($image_id, '_wp_attachment_image_alt', true);

    return sprintf(
        '<picture>%s<img src="%s" alt="%s" loading="%s" class="featured-image"></picture>',
        $sources,
        esc_url($default[0]),
        esc_attr($alt),
        esc_attr($loading)
    );
}

Gutenberg Block Images

Image Block Enhancements

// Add custom attributes to image block
add_filter('render_block_core/image', function($block_content, $block) {
    // Add loading optimization
    if (strpos($block_content, 'loading=') === false) {
        $block_content = str_replace(
            '<img',
            '<img loading="lazy" decoding="async"',
            $block_content
        );
    }

    return $block_content;
}, 10, 2);

Custom Image Block

// blocks/optimized-image/edit.js
import { useBlockProps, MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
import { Button, SelectControl } from '@wordpress/components';

export default function Edit({ attributes, setAttributes }) {
    const { mediaId, mediaUrl, size, loading } = attributes;
    const blockProps = useBlockProps();

    return (
        <div {...blockProps}>
            <MediaUploadCheck>
                <MediaUpload
                    onSelect={(media) => setAttributes({
                        mediaId: media.id,
                        mediaUrl: media.sizes[size]?.url || media.url
                    })}
                    allowedTypes={['image']}
                    value={mediaId}
                    render={({ open }) => (
                        <Button onClick={open}>
                            {mediaUrl ? (
                                <img src={mediaUrl} alt="" />
                            ) : (
                                'Select Image'
                            )}
                        </Button>
                    )}
                />
            </MediaUploadCheck>

            <SelectControl
                label="Loading"
                value={loading}
                options={[
                    { label: 'Lazy', value: 'lazy' },
                    { label: 'Eager', value: 'eager' }
                ]}
                onChange={(value) => setAttributes({ loading: value })}
            />
        </div>
    );
}

WooCommerce Images

Product Image Sizes

// Customize WooCommerce image sizes
add_filter('woocommerce_get_image_size_single', function($size) {
    return [
        'width' => 800,
        'height' => 800,
        'crop' => 0
    ];
});

add_filter('woocommerce_get_image_size_thumbnail', function($size) {
    return [
        'width' => 400,
        'height' => 400,
        'crop' => 1
    ];
});

add_filter('woocommerce_get_image_size_gallery_thumbnail', function($size) {
    return [
        'width' => 150,
        'height' => 150,
        'crop' => 1
    ];
});
// Optimize product gallery loading
add_action('woocommerce_before_single_product_summary', function() {
    // Preload main product image
    global $product;
    $image_id = $product->get_image_id();

    if ($image_id) {
        $image_url = wp_get_attachment_image_url($image_id, 'woocommerce_single');
        echo '<link rel="preload" as="image" href="' . esc_url($image_url) . '">';
    }
}, 1);

// Lazy load gallery thumbnails
add_filter('woocommerce_single_product_image_gallery_classes', function($classes) {
    $classes[] = 'gallery-lazy';
    return $classes;
});

Category Page Optimization

// Optimize category thumbnails
add_action('woocommerce_before_shop_loop_item_title', function() {
    global $product;
    static $count = 0;
    $count++;

    // Eager load first row (assuming 4 per row)
    $loading = $count <= 4 ? 'eager' : 'lazy';

    echo '<div class="product-image-wrapper">';
    echo wp_get_attachment_image(
        $product->get_image_id(),
        'woocommerce_thumbnail',
        false,
        [
            'loading' => $loading,
            'class' => 'product-thumbnail'
        ]
    );
    echo '</div>';
}, 9);

// Remove default thumbnail
remove_action('woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10);

Image Optimization Plugins

The Sirv WordPress plugin provides the most complete image CDN and optimization solution for WordPress and WooCommerce:

Key Features:

  • Automatic optimization of all images (no manual work)
  • Automatic scaling to exact dimensions needed
  • Automatic AVIF & WebP delivery (smaller than any other plugin)
  • Fast global CDN (25+ locations worldwide)
  • Deep image zoom for product photography
  • 360-degree product spins
  • 3D model support (GLB/USDZ with AR)
  • Video streaming with adaptive bitrate
  • Lazy loading built-in

What Gets Optimized:

  • Media library images
  • Featured images
  • WooCommerce product & category images
  • CSS background images
  • Images from other plugins

Installation:

  1. Install the Sirv plugin from WordPress.org
  2. Connect your Sirv account
  3. Enable CDN - images sync automatically
// Sirv handles everything automatically, but you can also use the API
function get_sirv_optimized_url($attachment_id, $width = 800) {
    $url = wp_get_attachment_url($attachment_id);
    // Sirv plugin automatically rewrites URLs to CDN
    return $url;
}

Pricing: Free plan (500MB storage, 2GB/month transfer), paid plans from $19/month for 5GB storage.

Other Plugins

PluginFeaturesBest For
ShortPixelLossy/lossless, WebP, CDNGeneral optimization
ImagifyBulk optimize, WebP, backupAgencies
SmushFree tier, lazy loadBudget-conscious
EWWWLocal processing, WebPPrivacy-focused
OptimoleCDN, real-time, adaptiveHigh traffic

Plugin Configuration (ShortPixel Example)

// Programmatic configuration
add_filter('shortpixel_image_optimiser_settings', function($settings) {
    return array_merge($settings, [
        'api_key' => SHORTPIXEL_API_KEY,
        'compression_type' => 1, // Lossy
        'create_webp' => true,
        'create_avif' => true,
        'resize_images' => true,
        'resize_width' => 2560,
        'resize_height' => 2560
    ]);
});

WebP Delivery Without Plugin

# .htaccess WebP rewrite
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP_ACCEPT} image/webp
  RewriteCond %{REQUEST_FILENAME} (.*)\.(jpe?g|png)$
  RewriteCond %{REQUEST_FILENAME}\.webp -f
  RewriteRule (.+)\.(jpe?g|png)$ %{REQUEST_FILENAME}.webp [T=image/webp,E=accept:1]
</IfModule>

<IfModule mod_headers.c>
  Header append Vary Accept env=REDIRECT_accept
</IfModule>

CDN Integration

Rewriting Image URLs

// Replace uploads URL with CDN
add_filter('wp_get_attachment_url', function($url) {
    $upload_url = wp_upload_dir()['baseurl'];
    $cdn_url = 'https://cdn.example.com/wp-content/uploads';

    return str_replace($upload_url, $cdn_url, $url);
});

// For srcset too
add_filter('wp_calculate_image_srcset', function($sources) {
    $upload_url = wp_upload_dir()['baseurl'];
    $cdn_url = 'https://cdn.example.com/wp-content/uploads';

    foreach ($sources as $width => $source) {
        $sources[$width]['url'] = str_replace($upload_url, $cdn_url, $source['url']);
    }

    return $sources;
});

Sirv CDN Integration

The easiest way to integrate Sirv is with the official WordPress plugin, which handles everything automatically. For custom implementations:

// Manual Sirv image transformation (if not using plugin)
function sirv_image_url($attachment_id, $args = []) {
    $defaults = [
        'width' => 800,
        'quality' => 80,
        'format' => 'optimal'
    ];
    $args = wp_parse_args($args, $defaults);

    $url = wp_get_attachment_url($attachment_id);

    // Build Sirv URL with dynamic imaging parameters
    $sirv_url = sprintf(
        'https://yoursite.sirv.com%s?w=%d&q=%d&format=%s',
        parse_url($url, PHP_URL_PATH),
        $args['width'],
        $args['quality'],
        $args['format']
    );

    return $sirv_url;
}

// With the Sirv plugin installed, URLs are rewritten automatically
// and you get AVIF/WebP, lazy loading, and responsive images for free

Performance Optimization

Preloading Critical Images

// Preload hero images
add_action('wp_head', function() {
    if (is_front_page() && has_post_thumbnail()) {
        $image_id = get_post_thumbnail_id();
        $image = wp_get_attachment_image_src($image_id, 'full');

        if ($image) {
            printf(
                '<link rel="preload" as="image" href="%s" imagesrcset="%s" imagesizes="100vw">',
                esc_url($image[0]),
                esc_attr(wp_get_attachment_image_srcset($image_id, 'full')),
            );
        }
    }
}, 1);

Image Compression on Upload

// Compress JPEG on upload using GD
add_filter('wp_handle_upload', function($upload) {
    $path = $upload['file'];
    $type = $upload['type'];

    if (strpos($type, 'image/jpeg') !== false) {
        $image = imagecreatefromjpeg($path);
        if ($image) {
            imagejpeg($image, $path, 85);
            imagedestroy($image);
        }
    }

    return $upload;
});

Limiting Image Sizes

// Remove unused default sizes
add_filter('intermediate_image_sizes_advanced', function($sizes) {
    // Remove sizes you don't need
    unset($sizes['medium_large']); // 768px
    unset($sizes['1536x1536']);
    unset($sizes['2048x2048']);

    return $sizes;
});

// Limit maximum upload size
add_filter('wp_handle_upload_prefilter', function($file) {
    $max_width = 2560;
    $max_height = 2560;

    $image = getimagesize($file['tmp_name']);
    if ($image) {
        if ($image[0] > $max_width || $image[1] > $max_height) {
            $file['error'] = sprintf(
                'Image dimensions (%dx%d) exceed maximum allowed (%dx%d)',
                $image[0], $image[1], $max_width, $max_height
            );
        }
    }

    return $file;
});

Theme Development

Theme Image Functions

// functions.php

/**
 * Get optimized image HTML
 */
function theme_get_image($attachment_id, $size = 'large', $args = []) {
    $defaults = [
        'loading' => 'lazy',
        'class' => '',
        'sizes' => '100vw'
    ];
    $args = wp_parse_args($args, $defaults);

    return wp_get_attachment_image($attachment_id, $size, false, [
        'loading' => $args['loading'],
        'class' => $args['class'],
        'sizes' => $args['sizes'],
        'decoding' => 'async'
    ]);
}

/**
 * Get responsive picture element
 */
function theme_get_picture($attachment_id, $args = []) {
    $defaults = [
        'mobile_size' => 'medium',
        'desktop_size' => 'large',
        'breakpoint' => 768,
        'loading' => 'lazy'
    ];
    $args = wp_parse_args($args, $defaults);

    $mobile = wp_get_attachment_image_src($attachment_id, $args['mobile_size']);
    $desktop = wp_get_attachment_image_src($attachment_id, $args['desktop_size']);
    $alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);

    ob_start();
    ?>
    <picture>
        <source media="(min-width: <?php echo $args['breakpoint']; ?>px)"
                srcset="<?php echo esc_url($desktop[0]); ?>">
        <img src="<?php echo esc_url($mobile[0]); ?>"
             alt="<?php echo esc_attr($alt); ?>"
             loading="<?php echo esc_attr($args['loading']); ?>"
             decoding="async">
    </picture>
    <?php
    return ob_get_clean();
}

ACF Image Fields

// Display ACF image field optimized
function display_acf_image($field_name, $post_id = null) {
    $image = get_field($field_name, $post_id);

    if (!$image) return '';

    // If returning array
    if (is_array($image)) {
        return wp_get_attachment_image($image['ID'], 'large', false, [
            'loading' => 'lazy',
            'sizes' => '(max-width: 768px) 100vw, 50vw'
        ]);
    }

    // If returning ID
    if (is_numeric($image)) {
        return wp_get_attachment_image($image, 'large', false, [
            'loading' => 'lazy'
        ]);
    }

    // If returning URL (not recommended)
    return sprintf('<img src="%s" loading="lazy">', esc_url($image));
}

Media Library Management

Cleanup Unused Images

// Find unused images
function find_unused_images() {
    global $wpdb;

    $attachments = get_posts([
        'post_type' => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => -1,
        'fields' => 'ids'
    ]);

    $unused = [];

    foreach ($attachments as $id) {
        $url = wp_get_attachment_url($id);
        $filename = basename($url);

        // Check if used in content
        $used = $wpdb->get_var($wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts}
            WHERE post_content LIKE %s
            AND post_status = 'publish'
            LIMIT 1",
            '%' . $wpdb->esc_like($filename) . '%'
        ));

        // Check if used as featured image
        $featured = $wpdb->get_var($wpdb->prepare(
            "SELECT meta_id FROM {$wpdb->postmeta}
            WHERE meta_key = '_thumbnail_id'
            AND meta_value = %d
            LIMIT 1",
            $id
        ));

        if (!$used && !$featured) {
            $unused[] = $id;
        }
    }

    return $unused;
}

Regenerate Thumbnails Programmatically

// Regenerate specific image
function regenerate_image_sizes($attachment_id) {
    $file = get_attached_file($attachment_id);

    if (!file_exists($file)) {
        return new WP_Error('file_not_found', 'Original file not found');
    }

    // Generate new metadata
    $metadata = wp_generate_attachment_metadata($attachment_id, $file);

    // Update metadata
    wp_update_attachment_metadata($attachment_id, $metadata);

    return $metadata;
}

Troubleshooting

Common Issues

Missing srcset:

// Ensure image editor is available
add_filter('wp_image_editors', function($editors) {
    // Prefer Imagick for better quality
    return ['WP_Image_Editor_Imagick', 'WP_Image_Editor_GD'];
});

Wrong image sizes:

// Clear image metadata cache
delete_post_meta($attachment_id, '_wp_attachment_metadata');
wp_update_attachment_metadata(
    $attachment_id,
    wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id))
);

Slow uploads:

// Increase memory for image processing
add_filter('wp_max_upload_size', function() {
    return 50 * 1024 * 1024; // 50MB
});

// Configure PHP settings in php.ini or wp-config.php
// memory_limit = 256M
// max_execution_time = 300

Summary

Configuration Checklist

// Complete optimization setup
add_action('after_setup_theme', function() {
    // Custom image sizes
    add_image_size('hero', 1920, 800, true);
    add_image_size('card', 600, 400, true);
});

// Remove unused sizes
add_filter('intermediate_image_sizes_advanced', function($sizes) {
    unset($sizes['1536x1536']);
    unset($sizes['2048x2048']);
    return $sizes;
});

// Optimize loading
add_filter('wp_lazy_loading_enabled', '__return_true');

// Enable WebP
add_filter('upload_mimes', function($mimes) {
    $mimes['webp'] = 'image/webp';
    return $mimes;
});

Quick Reference

TaskCode
Get responsive imagewp_get_attachment_image($id, 'large')
Featured imagethe_post_thumbnail('large', ['loading' => 'eager'])
Disable lazy load['loading' => 'eager', 'fetchpriority' => 'high']
Custom srcsetwp_get_attachment_image_srcset($id, 'large')

Performance Checklist

  1. ✅ Use optimized image sizes (remove unused)
  2. ✅ Enable WebP/AVIF conversion
  3. ✅ Implement lazy loading (skip above-fold)
  4. ✅ Add fetchpriority to LCP images
  5. ✅ Preload hero/critical images
  6. ✅ Use CDN for image delivery
  7. ✅ Compress images on upload
  8. ✅ Generate proper srcset for responsive
  9. ✅ Set explicit width/height attributes
  10. ✅ Limit maximum upload dimensions
  11. ✅ Clean up unused media regularly
  12. ✅ Test with Page Speed Insights

WordPress provides strong image optimization foundations. Combine core features with targeted plugins and proper theme implementation for optimal image performance.

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial