Plugin documentation

WebP/AVIF Images for WordPress

Writes WebP copies (and optional AVIF copies) of your JPEG and PNG images beside the originals, points your pages at them, loads each page's main image first, fills in missing image sizes and lists images without alt text. The originals are never changed.

Version 0.5.3 WordPress plugin Paid plugin Requires WordPress 6.5 Requires PHP 8.0 Tested up to 7.1 Updated 6 Oct 2026

1. Overview

WebP/AVIF Images for WordPress writes a smaller copy of every JPEG and PNG image in your Media Library, next to the original file. For the file and for every size WordPress made from it, photo-300x300.jpg gains a neighbour called photo-300x300.jpg.webp. When you switch serving on, the plugin reads the finished HTML of each front-end page and adds .webp to the image addresses that have a copy. Visitors download the smaller file; your originals, your Media Library and every address stored in your posts stay exactly as they were.

It works on the finished page rather than through server rules, so it needs no .htaccess or nginx change and works with any theme. It uses the image library WordPress already uses on your server (Imagick or GD). There are no binaries to install and no external conversion service: images are converted on your own server and are never sent anywhere.

The same screen also covers three related jobs. It can load the first large image of each page before everything else, make image sizes that the current theme expects and an image does not have, and list every image that has no alternative text so you can write it.

How it works

  1. Copies are written. New and edited images convert in the background about a minute after they arrive. For existing images you press a button on Media → Image optimisation, or run wp wphub-webp convert.
  2. Every copy is checked as it is written. A copy that is not a complete image is deleted and the reason recorded. A copy that comes out the same size as the original or larger is deleted too, and the original is kept.
  3. Pages are rewritten (only if you tick Serve to visitors). On each front-end page, any address in your uploads folder that ends in .jpg, .jpeg or .png and has a usable copy gets .webp added. The page cache of your caching plugin stores the rewritten page.
  4. A small script protects visitors. If a browser fails to show a rewritten image, the script on the page switches that image back (AVIF to WebP, WebP to the original).

At a glance

Originals untouched

Copies sit beside the original with .webp or .avif added to the file name. The original file, the attachment and the stored URLs are only read.

Every size converted

The attached file and each generated size of every JPEG and PNG. GIF and SVG files are left alone.

Optional AVIF

Off by default. An AVIF copy is kept only when it is smaller than both the original and the WebP copy, and is served only once your web server is confirmed to send .avif files as image/avif.

Graphics stay sharp

Each PNG is also encoded lossless. The lossless copy is kept when it is at most 10% larger than the lossy one. Photographs stay lossy.

Load first

On by default. The first large image of each page is fetched with high priority and is not lazy-loaded. It works even while serving is off.

Sizes and alt text

Make missing or wrongly sized image sizes from the original, and write alternative text for images that have none. Nothing is filled in for you.

What it does not do

  • It does not change, replace or delete your original images, and it does not change any URL stored in a post, option or the Media Library. The exceptions are the Image sizes tool, which adds new size files and updates the size list of the images it fixes (see Image sizes), and the Alt text tab, which saves the alternative text you type on that image (see Alt text). Old size files are never deleted.
  • It does not convert GIF, SVG, WebP or AVIF originals. Only image/jpeg and image/png attachments get copies.
  • It does not rewrite images served from another host or a CDN domain, images outside the uploads folder (theme and plugin images), or images that are set in an external CSS file. Images set in an inline style attribute are rewritten.
  • It does not rewrite emails, RSS feeds, sitemaps, the REST API, wp-admin, <meta> tags (Open Graph and Twitter images) or JSON-LD structured data. Those keep the original URLs.
  • It does not write alternative text for you.
  • It does not add shortcodes, blocks, REST routes, widgets or database tables, and it sets no cookies.

2. Requirements

ItemRequirementNotes
WordPress6.5 or laterFrom the Requires at least line of the plugin header. The readme says tested up to 7.1. This page was checked against WordPress 7.1.2.
PHP8.0 or laterFrom the Requires PHP line of the plugin header. WordPress itself refuses to activate a plugin on an older PHP version. The plugin has no PHP check of its own.
Image library for WebPImagick or GD with WebP supportThe plugin asks WordPress whether its image editor can write image/webp. If not, the Image optimisation screen shows an error and disables the convert buttons (see When the server cannot write WebP or AVIF).
Image library for AVIF (optional)Imagick with the AVIF format, or GD with imageavif()Only needed if you tick AVIF. Without it the AVIF checkbox is greyed out.
Web server for AVIF (optional)Sends .avif files as image/avifThe plugin checks this itself with one request to your own site (see AVIF copies).
Site can request itselfNeeded for background runs and the AVIF checkA password-protected staging site or a firewall that blocks the site calling itself stops both. On-screen runs do not need it.
Uploads folderWritable by PHPCopies are written beside the originals. If a copy cannot be written, the image is listed under Could not convert with the reason the image library gave.
WooCommerceOptionalNot required. If WooCommerce is active, the product gallery zoom and lightbox images and the variation images are rewritten too, and WooCommerce's bundled Action Scheduler runs the background conversions.
Action SchedulerOptionalIf any plugin provides it (WooCommerce does), new-upload conversions are queued there in the group wphub-webp. Otherwise WP-Cron is used.
WP-CLIOptionalAdds the wp wphub-webp commands.

3. Installation

  1. Download the plugin zip from your account on wpexpertshub.com.
  2. In WordPress open Plugins → Add New → Upload Plugin, choose the zip and click Install Now. You can also upload the wphub-webp folder to /wp-content/plugins/.
  3. Click Activate. A Settings link and a Licence link appear under the plugin name on the Plugins screen.
  4. Open Media → Image optimisation to convert your existing library. New uploads convert automatically.
  5. Optional but recommended: activate your licence to receive updates (see below).

What activation creates

Nothing. There is no activation hook, so activating creates no tables, no options, no cron events, no roles and no pages. The settings option wphub_webp_settings is written the first time you save the settings. Until then the plugin uses its defaults (see Settings). The plugin adds the menu item Media → Image optimisation, which only users with the manage_options capability can open.

Licence activation and updates

The plugin bundles the WpExperts Hub licence and update client. The plugin works as soon as it is activated. In the plugin's code, no feature checks the licence. The licence key only matters for updates: the update package link comes from the licence server, and without an active licence the Plugins screen shows Automatic update is unavailable for this plugin. Activate your licence to enable updates. under an available update.

  1. Open Plugins → WpExperts Hub Licences. The same screen opens from the Licence link under the plugin name, and from the Manage licences link in the notice on the Plugins screen. The screen needs the manage_options capability and lists every WpExperts Hub plugin active on the site.
  2. Paste the key from your purchase email (it is also shown under My Account → Downloads on wpexpertshub.com) into the box labelled Licence key and click Activate licence. Characters other than letters, digits and hyphens are removed from the key before it is sent.
  3. The card for the plugin then shows Active and the key with the middle masked, with a Deactivate licence button. New versions appear on Dashboard → Updates and on the Plugins screen and install like any other plugin update.
  • Can't find your key? Email it to me. Open this fold-out on the licence screen, enter the email address used for the purchase and click Send licence key. It asks wpexpertshub.com to email the key. The message you see comes from the server.
  • Moving to another site. The screen says: Deactivate before moving the plugin to another live site. Click Deactivate licence. The licence is removed from this site even when the licence server cannot be reached; the screen then shows Licence removed from this site.
  • If the server reports the licence as no longer active on this site, the card changes to Not active at the next update check. The key stays stored, so you can activate it again.
  • Update information is cached for 12 hours (one batched request for all WpExperts Hub plugins on the site). If the licence server cannot be reached or answers badly, the previous information is kept and the plugin tries again after one hour. The cache is cleared after any plugin update and after you activate, deactivate or request a key.
  • When the client loads. Only in wp-admin, during WP-Cron runs and under WP-CLI. Front-end requests do not load it.

