Skip to Content
PluginsLW Image

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 WordPress6.0+
Requires PHP8.0+
LicenseGPL-2.0-or-later
Sluglw-img
Optionlw_img_options
GitHublwplugins/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

  1. Install and activate the plugin
  2. Go to LW Plugins → Image
  3. Paste your API key on the General tab and save
  4. Check the Tester tab — it verifies WebP support, cron, disk space and the API connection
  5. Upload an image; it converts on the way in

Installation

Via Composer

composer require lwplugins/lw-img

The plugin lands in wp-content/plugins/lw-img/.

Manual installation from ZIP

  1. Open the latest release  and download the lw-img-<version>.zip asset
  2. In WordPress go to Plugins → Add New → Upload Plugin
  3. 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')" --activate

Take 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:

TabWhat it is for
GeneralAPI key and connection test, account tiles, the defaults every upload inherits, onboarding
StatsSavings with a before/after bar, biggest wins, leftover folders other optimizers left behind
UploadMaster toggle, the conversion pipeline, optimization level, EXIF handling, pattern rules
BulkRun the optimizer over the existing Media Library, with progress, speed and ETA
BackupBackup lifecycle, storage tiles, retention presets, restore instructions
TesterEnvironment checks — table engines, WebP/AVIF support, cron loopback, disk space, API
LogThe last 200 events, filterable, with paging

Optimization levels

Set on Upload, overridable per run from the CLI:

LevelUse it when
losslessPhotography or print sources where no visible loss is acceptable
normalThe default — the sensible trade for most sites
aggressiveContent images where file size matters more than fine detail
ultraThumbnails 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 status

optimize

Optimize attachments.

OptionMeaning
[<id>...]Attachment IDs to optimize
--allEvery unoptimized image in the Media Library
--limit=<number>With --all, stop after this many
--dry-runOnly 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-checkRun 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-run

Several 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.

OptionMeaning
<id>...Attachment IDs to restore
wp lw-img restore 123

list

List attachments by LW Image status.

OptionMeaning
--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=json

requeue

Re-queue skipped and/or failed attachments for the next run.

OptionMeaning
[--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 --skipped

leftovers

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.

OptionMeaning
[--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=json

doctor

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.

OptionMeaning
[--format=<format>]table (default), csv, json, yaml, count
wp lw-img doctor wp lw-img doctor --format=json

smartcrop

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.

OptionMeaning
[<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-run

Smart 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

FilterPurpose
lw_img_should_convertDecide per attachment whether to convert at all
lw_img_optimize_request_argsAdjust the request sent to the HelloImg API
lw_img_competitor_pluginsExtend the list of optimizers whose images are left alone
lw_img_site_hostOverride the host the API is told about
lw_img_dashboard_urlPoint 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.