LW Image
Converts WordPress uploads to WebP through the HelloImg API, before the sub-sizes are generated — one API call covers the original and every thumbnail. Originals are kept and restorable.
Overview
| Requires WordPress | 6.0+ |
| Requires PHP | 8.0+ |
| License | GPL-2.0-or-later |
| Slug | lw-img |
| Option | lw_img_options |
| GitHub | lwplugins/lw-img |
This plugin needs a HelloImg API key. Conversion happens on HelloImg’s servers, so nothing works until the key is set on the General tab.
Quick start
- Install and activate the plugin
- Go to LW Plugins → Image
- Paste your API key on the General tab and save
- Check the Tester tab — it verifies WebP support, cron, disk space and the API connection
- Upload an image; it converts on the way in
Installation
Via Composer
composer require lwplugins/lw-imgThe plugin lands in wp-content/plugins/lw-img/.
Manual installation from ZIP
- Open the latest release
and download the
lw-img-<version>.zipasset - In WordPress go to Plugins → Add New → Upload Plugin
- Choose the ZIP and click Install Now, then Activate
Or over SSH, resolving the current release so the command does not go stale:
wp plugin install "$(curl -s https://api.github.com/repos/lwplugins/lw-img/releases/latest \
| grep -oE 'https://[^"]+\.zip')" --activateTake the ZIP from the Releases page, not the green Code → Download ZIP
button. The latter gives you the repository — tests, tooling and CI config —
and unpacks under a lw-img-main/ folder name WordPress will not recognise
as the same plugin on update.
After installation
All LW packages share one LW Plugins admin menu. Whichever loads first registers it; this one adds an Image submenu.
Settings
LW Plugins → Image, seven tabs:
| Tab | What it is for |
|---|---|
| General | API key and connection test, account tiles, the defaults every upload inherits, onboarding |
| Stats | Savings with a before/after bar, biggest wins, leftover folders other optimizers left behind |
| Upload | Master toggle, the conversion pipeline, optimization level, EXIF handling, pattern rules |
| Bulk | Run the optimizer over the existing Media Library, with progress, speed and ETA |
| Backup | Backup lifecycle, storage tiles, retention presets, restore instructions |
| Tester | Environment checks — table engines, WebP/AVIF support, cron loopback, disk space, API |
| Log | The last 200 events, filterable, with paging |
Optimization levels
Set on Upload, overridable per run from the CLI:
| Level | Use it when |
|---|---|
lossless | Photography or print sources where no visible loss is acceptable |
normal | The default — the sensible trade for most sites |
aggressive | Content images where file size matters more than fine detail |
ultra | Thumbnails and decorative images |
Pattern rules
Wildcard patterns matched against filename or path, each with an action: skip the image, keep the original size, force a level, or keep EXIF. They apply identically to uploads and to bulk runs.
What is never converted
WebP and AVIF (already there), and animated GIF (conversion would drop the animation). Images belonging to another optimizer — ShortPixel, TinyPNG, Imagify — are left alone; an active competitor raises a dismissible notice.
WP-CLI
wp lw-img <command>status
Optimization status counts. Leftover figures come from the stored scan — use
wp lw-img leftovers --rescan to walk the uploads tree again.
wp lw-img statusoptimize
Optimize attachments.
| Option | Meaning |
|---|---|
[<id>...] | Attachment IDs to optimize |
--all | Every unoptimized image in the Media Library |
--limit=<number> | With --all, stop after this many |
--dry-run | Only list what would be optimized |
--speed=<speed> | gentle, normal or fast; overrides the saved setting for this process. All profiles back off while server load is high |
--level=<level> | lossless, normal, aggressive or ultra, instead of the saved setting |
--skip-redirect-check | Run even when the web server swallows uploads 404s. Old URLs of converted images will then 404 instead of redirecting — see the Tester tab |
wp lw-img optimize 123 456
wp lw-img optimize --all
wp lw-img optimize --all --speed=gentle
wp lw-img optimize 123 --level=lossless
wp lw-img optimize --all --limit=50 --dry-runSeveral workers can drain the same queue at once — run wp lw-img optimize --all in parallel shells and the Bulk tab follows along live. Images are
claimed, so no two workers take the same one.
restore
Restore attachments from backup.
| Option | Meaning |
|---|---|
<id>... | Attachment IDs to restore |
wp lw-img restore 123list
List attachments by LW Image status.
| Option | Meaning |
|---|---|
--status=<status> | optimized, skipped or failed |
[--limit=<number>] | Max rows, default 100 |
[--format=<format>] | table (default), csv, json, yaml, count |
wp lw-img list --status=skipped
wp lw-img list --status=failed --format=jsonrequeue
Re-queue skipped and/or failed attachments for the next run.
| Option | Meaning |
|---|---|
[--failed] | Attachments whose last attempt failed |
[--skipped] | Attachments that were skipped, e.g. after changing settings |
wp lw-img requeue --failed
wp lw-img requeue --failed --skippedleftovers
Report originals other image optimizers left on disk — both dedicated backup
folders (ShortPixel, Imagify, EWWW) and originals kept beside each image
(Swift Performance’s .swift-original, Smush’s .bak.<ext>).
LW Image only measures these. It never deletes another plugin’s files.
| Option | Meaning |
|---|---|
[--rescan] | Walk the uploads tree again instead of using the stored scan |
[--format=<format>] | table (default), csv, json, yaml, count |
wp lw-img leftovers
wp lw-img leftovers --rescan
wp lw-img leftovers --format=jsondoctor
The Tester tab’s environment checks from the terminal — missing WebP support, swallowed uploads 404s, MyISAM tables. Useful over SSH and from monitoring: it exits non-zero when any check is critical.
| Option | Meaning |
|---|---|
[--format=<format>] | table (default), csv, json, yaml, count |
wp lw-img doctor
wp lw-img doctor --format=jsonsmartcrop
Re-crop existing images with smart crop.
Smart crop normally runs only on new uploads, so a restore, a re-optimize or a thumbnail regeneration drops the crop with no way to redo it from the admin. This command is that way.
The smartcrop_enabled toggle is deliberately not checked — running the
command is the intent. The API key is checked up front, so a missing key fails
once rather than silently failing per size per image.
| Option | Meaning |
|---|---|
[<id>...] | Attachment IDs. Not combinable with --all |
[--all] | Every image attachment in the Media Library |
[--yes] | With --all, skip the confirmation prompt |
[--sizes=<name,name>] | Override the saved smart-crop size selection for this run |
[--dry-run] | List the planned jobs and total API calls; spend nothing |
wp lw-img smartcrop 123 456
wp lw-img smartcrop --all --yes
wp lw-img smartcrop --all --sizes=thumbnail,medium --dry-runSmart crop spends API calls per size per image. Run --dry-run first on a
whole-library job to see the total before committing to it.
Hooks
| Filter | Purpose |
|---|---|
lw_img_should_convert | Decide per attachment whether to convert at all |
lw_img_optimize_request_args | Adjust the request sent to the HelloImg API |
lw_img_competitor_plugins | Extend the list of optimizers whose images are left alone |
lw_img_site_host | Override the host the API is told about |
lw_img_dashboard_url | Point the account link somewhere other than app.helloimg.io |
// Never convert anything under /uploads/originals/.
add_filter( 'lw_img_should_convert', function ( bool $convert, int $attachment_id ): bool {
$file = (string) get_attached_file( $attachment_id );
return str_contains( $file, '/uploads/originals/' ) ? false : $convert;
}, 10, 2 );Troubleshooting
Nothing converts on upload. Check the API key on General, then run
wp lw-img doctor. A missing key and a blocked outbound connection both show
up there.
Old image URLs 404 after conversion. Some servers answer uploads requests
themselves and never reach WordPress, so the redirect from the old URL cannot
run. The Tester tab detects this and names it; --skip-redirect-check only
silences the guard, it does not fix the server.
A bulk run stopped partway. Most often the API quota ran out: the run halts
deliberately, and pending images stay queued rather than being marked failed.
Top up, then run wp lw-img optimize --all again.
Images were skipped and you changed your mind. wp lw-img requeue --skipped
puts them back in the queue.