What is sent to wpexpertshub.com, and when, is listed under Privacy and data.

Updating

With an active licence, update from Dashboard → Updates or the Plugins screen. Without one, upload the new zip under Plugins → Add New → Upload Plugin and choose to replace the current version. Either way your settings, the records in the database and the copies on disk are kept. After an update, existing copies stay valid; to redo copies with new settings use Re-convert (see The WebP and AVIF tab).

Multisite

The plugin has no multisite-specific code. Settings, records and the licence key are stored per site, and the copies sit in each site's own uploads folder. The update cache is a site transient, so it is shared by the network. uninstall.php does not loop over the sites of a network: on multisite, deleting the plugin clears the settings and records only for the site where the uninstall runs (see Deactivation and uninstall).

4. Quick start

  1. Activate the plugin and open Media → Image optimisation. Check that no red notice says the server cannot write WebP.
  2. Click Estimate time and disk space to see roughly how long the conversion will take and how much smaller the copies will be. This converts a small sample into temporary files and writes nothing beside your originals.
  3. Click Convert remaining images and leave the screen open. For a large library click Check all in the background instead, which carries on with the screen closed.
  4. Look at a few images in Media → Library (list view). The WebP column shows the saving for each image or the reason it could not be converted.
  5. Tick Serve to visitors under Settings on the same screen and click Save Changes.
  6. Clear any page cache. Open a page and view its source: image addresses now end in .webp. Add ?wphub_webp=0 to the page address to compare with the original page.
TipLeave AVIF off until WebP is working. AVIF is slower to make, and not every host can write it.

5. Features

How copies are made

For each attachment whose type is image/jpeg or image/png, the plugin lists the files that should have a copy:

  • the attached file, which is the -scaled version when WordPress scaled a large upload (in WordPress 7.1.2 core scales images whose width or height is above 2,560 pixels by default, through the big_image_size_threshold filter), and
  • every size listed in the image's metadata whose file exists on disk.

The pre-scaled original that WordPress keeps is left out: WordPress never puts it on a page, so a copy would only cost disk space.

Each file is converted in this order:

  1. Files are handled smallest first.
  2. A copy from an earlier run that is damaged is deleted before anything else, so it can never be served.
  3. If the file already has a current copy (the copy exists, is at least as new as the file and passes the check), it is left alone. If an earlier run found the copy larger than the original and the file has not changed since, it is skipped. Otherwise the file is encoded.
  4. The WebP copy is written to file.ext.webp beside the original, with Imagick or else GD directly. With Imagick the image is turned upright according to its orientation, CMYK images are converted to sRGB, the colour profile is kept and other metadata (EXIF and similar) is dropped. For the lossy WebP copy only: if both direct encoders fail, WordPress's own image editor is tried, and after it WordPress's GD editor.
  5. The copy is checked (see below). A copy that fails is deleted.
  6. If the copy is not smaller than the original, it is deleted and the file is remembered as kept as original. The plugin does not try that file again unless the file changes (its modification time is recorded) or you use Re-convert.
  7. If AVIF is on, the AVIF copy is made the same way and compared with the better of the original and the WebP copy (see AVIF copies).
  8. The outcome is stored on the attachment as the post meta _wphub_webp (counts, bytes, files kept as original, errors).

One conversion of an image may run for up to 120 seconds on hosts that allow the plugin to raise the PHP time limit.

Quality, lossless graphics and the size check

Lossy WebP copies use the WebP quality setting (40 to 95, default 80). The plugin writes copies with Imagick or GD directly because WordPress's image editor ignored the quality value when changing format. A change to the setting applies to images converted from then on; use Re-convert to redo existing copies.

With Keep PNG graphics lossless on (the default), every PNG is also encoded lossless. The lossless copy replaces the lossy one when it is at most 10% larger than the lossy copy (the filter wphub_webp_lossless_allowance, default 1.1, changes that). Logos, icons and flat artwork usually pass this test and stay pixel-sharp with exact transparency; photographs saved as PNG do not, and stay lossy. JPEG files are always lossy. Within one attachment the smallest size is tried first, and if lossless loses there, the larger sizes of that attachment are not tried lossless, which keeps a photographic PNG as quick to convert as a JPEG.

Every WebP or AVIF file is checked the moment it is written, and a file that fails is deleted and never counted or served:

  • It must be at least 32 bytes.
  • A WebP must be a RIFF/WEBP file whose declared length matches the file size, with a VP8 , VP8L or VP8X chunk, and PHP's own image reader must read it with a width and height above zero.
  • An AVIF must start with a file-type box naming the avif or avis brand, its boxes must run exactly to the end of the file with a meta and an mdat box among them, and PHP's image reader must recognise it as AVIF where PHP can (PHP 8.1 and later).

This check was added after a long bulk run on a development site produced thousands of 16-byte AVIF files when the PHP process had grown very large. A damaged file recorded this way shows the reason: The encoder wrote a damaged WEBP file (N bytes), so it was not kept. This usually means the PHP process ran short of memory during a long run; a later check makes it again.

Automatic conversion of new and edited images

With New uploads ticked (the default), the plugin queues a conversion whenever WordPress saves attachment metadata for a JPEG or PNG: on upload, after an image is edited, when sizes are regenerated, and when another plugin updates the metadata. It hooks wp_generate_attachment_metadata and wp_update_attachment_metadata at priority 99 and returns the metadata unchanged.

  • Delayed by a minute. WordPress writes an upload's sizes one at a time and saves the metadata after each, so an immediate job could convert only the sizes already on disk. The job runs 60 seconds later (the filter wphub_webp_queue_delay changes the delay, in seconds) and reads the file list when it runs.
  • One pending job per image. If a job for the same image is already waiting, no second one is added. A job that is already running does not count, so a second job queues behind it. Converting is safe to repeat, so the second one only fills what the first missed.
  • Action Scheduler or WP-Cron. If Action Scheduler is available (WooCommerce bundles it) the job is a single action of the hook wphub_webp_convert in the group wphub-webp. Otherwise it is a single WP-Cron event of the same hook. Both carry the attachment ID as the only argument.
  • Not forced. A copy already newer than its file is left alone. A file that WordPress regenerated is newer than its copy, so it is converted again.
  • Deleting an image deletes its copies too (hooked on delete_attachment, while the metadata still lists the sizes).

With the setting off, new images are not converted automatically. They show Not converted yet in the Media Library until you run a conversion yourself. Switching it off does not remove anything that already exists.

The Image optimisation screen

