WordPress 5.8+ generates WebP versions of uploaded images automatically (when the GD or Imagick extension supports it), and WordPress 6.5 added experimental AVIF support. A complete image optimisation pipeline combines format generation, quality tuning, responsive srcset attribute output, and a <picture> element fallback for maximum browser compatibility and minimum file size.
Problem: WordPress's default image handling uploads JPEG and PNG files as-is — large original images are served to all visitors, there is no automatic WebP or AVIF generation, and the theme lacks responsive image sizes appropriate for each viewport.
Solution: Add WebP conversion to the upload pipeline using wp_generate_attachment_metadata and the imagick PHP extension. Register custom image sizes with add_image_size() for each breakpoint. Use the wp_calculate_image_srcset filter to add WebP sources to srcset, and add AVIF as a progressive enhancement via a <picture> source element.
The code below enables WebP/AVIF output at the correct quality settings, adds a custom image size for above-the-fold hero images, outputs a <picture> element with AVIF and WebP sources, and defers off-screen images with native lazy loading.
<?php
// 1. Set WebP and AVIF output quality (add to functions.php)
add_filter( 'wp_editor_set_quality', fn() => 82 ); // JPEG quality
add_filter( 'wp_image_editors', function ( array $editors ): array {
// Prefer Imagick over GD for better AVIF/WebP support
return array_unique( array_merge( [ 'WP_Image_Editor_Imagick' ], $editors ) );
} );
// Enable AVIF output (WordPress 6.5 experimental)
add_filter( 'wp_uploading_image_mime_type', function ( string $mime ): string {
return $mime; // hook exists; AVIF generation happens via Imagick automatically
} );
// 2. Register a hero image size (large dimensions, high quality)
add_action( 'after_setup_theme', function () {
add_image_size( 'hero-2x', 2560, 1440, false );
add_image_size( 'hero-1x', 1280, 720, false );
add_image_size( 'card', 640, 400, true );
} );
// 3. Output a <picture> element with AVIF + WebP + JPEG fallback
function my_responsive_picture( int $attachment_id, string $size = 'large', array $attr = [] ): string {
$jpeg_src = wp_get_attachment_image_url( $attachment_id, $size );
$jpeg_srcset = wp_get_attachment_image_srcset( $attachment_id, $size );
$sizes_attr = wp_get_attachment_image_sizes( $attachment_id, $size );
$alt = esc_attr( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) );
// Look for WebP and AVIF variants generated by WordPress
$metadata = wp_get_attachment_metadata( $attachment_id );
$sources = [];
foreach ( ( $metadata['sources'] ?? [] ) as $mime => $source_data ) {
if ( in_array( $mime, [ 'image/avif', 'image/webp' ], true ) ) {
$dir = dirname( get_attached_file( $attachment_id ) );
$url_dir = dirname( $jpeg_src );
$srcset = implode( ', ', array_map(
fn( $w, $d ) => $url_dir . '/' . $d['file'] . " {$w}w",
array_keys( $source_data ),
$source_data
) );
$sources[ $mime ] = $srcset;
}
}
$picture = '<picture>';
foreach ( $sources as $mime => $srcset ) {
$picture .= sprintf(
'<source type="%s" srcset="%s" sizes="%s">',
esc_attr( $mime ), esc_attr( $srcset ), esc_attr( $sizes_attr )
);
}
$picture .= sprintf(
'<img src="%s" srcset="%s" sizes="%s" alt="%s" loading="lazy" decoding="async">',
esc_url( $jpeg_src ),
esc_attr( $jpeg_srcset ),
esc_attr( $sizes_attr ),
$alt
);
$picture .= '</picture>';
return $picture;
}
NOTE: WordPress generates WebP/AVIF sub-sizes only for newly uploaded images — existing images need to be regenerated with wp media regenerate --yes (WP-CLI) after enabling the image editors; this can be a long-running operation on sites with thousands of media items, so run it during off-peak hours.