Serve images in WebP format in WordPress without a plugin

WebP is a modern image format developed by Google that provides superior compression compared to JPEG and PNG while maintaining comparable visual quality — typically 25–35% smaller file sizes for the same perceived quality. Google PageSpeed Insights has flagged “Serve images in next-gen formats” as a performance opportunity for years, and since WebP is now supported by all major browsers, there is no good reason to keep serving only JPEG and PNG. WordPress 5.8 added native WebP support, meaning you can upload .webp files directly and they will be registered as valid attachment types. However, automatically converting existing JPEG and PNG uploads to WebP on upload requires a small functions.php snippet that hooks into the image upload pipeline — or you can convert your existing library in bulk via WP-CLI. The approach below uses the PHP GD library (available on virtually every host) to generate a WebP copy of every uploaded image alongside the original. Pair this with lazy loading for maximum page speed gains. You can also serve WebP conditionally via .htaccess rewrite rules so browsers that do not support WebP receive the original JPEG — though in 2021+ that is an increasingly small audience. For sites with hundreds of existing images, WP-CLI’s wp media regenerate command combined with a conversion filter handles the bulk migration cleanly. These techniques directly improve your Core Web Vitals Largest Contentful Paint (LCP) score.

Problem: WordPress serves JPEG and PNG images, but you want to automatically generate and serve WebP versions to reduce file size and improve page performance.

Solution: Add the following code to your functions.php file:

/**
 * Generate a WebP copy of every JPEG/PNG uploaded to the media library.
 * Requires PHP GD with WebP support (gd_info()['WebP Support'] === true).
 */
add_filter( 'wp_handle_upload', 'helloadmin_generate_webp_on_upload' );
function helloadmin_generate_webp_on_upload( $upload ) {
    $file = $upload['file'];
    $type = $upload['type'];

    if ( ! in_array( $type, [ 'image/jpeg', 'image/png' ], true ) ) {
        return $upload;
    }

    $webp_path = $file . '.webp';

    if ( $type === 'image/jpeg' ) {
        $image = imagecreatefromjpeg( $file );
    } else {
        $image = imagecreatefrompng( $file );
        // Preserve transparency
        imagepalettetotruecolor( $image );
        imagealphablending( $image, true );
        imagesavealpha( $image, true );
    }

    if ( $image ) {
        imagewebp( $image, $webp_path, 82 ); // 82 = quality (0-100)
        imagedestroy( $image );
    }

    return $upload;
}

/**
 * Optional: serve WebP from <picture> tag in content images.
 */
add_filter( 'the_content', 'helloadmin_replace_img_with_picture' );
function helloadmin_replace_img_with_picture( $content ) {
    return preg_replace_callback(
        '/<img([^>]+)src=["']([^"']+\.(jpe?g|png))["']([^>]*)>/i',
        function ( $m ) {
            $webp = $m[2] . '.webp';
            return '<picture>'
                 . '<source srcset="' . esc_url( $webp ) . '" type="image/webp">'
                 . '<img' . $m[1] . 'src="' . esc_url( $m[2] ) . '"' . $m[4] . '>'
                 . '</picture>';
        },
        $content
    );
}

NOTE: Before enabling the the_content filter, verify that WebP files actually exist alongside the originals; otherwise the <source> element will point to a missing file and browsers will fall back to the JPEG/PNG silently. Check that your PHP installation has WebP support enabled by running var_dump( gd_info() ) and looking for "WebP Support" => bool(true). If it is false, ask your hosting provider to recompile PHP with --with-webp.