Open Media → Image optimisation. The screen needs the manage_options capability. For other users WordPress hides the menu item and refuses a direct visit to the address with its own message, Sorry, you are not allowed to access this page. (in WordPress 7.1.2 this check is in wp-admin/includes/menu.php, and it runs before the plugin's own You cannot do that. check is reached). It has three tabs: WebP & AVIF (the default), Image sizes and Alt text. A Settings link under the plugin name on the Plugins screen opens it.

The WebP and AVIF tab

Status

Four tiles summarise the library. Figures refresh while a run is going.

TileWhat it shows
Images with WebPHow many JPEG and PNG images have been through the converter (they have a current record), out of all JPEG and PNG images. An image that was converted but kept as original still counts here.
Still to convertJPEG and PNG images with no record yet.
Space savedThe percentage by which the copies are smaller than the originals they came from, with the byte totals (and the number of AVIF files once there are any). Shows a dash while nothing is converted.
Could not convertImages with at least one file that could not be converted. The tile turns to a warning colour when above zero. These keep serving their originals.

A progress bar shows percentage, count and an estimate of the time left. It only moves forward within a run.

Buttons

ButtonWhat it does
Estimate time and disk spaceTakes the images that have no record (it looks at up to 5,000 of them), picks up to 15 of their files at random, converts those into temporary files, deletes them and extrapolates. The result reads, for example: N files to convert (X MB). Estimated WebP copies: about Y MB on disk — roughly Z% smaller than the originals. Estimated time on this server: about T. If nothing is left to convert it says Nothing left to convert. Writes nothing beside your originals.
Convert remaining imagesConverts every image that has no record, newest first, in the browser. Greyed out when there are none, when the server cannot write WebP, and while a background run is active.
Check all images againWalks every JPEG and PNG image in ID order and converts only what is missing: a size with no copy, a copy older than its file, a damaged copy, an image with no record and, once AVIF is on, a missing AVIF copy. Copies that are already right are not touched, so checking an up-to-date library writes nothing. Damaged copies from earlier runs are removed and made again, and the finish message says how many.
Check all in the backgroundThe same check, run by the site itself so it carries on with the screen or browser closed (see below).
Re-convert every image (fold-out): Re-convert all imagesMakes every copy again with the current settings, replacing existing copies as each image is done, so the site keeps working throughout. Asks you to confirm. Use it after changing the quality or switching lossless graphics on. Originals are not touched.
Re-convert all in the backgroundThe same, run in the background. Asks you to confirm.
Stop background runAppears while a background run is active (the other background buttons are hidden then). The run's records are deleted and no further step is started. A step that is already running is not interrupted: it keeps converting until its time slice (about 20 seconds by default) ends, and what it writes to disk stays, but nothing is added to the run. A check started again skips everything already done; a background re-convert started again begins from the first image.
Remove all WebP and AVIF copies (fold-out): Remove all copiesDeletes the copies this plugin made and their records, in the browser, after a confirmation. Greyed out while Serve to visitors is ticked in the saved settings. Originals are not touched. It removes the copies of images that have a record. An image the plugin has never processed (no record) keeps any copies you uploaded by hand.

A run in the browser pauses when you click the same button again (it then reads Pause while running). Click it again to carry on from where it stopped. Convert, Check and Re-convert work in parallel lanes (three by default, see wphub_webp_parallel); each lane takes only the images whose ID falls in its share, so two lanes never work on the same image. Each request works for about five seconds (the filter wphub_webp_batch_seconds, clamped to 1 to 25).

Under the buttons the screen says how many images are waiting for the background job (N images are waiting for that now.), that every image has been through the converter when none is left (which is why Convert remaining images is greyed out), and what Check all images again does.

Background runs

A background run is a chain of short requests that the site makes to its own admin-ajax.php (action wphub_webp_bg). No request depends on a visitor or on your browser.

  1. Starting a run sends one test request to the site itself and waits up to 15 seconds for the expected answer. If the site cannot reach itself, the run is refused with a reason (see Troubleshooting).
  2. The work is split into lanes by attachment ID (ID % lanes). Each lane runs a step of about 20 seconds (the filter wphub_webp_background_seconds, clamped to 5 to 45), records where it got to, sends a request for its own next step and returns.
  3. The request carries a random token stored with the run, not a login cookie. A request with the wrong or no token gets HTTP 403.
  4. A watchdog restarts a lane that has gone quiet. It runs on WP-Cron every minute and on every status check the screen makes while it is open, so an open screen alone keeps a run alive. A lane with no step running that has not reported for 25 seconds is sent another request. A step whose lock is older than 120 seconds is treated as dead and taken over.
  5. If the encoder writes damaged files three steps in a row, that lane stops and the screen says why. Restart PHP (or wait for the host to recycle it) and start the check again.
  6. When you come back to the screen, a run in progress is shown with its progress, and the bar updates every five seconds. A finished run's summary stays on the screen until you start another run (the Stop button only shows while a run is active).

Images that could not be converted

When the Could not convert count is above zero, a table lists up to 25 images with the file name and the reason recorded. The reason comes from the image library on your server (shortened to 240 characters); a damaged file usually needs uploading again. Try again re-converts that image with the force option. AVIF problems are shown in the Media Library column, but do not add to the Could not convert count.

Serving copies to visitors

Tick Serve to visitors (off by default) and save. The plugin then passes the finished HTML of each front-end page through one filter. It starts an output buffer on template_redirect at the latest possible priority, so a plugin that answers the request itself and exits (a sitemap, a redirect) has already done so.

Which requests are rewritten

Only ordinary front-end HTML pages: not wp-admin, AJAX, cron, feeds, robots.txt, trackbacks, embeds, REST requests, XML-RPC or any JSON request, and not any response whose Content-Type header, if one has been set by then, is not text/html. The page must contain an <html tag. It applies to logged-in users as well as visitors. The filter wphub_webp_rewrite_enabled can switch the whole pass off for a request, and adding ?wphub_webp=0 to any page address shows that page without the rewrite and without Load first, so you can compare.

What is rewritten

Any address on your site's uploads URL (as WordPress reports it, for example https://example.com/wp-content/uploads/2026/09/a.jpg) that ends in .jpg, .jpeg or .png, in any case, and whose copy exists gets .webp added. This applies to:

  • absolute and protocol-relative addresses (https://… and //…),
  • root-relative addresses (/wp-content/uploads/…), only where an address can start (after a quote, an opening parenthesis, a space, a comma, an equals sign or a semicolon), so a path inside another site's address is not touched,
  • addresses in src, srcset, data-* attributes and inline style="background-image:…", and
  • JSON-escaped addresses such as https:\/\/example.com\/wp-content\/uploads\/a.jpg, which is how WooCommerce prints the variation images.

Only images that already have a usable copy are switched. Nothing else in the markup changes, so theme CSS is not affected. A copy counts as usable when it exists and is at least 32 bytes (for AVIF: it also starts with an AVIF file-type box). The front end does not compare dates, so a copy that is older than a replaced original is served until it is converted again; "Check all images again" converts copies older than their file. A path containing .. is never followed.

What keeps its original address

Before the rewrite, every <meta> tag and every application/ld+json script block is set aside and put back afterwards. Open Graph and Twitter images and structured data therefore keep the JPEG or PNG address: some social platforms still refuse WebP, and product feeds read the structured data. Emails, feeds, sitemaps, the REST API and wp-admin never pass through the buffer at all.

The fallback script

When at least one address on a page was changed, a small inline script (<script id="wphub-webp-fallback">) is added just before </head>. It listens for image load errors. When an <img> fails and its current source contains .jpg.avif, .jpeg.avif or .png.avif, it switches that image's src and srcset to the .webp copy. When the source contains .jpg.webp, .jpeg.webp or .png.webp, it switches to the original. Each step removes a suffix, so it cannot loop, and it works on images that a gallery swaps to a new photo later. It covers <img> elements only: a CSS background that a browser cannot show is not repaired.

Safety

If the rewrite fails for any reason (a regular expression error or an exception), the original page is sent unchanged. Turn Serve to visitors off, or deactivate the plugin, and pages return to the original addresses. Clear any page cache after you change this setting, because cached pages hold the rewritten addresses.

AVIF copies

Tick AVIF (off by default) to make a second copy, file.ext.avif, beside the WebP copy. AVIF is usually smaller than WebP but slower to make.

  • Server support. The checkbox is greyed out and the screen says This server cannot write AVIF images (neither Imagick nor GD supports it). if WordPress's image editor cannot write image/avif.
  • Kept only when worthwhile. An AVIF copy is kept only if it is smaller than the original and smaller than the WebP copy (when there is one). Otherwise it is deleted and remembered, like a WebP that came out larger.
  • Quality. The AVIF quality setting, 30 to 90, default 60. The encoder speed is 8 on a scale of 0 (slowest, smallest) to 10 (the filter wphub_webp_avif_speed; Imagick uses at most 9).
  • Where it is served. AVIF goes to <img> src and srcset, and since 0.5.3 also to the WooCommerce gallery's data-src, data-large_image and data-thumb attributes and to the data-product_variations data. Anchor links, CSS backgrounds and everything else stay WebP. The filter wphub_webp_avif_scripted (default true) turns off the gallery part.
  • The web server check. Many servers still send .avif as application/octet-stream. Chrome shows such an image anyway, but Safari and others may not. So AVIF copies are put on pages only after the plugin has confirmed the type. When the Image optimisation screen opens and AVIF is supported, the plugin writes an 8 by 8 pixel AVIF file named wphub-webp-check-<random>.avif into the uploads folder, requests it once with a HEAD request to your own site (5 second timeout, certificate not verified because the request goes to the same site), deletes it, and stores the answer in the option wphub_webp_avif_mime with the time. The answer is reused for 24 hours. Only an HTTP 200 answer counts; a content type starting with image/avif is ok, any other is bad, and no answer is unknown.

The screen reports the result under the AVIF checkbox:

ResultMessageEffect
okThe web server sends .avif files with the right type.AVIF is served.
badThis web server sends .avif files as "type" instead of image/avif. Some browsers (Safari among them) will not show an image sent that way, so AVIF copies are made but not put on pages until the server is told the type. On nginx add "image/avif avif;" to mime.types; on Apache add "AddType image/avif .avif". Managed hosts do this on request. This screen checks again once a day.Copies are made, not served.
unknownCould not ask the web server how it sends .avif files (the site could not reach itself), so AVIF copies are made but not put on pages.Copies are made, not served.

The check only runs when the WebP and AVIF tab is opened, and it asks again only when the stored answer is older than 24 hours. After fixing the server, open the tab again after a day, or delete the option wphub_webp_avif_mime to force a fresh check. The filter wphub_webp_serve_avif can override the verdict. After switching AVIF on, press Check all images again to make the AVIF copies for existing images.

Saving without AVIF supportOn a server that cannot write AVIF, the AVIF checkbox and the AVIF quality box are disabled and are not submitted with the form. Saving the settings there stores AVIF as off and AVIF quality as 60.

Load first

With Load first ticked (the default), the plugin marks the first large image of each page so browsers fetch it before anything else. This runs on the same page pass as serving and works even while Serve to visitors is off. If both settings are off, the plugin does not touch the page at all. The filter wphub_webp_priority_enabled can turn it off for a request.

The plugin works from the finished HTML. Starting at the <body> tag it looks for the first image that qualifies:

  • Content inside <noscript> is ignored, because it is not what renders.
  • An <img> counts when it is at least 300 by 150 pixels by its width and height attributes (a missing attribute is not compared), or has a srcset and no stated size. Logos, icons and emoji (class emoji), SVG files and images with a data: source do not count. The filters wphub_webp_priority_min_width (300) and wphub_webp_priority_min_height (150) change the thresholds.
  • If the first candidate has a data: placeholder source and a width of at least 300, or has data-src or data-srcset (a lazy-loading script will swap it), the plugin does nothing for the page, because choosing a later image would be a guess.
  • An element counts when its inline style gives it a background picture (jpg, jpeg, png, webp, avif or gif), unless the file is shipped by WordPress or a plugin (the address contains /wp-includes/, /wp-admin/, /plugins/ or /mu-plugins/), or the style gives it a width below 300 pixels or a height below 150 pixels.

For an <img> the plugin removes loading="lazy" and any existing fetchpriority and adds fetchpriority="high". For a background it adds <link rel="preload" as="image" href="…" fetchpriority="high" id="wphub-webp-preload"> before </head>, because a CSS background cannot carry fetchpriority. Pages where an <img> in the body already has fetchpriority="high" (WordPress core and most themes do that on ordinary pages) are left alone.

Image sizes

WordPress makes an image's sizes once, when it is uploaded, with the sizes registered that day. After a theme switch or a change to WooCommerce image settings or Settings → Media, older images keep their old sizes: a thumbnail registered today at 400 × 400 may still be served from a 100 × 100 file, blurred. WordPress's own missing-sizes check compares only size names, so it never notices a size whose dimensions changed. The Image sizes tab compares by dimensions.

  • Sizes in use now lists every size registered by the active theme and plugins (name, width, height, cropped yes or no), after WordPress's intermediate_image_sizes_advanced filter.
  • Images counts JPEG, PNG and WebP attachments, which are the types this tab looks at.
  • Check (changes nothing) goes through every image and counts how many need new or corrected sizes and how many files that would be. It writes nothing. The result reads, for example: Check finished: N images looked at; M need new or corrected sizes (F files to make). Nothing was changed.
  • Make missing sizes makes them. An image needs a size when the size is missing from its metadata, when the file named in its metadata is not on disk, or when its recorded width or height differs by more than one pixel from what WordPress would make now. A size the original is too small for is skipped, as WordPress would skip it.

How a size is made: the old entry is taken out of the image's metadata, then WordPress's own routines (wp_update_image_subsizes) make the file from the original upload, as on upload. The new file gets its own name, because the dimensions are part of the file name. Old size files are never deleted, because older posts may still link to them. If a size cannot be made, its old metadata entry is put back. Each JPEG or PNG that gains sizes has its WebP copies made straight away, and its attachment metadata now lists the new files. This is the one place where the plugin writes to the Media Library's size data. The result reads, for example: Finished: N images looked at; M were given new sizes (F files made, with their WebP copies). Originals and old size files were left in place. Up to five error lines of the form Title: Some sizes could not be made from the original file. are added.

The same tool is available as wp wphub-webp sizes (see WP-CLI).

Media Library column and per-image actions

In the list view of Media → Library a WebP column appears after the title. Each row shows the status of that image, one line per fact:

LineMeaning
Not a JPEG or PNGThe image type is not handled.
Not converted yetThe image has no record.
N% smaller · N file(s)Saving over the files that have a WebP copy, and how many files that is (copies made now plus copies already current).
N kept lossless (graphic)How many of the files used the lossless encoding.
AVIF: N file(s)How many AVIF copies were kept.
N kept as original (WebP was larger)Files whose copy was not smaller than the original, so the original is served.
Could not convert FILE: reasonUp to three failures, shown in red. AVIF problems are prefixed AVIF:.
No copiesThe image was processed and none of its files needed or kept a copy.

Under the file name, a row action reads Convert to WebP for an image with no record or Re-convert WebP for one with a record (which forces its copies to be redone with the current settings). The same status and a Convert now or Re-convert link appear in a read-only WebP row of the attachment details panel, which is what the grid view uses because it has no columns. Both need permission to edit that image. After the click you return to where you were and a notice says "Title" converted: N WebP file(s) written. or, on failure, "Title": N file(s) could not be converted. The reason is shown in the WebP column; the original is served meanwhile.

Alt text

The Alt text tab lists every image in the Media Library (any image type, not only JPEG and PNG) that has no alternative text, or only blank text, newest first, 20 per page. The heading counts them (N images have no alternative text) and the count goes down as you save.

  1. Each row shows a small preview, the title (linked to the attachment's edit screen), the file name and the post it was uploaded to.
  2. Type a description in the box and click Save or press Enter. The text is stored as the image's alternative text (the _wp_attachment_image_alt meta) after WordPress's text sanitising. The box allows 300 characters in the browser.
  3. Saving an empty box shows Type the alternative text first. and saves nothing. After a successful save the row shows Saved, the button reads Saved and the cursor moves to the next box.

The pager above and below the list reads Showing 1–20 of N, with Previous, Next, page numbers and, under the list, a Go to page box. Next carries on from the last image shown rather than from a page offset, so saving some images on a page never makes it skip images. Saving requires permission to edit that image. Nothing is filled in automatically: an image that is only decoration can go without, which is why the plugin leaves the decision to you.

When each feature arrived

From the readme's changelog:

  • 0.2.0: Check all images again, a one-minute wait before converting a new upload, and files whose WebP came out larger are remembered.
  • 0.3.0: the status tiles and progress bar, and batches of about five seconds.
  • 0.4.0: the three tabs under Media → Image optimisation, the working quality setting, optional AVIF, lossless PNG graphics, Load first, the Image sizes tab, the Media Library column and per-image actions, the Alt text tab, and root-relative addresses.
  • 0.4.1: the Alt text pager.
  • 0.5.0: faster AVIF (encoder speed 8), parallel lanes, and background runs.
  • 0.5.1: the check that discards damaged copies, and the action wphub_webp_written.
  • 0.5.2: runs that no longer stop part-way or step backwards.
  • 0.5.3: AVIF for the product gallery, the two-stage browser fallback, and licence activation under Plugins → WpExperts Hub Licences.

Command line

With WP-CLI the plugin adds wp wphub-webp status, convert, sizes and purge. They are described, with options and examples, under WP-CLI.

6. Where to find things

PlacePathWhoWhat is there
Image optimisation, tab WebP & AVIFMedia → Image optimisationmanage_optionsStatus tiles, convert and check buttons, re-convert, background runs, the failure list, all settings, and Remove all copies.
Image optimisation, tab Image sizesMedia → Image optimisation then Image sizes (upload.php?page=wphub-webp&tab=sizes)manage_optionsSizes in use now, Check, Make missing sizes.
Image optimisation, tab Alt textMedia → Image optimisation then Alt text (upload.php?page=wphub-webp&tab=alt)manage_options to open it; permission to edit the image to saveThe list of images without alternative text.
WebP column and row actionMedia → Library, list viewPermission to edit the image for the actionPer-image status, Convert to WebP, Re-convert WebP.
WebP row in attachment detailsMedia Library grid view, or the media dialog, with an image selectedPermission to edit the imageThe same status with Convert now or Re-convert.
LicencesPlugins → WpExperts Hub Licencesmanage_optionsActivate or deactivate the licence, email me my key.
Plugin linksPlugins screen, under the plugin nameSettings for everyone who sees the row; Licence for manage_optionsShortcuts to the screens above.

7. Settings

All settings are on Media → Image optimisation, in the Settings card of the WebP & AVIF tab. They are stored together in one option, wphub_webp_settings, and saved with Save Changes through the WordPress Settings API. Values outside a range are clamped to the nearest allowed value when saved. An unticked checkbox is saved as off. Until the first save, the defaults below apply.

Setting (label)KeyDefaultWhat it does
Serve to visitorsserveOffPoints front-end pages at the WebP (and AVIF) copies. Only images that have a copy are switched. Emails, feeds, sitemaps, social-sharing images and structured data keep the originals. Add ?wphub_webp=0 to a page address to see it without WebP. Off by default so a fresh install converts first and rewrites pages only after you have looked at the result.
Load firstpriorityOnTells browsers to fetch the first large image of each page before anything else: fetchpriority="high", never lazy-loaded, or a preload when it is a background. Pages where WordPress or the theme already marked one are left alone. Works while Serve to visitors is off.
New uploadson_uploadOnConverts images automatically, in the background, when they are uploaded or edited.
WebP qualityquality80Quality of lossy WebP copies, a whole number from 40 to 95. A change affects images converted from then on; existing copies are kept until you re-convert.
Graphics: Keep PNG graphics lossless where that is no biggerlosslessOnEach PNG is encoded both ways. The lossless copy is kept when it is at most 10% larger than the lossy copy. Photographs saved as PNG stay lossy. JPEGs are always lossy.
AVIF: Also make AVIF copies, and serve them to images on the pageavifOffMakes AVIF copies and serves them to <img> tags once the web server check passes. Greyed out when the server cannot write AVIF. After switching it on, press Check all images again.
AVIF qualityavif_quality60Quality of AVIF copies, a whole number from 30 to 90. Greyed out when the server cannot write AVIF.

Other settings that are not on this form: the licence key (stored by the licence client, see Reference), and the tuning filters listed under Filters.

8. Reference for developers

WP-CLI

The commands are registered under wp wphub-webp when WP-CLI is running.

CommandOptionsWhat it does
wp wphub-webp statusnonePrints one line: N of M images have WebP copies; P still to do. Converted files: originals X, WebP Y — Z% smaller.
wp wphub-webp convert--limit=<n>, --id=<id>, --forceWithout --id, converts images that have no record, newest first, in batches of up to 50, stopping after --limit images (at least 1) or when none are left, then prints the summary line. With --id, converts that one attachment and prints the result as JSON. --force only works together with --id and redoes copies that are already current. It does not look for missing sizes or damaged copies on images that already have a record; use the screen's Check all images again for that.
wp wphub-webp sizes--dry-run, --id=<id>Finds and makes the image sizes that are missing or have the wrong dimensions, for all JPEG, PNG and WebP images or for one. With --dry-run it only lists, per image, the size names that are needed. It ends with N images checked; M needed sizes; F files made. (or to make with --dry-run). Originals are only read and old size files are kept.
wp wphub-webp purge--yesAsks Remove every WebP and AVIF copy? Originals are not touched. unless --yes is given, then removes the copies and records of every image that has a record and prints N WebP and AVIF files removed.
wp wphub-webp status
wp wphub-webp convert --limit=200
wp wphub-webp convert --id=123 --force
wp wphub-webp sizes --dry-run
wp wphub-webp sizes --id=123
wp wphub-webp purge --yes

AJAX and admin-post actions

All AJAX calls are POST requests to admin-ajax.php. The screen's calls send the nonce for the action wphub_webp as _ajax_nonce.

ActionWhoParametersWhat it does
wphub_webp_batchmanage_options, noncemode (convert, purge, scan, redo; anything else is convert), slot, slots (1 to 8), after (cursor)One batch of about five seconds of the on-screen convert, remove, check or re-convert. Returns counts, the next cursor, whether it finished, and the figures for the status tiles.
wphub_webp_estimatemanage_options, noncenoneThe estimate described above. Returns the message as HTML.
wphub_webp_sizesmanage_options, nonceafter, write (1 makes sizes, otherwise only counts)One batch of the size check or repair: up to 100 images when counting, 5 when writing, for up to the batch time.
wphub_webp_altPermission edit_post for that attachment, nonceid, altSaves the alternative text. An empty text returns HTTP 400 with Type the alternative text first.; a missing or non-attachment ID, or no permission, returns 403.
wphub_webp_bg_startmanage_options, noncemode (redo or anything else for scan)Starts a background run (replacing any run in progress) and returns its status, or an error message.
wphub_webp_bg_stopmanage_options, noncenoneStops the run and deletes its records.
wphub_webp_bg_statusmanage_options, noncenoneRuns the watchdog and returns the run's status and the tile figures.
wphub_webp_bg (registered as wp_ajax_wphub_webp_bg and wp_ajax_nopriv_wphub_webp_bg)The run's random token, no logintoken, slot, or ping=1One step of one lane, or a test that answers pong. Wrong or missing token: HTTP 403. A slot that is not part of the run: 400. Other answers: busy (another step holds the lane), done, stalled, stopped, next.
wphub_webp_one (admin-post, admin-post.php?action=wphub_webp_one)Permission edit_post for that attachment, nonce wphub_webp_one_<id>id, force (0 or 1)Converts one image, then redirects back with the result in the query arguments wphub_webp_done, wphub_webp_made and wphub_webp_failed, which WordPress removes from the address after display.

Filters

FilterDefaultWhere and what
wphub_webp_rewrite_enabledtrueParameter: bool $enabled. Checked after the settings and the request type. Return false to skip the page pass (rewrite and Load first) for this request.
wphub_webp_priority_enabledtrueParameter: bool $enabled. Return false to skip Load first for a page.
wphub_webp_priority_min_width300Minimum width in pixels for the first large image.
wphub_webp_priority_min_height150Minimum height in pixels for the first large image.
wphub_webp_avif_scriptedtrueReturn false to keep WebP for the gallery's data-src, data-large_image, data-thumb and data-product_variations even when AVIF is served.
wphub_webp_serve_avifthe stored web server check (true when ok)Parameter: bool $ok. Only applied when AVIF is ticked and the server can write it. Return true to serve AVIF although the check did not pass, or false to never serve it.
wphub_webp_lossless_allowance1.1A float. A lossless PNG copy is kept when its size is at most the lossy size times this number.
wphub_webp_avif_speed8Encoder speed, clamped to 0 (slowest, smallest) to 10. Imagick uses at most 9.
wphub_webp_queue_delay60Seconds between an image changing and its background conversion.
wphub_webp_batch_seconds5Seconds one on-screen batch may run, clamped to 1 to 25.
wphub_webp_background_seconds20Seconds one background step may run, clamped to 5 to 45.
wphub_webp_parallel3Number of lanes for on-screen and background runs, clamped to 1 to 8. Lower it to 1 on a very small host.
wphub_webp_background_urladmin_url( 'admin-ajax.php' )The address a background run requests. Change it where the site cannot reach its own public address.
// Turn Load first off on the front page.
add_filter( 'wphub_webp_priority_enabled', function ( $enabled ) {
	return is_front_page() ? false : $enabled;
} );

// Allow lossless PNG copies up to 25% bigger than the lossy ones.
add_filter( 'wphub_webp_lossless_allowance', function () {
	return 1.25;
} );

The first filter runs while the page is being output, after the main query, so conditional tags such as is_front_page() work. The second runs each time a PNG is converted.

Actions

ActionParametersWhen
wphub_webp_writtenstring $dest (path of the file), string $kind (webp or avif)Since 0.5.1. Fires after the encoder wrote a file and before the plugin checks it. It also fires for the temporary files used by the lossless attempt (….lossless-tmp) and by the estimate, so test $dest before acting on it. A plugin that post-processes copies can hook here; the check that follows still decides whether the file is kept.
wphub_webp_convertattachment IDThe queued conversion job (an Action Scheduler action or a WP-Cron event). The plugin hooks its own handler here.

Core hooks the plugin uses

HookPriorityWhy
template_redirectPHP_INT_MAXStarts the output buffer that rewrites the page.
wp_generate_attachment_metadata, wp_update_attachment_metadata (filters)99Queue a conversion. The metadata is returned unchanged.
delete_attachment10Removes the copies of an image that is being deleted.
cron_schedules (filter)10Adds the one-minute schedule wphub_webp_minute, labelled Every minute (WebP/AVIF Images background run).
intermediate_image_sizes_advanced (filter applied by the plugin)n/aThe Image sizes tab applies WordPress's own filter to the registered sizes, so plugins that remove or add sizes on upload are respected.
wp_get_missing_image_subsizes (filter added briefly by the plugin)PHP_INT_MAXWhile making sizes, the plugin hands WordPress exactly the sizes it found, then removes the filter. Core's own check looks at names only.
https_local_ssl_verify (filter applied by the plugin)n/aDefault false: the background run's requests to the site itself do not verify the certificate unless this filter returns true.
manage_media_columns, manage_media_custom_column, media_row_actions, attachment_fields_to_edit10The WebP column, row action and details row.
admin_menu, admin_init, admin_enqueue_scripts, admin_notices, admin_head, removable_query_args, plugin_action_links_<basename>10The screen, its Settings API registration, its script and style (loaded only on the Image optimisation screen), the result notice, a few lines of column CSS, and the Settings link.

Cron events and scheduled jobs

HookTypeWhat it does
wphub_webp_convertSingle event (or Action Scheduler action, group wphub-webp), argument: attachment IDConverts one image, not forced. Scheduled 60 seconds after the image's metadata is saved, once per pending image.
wphub_webp_bg_healthRecurring, schedule wphub_webp_minute (every 60 seconds)The watchdog of a background run. Scheduled when a run starts and cleared when the run is stopped or every lane is done. No deactivation hook clears it, so deactivating the plugin during a run leaves the event scheduled.

Options, post meta and transients

NameKindContents
wphub_webp_settingsOptionThe seven settings: quality, on_upload, serve, avif, avif_quality, lossless, priority.
wphub_webp_avif_mimeOptionThe web server check: status (ok, bad, unknown), type (the content type sent) and time.
wphub_webp_bgOptionThe current background run: token, mode (scan or redo), lanes, started.
wphub_webp_bg_lane_0 to wphub_webp_bg_lane_7Option (one per lane)A lane's progress: cursor after, counts seen, fixed, files, repaired, broken, strikes, and the flags stalled, done, and the time of the last report beat.
wphub_webp_bg_lock_0 to wphub_webp_bg_lock_7Option (one per lane)A lane's lock while a step runs: the time and a random step ID. Taken with an atomic database insert; a lock older than 120 seconds is taken over.
_wphub_webpPost meta on each JPEG or PNG attachmentThe record of the last conversion: files, converted, current, larger, failed, in and out (bytes before and after), lossless, avif, avif_out, repaired, v (record version, currently 1), quality, time, skip and avif_skip (file name to modification time of files kept as original), and errors (file name to reason).
_wp_attachment_image_altPost meta (core)Written by the Alt text tab.
_wphub-webp_licence_key, _wphub-webp_key_statusOptionsWritten by the licence client: the key and active or inactive.
wpxh_licence_check_v2Site transient, 12 hours (1 hour after a failure)The cached answer of the licence server's update check.
wpxh_licence_msg_<user id>Transient, 120 secondsThe message shown once after activating, deactivating or requesting a key.

The plugin creates no database tables.

Files, constants and code

  • Copies: <original path>.webp and <original path>.avif, in the same folder as the original. During a lossless attempt a temporary file <original path>.webp.lossless-tmp exists for a moment.
  • AVIF check file: wphub-webp-check-<random>.avif in the uploads root, deleted straight after the check.
  • Constants the plugin defines: WPHUB_WEBP_VERSION, WPHUB_WEBP_FILE, WPHUB_WEBP_PATH. Do not define them yourself.
  • Constants and filters you may define for the licence client: WPXH_LICENCE_SERVER and the filters wpxh_licence_server and wpxh_licence_sslverify change the licence server address and whether its certificate is verified (verification is on unless the server host ends in .local, .test, .localhost or is localhost or 127.0.0.1).
  • Layout: wphub-webp.php (loader), inc/ (settings, converter, sizes, queue, background, rewriter, CLI), admin/ (screen and Media Library), assets/ (admin script and style), licence/ (licence client), uninstall.php.
  • PHP classes: the plugin's classes are final and used by its own screens and commands. They are not documented as a stable API. For example, WPHub_WebP_Converter::convert( int $id, bool $force = false ) converts one attachment and returns the counts, and WPHub_WebP_Rewriter::rewrite( string $html, ?bool $serve = null, ?bool $priority = null ) rewrites a string of HTML. Prefer the hooks above.
  • The licence client defines the class once: the first WpExperts Hub plugin to load defines WPXH_Licence_Client_V2, and every plugin registers itself with it.

9. Privacy and data

The plugin stores no personal data about visitors and sets no cookies. It does not register a privacy policy text, a personal data exporter or an eraser.

What is stored

  • Your settings in the option wphub_webp_settings.
  • A record per image in the post meta _wphub_webp on each JPEG or PNG attachment: counts, byte totals, the names of files kept as original and the reasons for failures.
  • Run state in the options wphub_webp_bg, wphub_webp_bg_lane_* and wphub_webp_bg_lock_* while a background run exists, and the web server check result in wphub_webp_avif_mime.
  • The copies as .webp and .avif files in your uploads folder, next to the originals. They are public in the same way as the originals: anyone who can request an original from your web server can request its copy. The plugin applies no access control and sets no file permissions of its own.
  • Licence data (if you activate a licence): the key and its status in the options _wphub-webp_licence_key and _wphub-webp_key_status, and the cache of the update check.

The page pass adds the script wphub-webp-fallback to pages and, for Load first, may add an fetchpriority attribute or a preload link. Neither collects anything.

What is sent anywhere

Images are converted on your own server and are never sent anywhere. The plugin makes two kinds of request to your own site, and requests to wpexpertshub.com for the licence only.

ToWhenWhat is sent
Your own site (admin-ajax.php)When you start a background run, and for each step of the runThe action name, the run's random token, the lane number. Nothing leaves your server.
Your own site (the uploads folder)When the Image optimisation screen opens with AVIF supported and the stored web server check is older than 24 hoursA HEAD request for a temporary 8 by 8 pixel AVIF file. Nothing leaves your server.
https://wpexpertshub.com/wp-json/wphub-licence/v1/activateWhen you click Activate licenceThe plugin slug (wphub-webp), the licence key and your site address (site_url()).
…/deactivateWhen you click Deactivate licenceThe plugin slug, the licence key and your site address.
…/send-keyWhen you click Send licence keyThe plugin slug and the email address you typed.
…/checkWhen WordPress refreshes its plugin update information in wp-admin or in a WP-Cron run, or when plugin details are opened, and the cached answer is missing or older than 12 hours (1 hour after a failed request)Your site address and, for each WpExperts Hub plugin active on the site, its slug, its licence key (empty if none) and its installed version. The reply carries the latest version and the package link. The client's own header comment says the package link is only returned for sites with an active licence; that is decided on wpexpertshub.com.

As with every request WordPress makes, the HTTP headers include WordPress's user agent, which contains the WordPress version and your site address. No customer, order or visitor data is sent. Front-end page requests do not load the licence client. A request to wpexpertshub.com can still happen inside a WP-Cron run, because WordPress checks for plugin updates twice a day through WP-Cron.

Deactivation and uninstall

  • Deactivating stops the rewrite at once, so pages return to the original addresses. It deletes nothing: copies, records, settings and any queued or running jobs stay. The plugin has no deactivation hook.
  • Deleting the plugin runs uninstall.php, which deletes the options wphub_webp_settings and wphub_webp_avif_mime and every _wphub_webp post meta row. It does not loop over the sites of a multisite network.
  • It leaves behind all .webp and .avif files, because deleting thousands of files in one request can time out half way; the options wphub_webp_bg, wphub_webp_bg_lane_* and wphub_webp_bg_lock_* if a run state still exists; the licence options _wphub-webp_licence_key and _wphub-webp_key_status and the licence cache; and the key's activation on wpexpertshub.com, unless you deactivated the licence first.
Before deleting the pluginTurn Serve to visitors off, press Remove all copies (or run wp wphub-webp purge), deactivate your licence under Plugins → WpExperts Hub Licences, then delete the plugin.

10. Troubleshooting

When the server cannot write WebP or AVIF

If WordPress's image editor on your server cannot write image/webp, the WebP and AVIF tab shows a red notice: This server cannot write WebP images (neither Imagick nor GD supports it), so nothing can be converted. Ask the host to enable WebP in Imagick or GD. The buttons Convert remaining images, Check all images again, Check all in the background and both Re-convert buttons are greyed out. Ask your host to enable WebP in Imagick or GD.

If the server cannot write AVIF, the AVIF checkbox and the AVIF quality box are greyed out and the screen says This server cannot write AVIF images (neither Imagick nor GD supports it). WebP still works. A single file can still fail with This server cannot write WebP images. or File is not an image the server can read.; then the file or the library is the problem, and the reason is shown in the WebP column.

Common problems

ProblemLikely cause and fix
Pages still show the original JPEG addresses.Serve to visitors is off (the default). Or a page cache is holding an older page: clear it. Or the image has no copy yet, or its copy was kept as original because it was not smaller. Or the address is on another host, a CDN domain, outside the uploads folder or in an external CSS file. Or the page is not a front-end HTML page. Add ?wphub_webp=0 to compare, and check the WebP column for that image.
Convert remaining images is greyed out.Every image has already been through the converter, the server cannot write WebP, or a background run is active. Use Check all images again to fill missing sizes, copies that are out of date or damaged copies.
An image shows "kept as original (WebP was larger)".The copy was not smaller than the original (it happens with tiny or already heavily compressed files), so it was deleted. This is expected. The plugin does not try that file again unless it changes. Use Re-convert to force another try.
An image is listed under "Images that could not be converted".The reason comes from the image library on your server. A damaged or unreadable file usually needs uploading again. Click Try again after fixing the cause.
The error says "The encoder wrote a damaged WEBP file" (or AVIF), or a run stops with "Stopped: the server's image encoder has started writing damaged files".The PHP process has run short of memory during a long run. Nothing damaged is kept or shown. Restart PHP (or wait a few minutes for the host to recycle it), then start the check again; it carries on and skips everything already done.
AVIF copies exist, but pages still use WebP.AVIF is served only after the web server check says ok. Read the message under the AVIF checkbox: if the server sends another type, add image/avif avif; to nginx's mime.types, or AddType image/avif .avif to Apache. The screen checks again once a day. Also check that Serve to visitors is ticked, and that the image has an AVIF copy (it is kept only when smaller than both the original and the WebP).
The message says "Could not ask the web server how it sends .avif files".The site could not request its own uploads folder (a password on the site, a firewall, or an address the server cannot reach), or the request did not return HTTP 200. Fix the loopback access, then wait for the next daily check or delete the option wphub_webp_avif_mime.
Starting a background run is refused: "This site could not reach itself to run in the background (…)" or "This site answered its own background request with HTTP N rather than the expected reply — often a password on the site, or a firewall."The site cannot call its own admin-ajax.php. Use Check all images again on the screen instead (it carries on while the screen is open), or open the loopback (for example allow the server's own address through the firewall or remove the staging password). The filter wphub_webp_background_url can change the address that is requested.
A background run seems stuck at a percentage.A lane lost its next-step request. The watchdog restarts a lane that has been quiet for 25 seconds, on WP-Cron every minute and whenever the screen is open, so keep the screen open or make sure WP-Cron runs. A lane that stopped because of damaged files shows the message about the encoder and does not restart on its own.
A new upload has no copy straight away.Conversion waits a minute after the upload and then runs on Action Scheduler or WP-Cron, so it also needs a WP-Cron run to happen. Check that New uploads is ticked and that WP-Cron is not disabled without a real cron job. The screen tells you how many images are waiting.
Changing the WebP quality did not change my existing images.A quality change applies to images converted from then on. Use Re-convert all images (or the background version) to redo existing copies.
The Image sizes check lists images after I changed the theme, and "Make missing sizes" reports "Some sizes could not be made from the original file."Sizes are made from the original upload. If the original is missing, too small or unreadable, WordPress cannot make the size and the old entry is put back. Old size files are never deleted either way.
I saved the settings and AVIF was turned off.On a server that cannot write AVIF, the AVIF checkbox and quality box are disabled and are not submitted, so saving stores AVIF as off and its quality as 60.
"Remove all copies" is greyed out.Serve to visitors is ticked in the saved settings. Untick it, save, and then remove the copies, so pages do not point at files that are about to disappear.
Update notice says "Automatic update is unavailable for this plugin."No active licence on this site. Activate it under Plugins → WpExperts Hub Licences, or upload the new zip by hand.

11. FAQ

Does it change or delete my original images?

No. Conversion only reads your files and writes copies named like photo.jpg.webp. Deactivate the plugin and your pages show the originals again. Two tools write anything else, and only when you use them: Image sizes adds new size files and updates the size list of the images it fixes (it never deletes old files), and Alt text saves the alternative text you type on that image.

Do I need to edit .htaccess or nginx rules?

No. The plugin rewrites the finished page HTML. Only AVIF needs your web server to send .avif files as image/avif, and the screen tells you how to set that on nginx or Apache if it does not.

Is it switched on after I install it?

New uploads are converted and Load first is on. Serving the copies to visitors is off until you tick Serve to visitors, and AVIF is off until you tick it.

Can it convert my existing library?

Yes: Convert remaining images, Check all images again, or the background versions that keep running with the browser closed. From the command line use wp wphub-webp convert.

What if the WebP copy is bigger than the original?

The copy is deleted and the original is kept. The Media Library column shows kept as original (WebP was larger), and the plugin does not try that file again unless it changes.

Will my logos and icons go blurry?

No. With Keep PNG graphics lossless on, each PNG is also tried lossless and that copy is kept when it is at most 10% larger than the lossy one. Photographs stay lossy.

Which images does it handle?

JPEG and PNG, for the full-size image and every generated size. GIF and SVG files are left alone, and so are images that are already WebP or AVIF.

Does it work with WooCommerce?

Yes, but WooCommerce is not required. Because it works on the page HTML, it also switches the product gallery zoom, lightbox and variation images. Structured data keeps the original URLs.

Will it affect SEO or social sharing?

Pages load faster. Image addresses on pages change to .webp, so image search will crawl them again, and the original addresses keep working. Open Graph and Twitter tags (all <meta> tags), JSON-LD, sitemaps, feeds and the REST API keep the original URLs.

What about browsers that cannot show WebP or AVIF?

A small inline script steps a failing <img> back from AVIF to WebP to the original. AVIF is only served after the server check passes. A CSS background is not covered by the script.

What does it not cover?

Images set in external CSS files, images from another host or a CDN domain, and theme or plugin images outside the uploads folder are not rewritten. Images set in an inline style attribute are.

Should I clear my cache?

Yes. After you switch serving on or off, clear any page cache so visitors get the updated pages.

Can I convert on another machine and upload the copies?

Yes. Copies uploaded next to their originals are recognised as long as they are newer than the original file, so do not preserve old modification times when uploading. The plugin only converts what is missing. Remove all copies only handles images that have a record, so copies of an image the plugin has never processed are left in place.

How do I remove it cleanly?

Turn Serve to visitors off, press Remove all copies (or run wp wphub-webp purge), deactivate your licence, then delete the plugin. Uninstalling removes the settings and records but leaves any remaining .webp and .avif files on disk, and the licence options (see Deactivation and uninstall).

Does it write alt text for me?

No. The Alt text tab lists images that have none, and you write each description yourself.

How do I activate my licence and get updates?

Open Plugins → WpExperts Hub Licences, paste the key from your purchase email and click Activate licence. Updates then appear on the normal WordPress updates screen. The plugin works without a licence; the key only unlocks update packages. See Licence activation and updates.

Does the plugin contact any outside server?

Only for the licence and updates, and only from wp-admin, WP-Cron and WP-CLI. See What is sent anywhere. Images are converted on your own server and never sent anywhere.

12. Changelog

0.5.3

  • AVIF is now also used for the product gallery's zoom and lightbox images (data-src, data-large_image, data-thumb) and for the variation data, not only for <img> src/srcset. Anchor links and everything else stay WebP. Filter wphub_webp_avif_scripted turns it off.
  • The fallback for browsers that cannot show an image now steps down in two stages (AVIF, then WebP, then the original) and works on images the gallery swaps to a new photo later; before, it acted once per image element.

0.5.2

  • Background and on-screen runs no longer stop part-way or step backwards. The "next step" request is now delivered reliably (it used a 0.01 second timeout that a TLS handshake outlasts, so lanes silently stopped until the watchdog restarted them). A lane that has lost its next step is restarted within 25 seconds instead of 2 minutes.
  • Two steps can no longer both own a lane: the lock is atomic, a step that outlives it writes nothing back, and progress is added to the lane record under the lock. Previously a late step overwrote newer counts and the percentage dropped (for example from 50% to 35%).
  • The progress bar only moves forward within a run, because lanes report in whatever order their requests finish.

0.5.1

  • Damaged copies are never kept, counted or served. Every WebP and AVIF is checked as it is written (headers, length, and PHP's own image reader); a damaged one is deleted and the reason recorded. Seen on a long bulk run: once the PHP process had grown very large, the AVIF encoder wrote 16-byte files without reporting an error, and 0.5.0 and earlier kept them.
  • "Check all images again" now finds damaged copies from earlier runs, removes them and makes them again.
  • The front end only points a page at a copy that passes the same check.
  • A run stops, with an explanation, when the encoder writes damaged files several images in a row, instead of failing through the whole library. Restart PHP (or wait for the host to recycle it) and press the button again.
  • New action wphub_webp_written (file, kind), fired after a copy is written and before it is checked.

0.5.0

  • AVIF is made about 2.5 times faster: the encoder runs at speed 8 instead of 6 (measured: identical file sizes with Imagick, about 5% larger with GD). Filter wphub_webp_avif_speed.
  • The convert, check and re-convert runs on the screen now work in three parallel lanes (filter wphub_webp_parallel, 1–8), each taking its own share of the images.
  • "Check all in the background" and "Re-convert all in the background": the run carries on with the screen or browser closed, as a chain of short requests the site makes to itself, with a watchdog (WP-Cron every minute, and the screen while it is open) that restarts a lane that goes quiet. Refused up front, with the reason, if the site cannot reach itself.
  • No more PHP 8.5 deprecation notices from imagedestroy().

0.4.1

  • Alt text: a proper pager above and below the list — "Showing 1–20 of 899", Previous/Next, page numbers and a box to jump to a page.
  • Alt text: "Next" no longer skips images after you save some on the current page (it now carries on from the last image shown).
  • Alt text: the count in the heading goes down as you save, and the cursor moves to the next image's box.
  • Alt text: the list uses a wider card.

0.4.0

  • Media → Image optimisation (was "WebP images"), in three tabs: WebP & AVIF, Image sizes, Alt text.
  • The Quality setting now works. WordPress's image editor ignored it when changing format, so every copy came out at about 86; copies are now made with Imagick or GD directly. "Re-convert all images" redoes existing copies with the current settings.
  • Optional AVIF copies for <img> tags, with a check that the web server sends them as image/avif.
  • PNG graphics kept lossless when that is no bigger; photographs stay lossy.
  • "Load first": the page's first large image is fetched before anything else.
  • Image sizes tab: make missing or out-of-date sizes from the originals.
  • Media library column, per-image convert/re-convert, and recorded reasons for images that cannot be converted, with "Try again".
  • Alt text tab.
  • Root-relative image URLs (/wp-content/uploads/…) are served as WebP too.

0.3.0

  • Media → WebP images redesigned: status tiles (images with WebP, still to convert, space saved, could not convert) and a new progress bar with percentage, count and time left.
  • The bar glides smoothly between the figures each batch reports, instead of jumping; converting, checking and removing each show their own progress.
  • Batches run for about 5 seconds instead of 20, so the figures update more often (filter wphub_webp_batch_seconds).

0.2.0

  • "Check all images again" on Media → WebP images: looks at every image and converts any size with a missing or out-of-date copy. Changes nothing that is already right.
  • Background conversion now waits a minute after an upload, so it no longer runs while WordPress is still writing the image's sizes (which left some sizes without a copy), and queues again if a conversion is already running.
  • Files whose WebP came out larger are remembered, so a re-check does not try them again unless the file changes.
  • The screen says why "Convert remaining images" is greyed out and how many images are waiting for the background job.

0.1.0

  • First release.

13. Support

Email support@wpexpertshub.com. Please include:

  • your order ID,
  • the plugin version (0.5.3 or whichever you run), your WordPress version and your PHP version,
  • whether your server uses Imagick or GD, if you know it (the host can tell you),
  • what you did, what you expected and what happened, with any message shown on the Image optimisation screen or in the WebP column, and
  • what you already tried.