WP Auto WebP Converter
WordPress plugin: auto-convert post images to WebP on save. Drop-in, idempotent, preserves originals as fallback.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/burakgon/wp-auto-webp-converter-plugin/archive/refs/heads/main.zipA WordPress plugin that turns the images in your posts into WebP automatically the moment you hit Save.
No setup. No background queue to babysit. No client-side JavaScript. Just install, activate, and every post you save afterwards ships with .webp images in the HTML — while the originals stay on disk untouched as a fallback.
Why
Most WebP plugins fall into one of two camps: heavyweight "image optimization suites" that ask for an API key and a credit card, or .htaccess rewrites that quietly serve .webp to browsers behind the scenes. Both have their place. This one solves a smaller, sharper problem:
When I publish a post, I want the rendered HTML to literally contain
<img src="…/photo.webp">, not…/photo.jpg.
That way every downstream consumer — Cloudflare, your CDN, AMP, RSS aggregators, Open Graph crawlers, RSS-to-newsletter pipelines — gets the WebP URL directly, no Accept header negotiation, no <picture> element gymnastics.
What it does
On every save_post_post (manual draft save, publish, scheduled, REST API, classic editor, block editor — anything that fires the hook), the plugin will:
- Read the saved post content and find every
<img>-style URL pointing to a JPG / PNG / GIF inside/wp-content/uploads. - Generate a sibling
.webpnext to each original. Photos use WebP quality 95; PNG files use lossless WebP so text, UI graphics, and transparency remain pixel-accurate. Imagick is preferred, with the WordPress image editor as fallback. - Rewrite
src,srcset,data-src,data-lazy-src, anddata-srcsetattributes — plus Gutenberg block-comment"url":"…"metadata — to point at the.webptwin. - Write the updated HTML straight back to the
wp_poststable. - Generate
.webpsidecars for the featured image attachment original and every WP-generated thumbnail size. - At render time, transparently swap featured image, gallery, REST API, schema.org, and Open Graph URLs to their
.webpversions via thewp_get_attachment_image_srcandwp_calculate_image_srcsetfilters whenever a sidecar is present on disk.
Originals are never deleted. External URLs and non-raster files (SVG, JS, etc.) are skipped. Already-converted posts are a no-op on resave.
What it does not do
- Does not generate AVIF (the underlying libraries support it; a small fork away if you want it).
- Does not bulk-process the entire media library automatically. The opt-in
wp auto-webp regeneratecommand can upgrade it in disk-safe batches. - Does not purge your CDN. If you sit behind Cloudflare / Bunny / Fastly, your edge cache will keep serving the old HTML until it expires. Wire your own purge into the
wp_auto_webp_post_typesflow if you need instant invalidation.
Installation
Via WP-CLI
wp plugin install https://github.com/burakgon/wp-auto-webp-converter-plugin/archive/refs/heads/main.zip --activate
Manual
- Download or clone this repository.
- Copy the
wp-auto-webp-converter-plugindirectory intowp-content/plugins/. - Activate WP Auto WebP Converter from the WordPress Plugins screen.
Requirements
- WordPress 5.8+
- PHP 7.4+
- One of:
- PHP Imagick extension built with WebP support (recommended), or
- PHP GD extension built with WebP support (
gd_info()['WebP Support']should be true).
Most modern hosts ship both. If you are unsure, save a post with an image and check the post meta _wp_auto_webp_stats — converted > 0 confirms the encoder is wired up.
Usage
There is nothing to configure. Save a post; check the rendered HTML.
A short, deliberate example:
<!-- Before save -->
<figure class="wp-block-image">
<img src="https://example.com/wp-content/uploads/2026/01/cover.jpg"
srcset="https://example.com/wp-content/uploads/2026/01/cover-300x200.jpg 300w,
https://example.com/wp-content/uploads/2026/01/cover-768x512.jpg 768w" />
</figure>
<!-- After save -->
<figure class="wp-block-image">
<img src="https://example.com/wp-content/uploads/2026/01/cover.webp"
srcset="https://example.com/wp-content/uploads/2026/01/cover-300x200.webp 300w,
https://example.com/wp-content/uploads/2026/01/cover-768x512.webp 768w" />
</figure>
The corresponding cover.webp, cover-300x200.webp, and cover-768x512.webp files now exist on disk alongside the JPGs.
Backfilling existing posts (WP-CLI)
After activating, you almost certainly want to run the conversion on everything you already published:
# Convert a single post
wp auto-webp run --post=1234
# Convert several posts
wp auto-webp run --post=1234,5678,9012
# Convert every post matching wp_auto_webp_post_types (default: 'post')
wp auto-webp run --all
The CLI command prints per-post counts and a final summary:
post 1234: converted=3 failed=0 skipped=1 (content updated)
post 5678: converted=0 failed=0 skipped=0 (no change)
Success: Done. posts_changed=12 converted=47 failed=0 skipped=8
skipped counts external image URLs (URLs not under /wp-content/uploads). failed counts images the editor could not open — usually deleted source files or unreadable permissions.
Re-encoding existing WebP sidecars
Version 1.2 fixes an Imagick ordering issue that could make the configured WebP quality ineffective. New and re-saved images automatically use the corrected encoder. Existing media can be upgraded explicitly:
# Re-encode one source image (path is relative to wp-content/uploads)
wp auto-webp regenerate --file=2026/01/cover.jpg
# Preview how many old sidecars remain
wp auto-webp regenerate --all --dry-run
# Upgrade in repeatable batches; already-upgraded files are skipped on the next run
wp auto-webp regenerate --all --limit=1000
The command writes to a temporary file and atomically replaces the public .webp only after a successful encode. A 5 GiB free-space guard stops bulk work before the uploads disk becomes dangerously full.
Extending
Process additional post types
add_filter( 'wp_auto_webp_post_types', function ( $types ) {
$types[] = 'page';
$types[] = 'product';
return $types;
} );
Adjust photo quality or PNG lossless mode
add_filter( 'wp_auto_webp_quality', function ( $quality, $source_path, $mime ) {
return 95; // 1-100; used for lossy images.
}, 10, 3 );
add_filter( 'wp_auto_webp_lossless', function ( $lossless, $source_path, $mime ) {
return $lossless; // Defaults to true for PNG and false for JPEG.
}, 10, 3 );
Read what happened on the last save
Two post-meta keys are written every time the hook runs (whether or not content changed):
| Meta key | Value |
|---|---|
_wp_auto_webp_last_run |
Unix timestamp of the most recent run |
_wp_auto_webp_stats |
['converted' => N, 'failed' => N, 'skipped_external' => N] |
$stats = get_post_meta( $post_id, '_wp_auto_webp_stats', true );
How it works (under the hood)
- Hook:
save_post_postat priority 20. Autosaves, revisions,auto-draft,trash, andinheritstatuses are rejected up front so the heavy work never runs for noise saves. - Encoder: With Imagick, the output format is switched to WebP before WebP options are applied. JPEG uses quality 95 and method 6. PNG uses lossless mode with alpha quality 100. ICC colour profiles are retained while EXIF and other non-rendering metadata are removed. The WordPress image editor remains the compatibility fallback.
- Safe writes: Encoding happens in a same-directory temporary file. The public sidecar is atomically replaced only after a complete, non-empty WebP has been produced.
- Idempotency: The plugin compares source/sidecar timestamps and the encoder release timestamp. Current sidecars are reused; older v1.0/v1.1 sidecars are upgraded the next time that source is reached or through
wp auto-webp regenerate. - DB writes: Content updates go through
$wpdb->updatedirectly, notwp_update_post. This is deliberate: we only rewrite URL substrings inside attributes we found, never inject new HTML, so we skipwp_ksesre-sanitization and we don't re-enter thesave_postaction. - Block editor compatibility: Gutenberg stores image URLs both in the inner
<img src>and in the block-comment JSON header (``). Both are rewritten in lockstep, so the editor will not flag a "block recovery" mismatch on reopen.
FAQ
Will it break my image lightbox / responsive images / lazy-load plugin?
It only changes attribute values, never tag structure. If your lightbox plugin reads src and data-src (the standard pair), it will keep working — the URL just ends in .webp now.
Does this delete the original JPGs/PNGs? No. They remain on disk so any external link, RSS reader, OG image consumer, or old cache that still references them gets a 200, not a 404.
Will my old posts get converted?
Not until you re-save them, or run wp auto-webp run --all once.
Why not use a <picture> element with <source type="image/webp">?
You can, and it's a fine approach. But rewriting src directly is simpler, plays better with downstream consumers (RSS, OG tags, AMP), and works with every theme/block out of the box. WebP browser support is universal as of 2024 — the fallback path is rarely exercised in practice.
What happens if the same source has both a .webp from me and one from another plugin?
The newest one wins per filemtime check — usually that's whichever plugin ran most recently. There is only ever one .webp per source.
Can I change the quality?
Yes. Use the wp_auto_webp_quality filter; the default is 95. PNG is lossless by default and can be changed with wp_auto_webp_lossless.
Changelog
1.2.1
- Rejects lookalike hosts such as
example.com.eviland sibling paths such asuploads-archivewhen mapping upload URLs to local files. - Adds automated URL-boundary regression tests across supported PHP versions.
1.2.0
- Fixes the Imagick format/quality ordering bug that could make the configured WebP quality ineffective.
- Raises lossy photo quality to 95 and encodes PNG as lossless WebP by default.
- Preserves ICC colour profiles while removing non-rendering metadata.
- Uses atomic temporary-file writes so a failed encode cannot replace a working public image.
- Automatically upgrades old sidecars when their source is reached.
- Adds
wp auto-webp regenerate --file=...and disk-safe, repeatable--all --limit=...batches, plus--dry-run. - Adds
wp_auto_webp_quality,wp_auto_webp_lossless, andwp_auto_webp_min_free_disk_bytesfilters.
1.1.0
- Featured image + everything-else coverage. On save,
.webpsidecars are generated for the featured image attachment and every WordPress-generated thumbnail size. At render time URLs are swapped to.webpvia a stack of complementary filters:wp_get_attachment_image_src,wp_calculate_image_srcset,wp_get_attachment_image_attributes,wp_get_attachment_url— standard WP rendering paths.aioseo_facebook_tags,aioseo_twitter_tags— All in One SEO's cached og/twitter image URLs.- A final-sweep output buffer on
template_redirectrewrites any remaining raster URL under/wp-content/uploadsin the rendered HTML head/body — including JSON-LD `