WordPress WebP Image Support: Upload, Generate, and Serve WebP with picture Element

WordPress 5.8 added native support for uploading WebP images through the Media Library — previously WebP files were blocked by the default MIME type allowlist. WebP is a modern image format developed by Google that typically achieves 25–35% smaller file sizes than JPEG at comparable visual quality, and 25–50% smaller than PNG for images with transparency. With WP 5.8 you can upload .webp files directly, use them in posts, and generate WebP thumbnails automatically if the server’s GD or Imagick extension supports WebP encoding. However, converting existing JPEG and PNG images to WebP at upload time — generating a WebP alternative alongside the original — requires additional code or a plugin, as WordPress does not do this automatically. Understanding how to hook into the image upload pipeline is essential for building a server-side image optimisation workflow.

Problem: A site needs to automatically generate a WebP version of every JPEG/PNG image uploaded to the Media Library, store it alongside the original, and serve the WebP version to browsers that support it via the <picture> element in post content.

Solution: Hook into wp_handle_upload to convert the uploaded file to WebP using PHP's Imagick or GD extension. Store the WebP path in post meta. Filter wp_get_attachment_image to wrap the output in a <picture> element with a WebP source.

<?php
// ── 1. Check WebP support on the server ────────────────────────────────
function my_plugin_server_supports_webp(): bool {
    if ( function_exists( 'imagewebp' ) ) {
        return true; // GD with WebP support
    }
    if ( extension_loaded( 'imagick' ) ) {
        $formats = Imagick::queryFormats( 'WEBP' );
        return ! empty( $formats );
    }
    return false;
}

// ── 2. Generate WebP on upload ─────────────────────────────────────────
add_filter( 'wp_handle_upload', function ( array $upload ): array {
    if ( ! my_plugin_server_supports_webp() ) {
        return $upload;
    }
    $allowed = [ 'image/jpeg', 'image/png' ];
    if ( ! in_array( $upload['type'], $allowed, true ) ) {
        return $upload;
    }

    $source    = $upload['file'];                          // /path/to/image.jpg
    $webp_path = preg_replace( '/\.(jpg|jpeg|png)$/i', '.webp', $source );

    if ( function_exists( 'imagewebp' ) ) {
        $fn  = str_ends_with( strtolower( $source ), '.png' ) ? 'imagecreatefrompng' : 'imagecreatefromjpeg';
        $img = $fn( $source );
        imagewebp( $img, $webp_path, 82 ); // quality 82
        imagedestroy( $img );
    } else {
        $imagick = new Imagick( $source );
        $imagick->setImageFormat( 'webp' );
        $imagick->setImageCompressionQuality( 82 );
        $imagick->writeImage( $webp_path );
        $imagick->clear();
    }

    return $upload;
} );

// ── 3. Store WebP path in attachment meta after upload ─────────────────
add_action( 'add_attachment', function ( int $attachment_id ) {
    $file = get_attached_file( $attachment_id );
    if ( ! $file ) return;

    $webp = preg_replace( '/\.(jpg|jpeg|png)$/i', '.webp', $file );
    if ( file_exists( $webp ) ) {
        $webp_url = str_replace(
            wp_upload_dir()['basedir'],
            wp_upload_dir()['baseurl'],
            $webp
        );
        update_post_meta( $attachment_id, '_webp_url', $webp_url );
    }
} );

// ── 4. Serve WebP via <picture> in post content ──────────────────────
add_filter( 'wp_get_attachment_image', function ( string $html, int $id ): string {
    $webp_url = get_post_meta( $id, '_webp_url', true );
    if ( ! $webp_url ) {
        return $html;
    }
    // Wrap in <picture> with WebP source
    return sprintf(
        '<picture><source srcset="%s" type="image/webp">%s</picture>',
        esc_url( $webp_url ),
        $html
    );
}, 10, 2 );

NOTE: WordPress 5.8 allows uploading WebP natively but does not auto-convert uploaded JPEG/PNG files to WebP — that step still requires custom code or a plugin like Imagify, ShortPixel, or WebP Express. The <picture> element approach shown above is broadly compatible: browsers that support WebP download the <source> element; older browsers fall back to the <img> inside <picture>. For a simpler serving strategy, configure your web server (Nginx or Apache with mod_rewrite) to serve .webp files automatically when the browser sends Accept: image/webp — this approach works without any PHP filter and is more performant for cached responses.