Pro registers a wp infyp command tree, so image optimization runs from a shell instead of wp-admin. The WP-CLI commands cover single images, whole-library backfills and the full job lifecycle. Every command that writes needs --user, which is the first thing to get right.
WP-CLI is a Pro feature Pro
The commands are registered by the Pro plugin, inside the branch that runs only when the license check passes — no valid license, no wp infyp. They arrived in Pro 1.0.1. The free plugin ships no WP-CLI commands at all, so everything below assumes Pro active on top of it.
What the WP-CLI commands require Pro
Three things: the free plugin and Pro both active, a license Pro accepts, and a WP-CLI binary that can bootstrap the site. Registration happens inside the licensed branch, so an expired key removes the commands from wp help rather than failing them at runtime.
WP-CLI runs as user 0 — nobody is logged in. Anything that writes therefore refuses to start until you name a user with --user=<id|login>. That is not ceremony: the physical file rename behind filename optimization is capability-guarded and must not run as nobody.
$ wp infyp optimize 42
Error: This command writes data and must run as a real user. Add --user=<id|login> (needs upload_files).
$ wp infyp optimize --all --user=author
Error: User 7 lacks the required capability (edit_others_posts + upload_files).
Three commands only read, and run fine without --user: wp infyp status, wp infyp job list and wp infyp job status. Everything else names a capability, and checks upload_files alongside it.
Capabilities per command
| Feature | Capabilities | –user |
|---|---|---|
| wp infyp optimize <id>… One or more attachment IDs. | upload_files, plus edit_post on each ID | Required |
| wp infyp optimize –all Whole-library backfill. | edit_others_posts + upload_files | Required |
| wp infyp status Four lines of library state. | None | Not needed |
| wp infyp job create Queues a job without running it. | edit_others_posts + upload_files | Required |
| wp infyp job list The 20 most recent jobs. | None | Not needed |
| wp infyp job status <id> One job and its failures. | None | Not needed |
| wp infyp job run <id> Executes one batch. | edit_others_posts + upload_files | Required |
| wp infyp job cancel <id> Stops a pending or running job. | edit_others_posts + upload_files | Required |
–user does not lower the checks
The command runs with that user’s authority and nothing more, including the per-image edit_post check on single-image runs. An ID the named user cannot edit is reported as a warning, counted as a failure, and the command exits with an error once any ID has failed.
wp infyp optimize Pro
One command, two modes. Given attachment IDs it optimizes them one after another in the current process. Given --all it creates a server-side job and drives that job to the end behind a progress bar.
wp infyp optimize [<id>...] [--all] [--field=<type>] [--custom-prompt=<text>]
[--clear-keywords] [--dry-run] [--max-cost-usd=<amount>]
[--yes] [--porcelain]
The flags split along the same line. Most of them are read on one path only, and are silently ignored on the other.
Flags
| Setting | Description |
|---|---|
[<id>...]
Pro
|
One or more attachment IDs, optimized synchronously in the order given. Each ID goes through the same call the Optimize with AI button makes. |
--all
Pro
|
Every never-optimized image in the library, as a resumable job. Refuses to start while another job is pending or running. |
--field=<type>
Pro
|
Optimize one field instead of the full set: alt_text, title or filename, plus caption and description, which Pro registers. Single-image mode only. |
--custom-prompt=<text>
Pro
|
Prompt override. On the single-image path it applies to the IDs given; with –all it is stored on the job and applied to every image in it. |
--clear-keywords
Pro
|
Clear stored keywords first, so the AI writes fresh ones instead of building on what is already there. Works in both modes. |
--dry-run
Pro
|
Generate without saving. Single-image mode only — the AI call still happens, so it costs the same as a real run. |
--max-cost-usd=<amount>
Pro
|
With –all only. Halts the job once estimated spend reaches this figure. Omitted or 0 means no ceiling. |
--yes
Pro
|
With –all only. Skips the confirmation prompt, which is what makes the command usable from cron or a deploy script. |
--porcelain
Pro
|
Print only the IDs of successfully optimized images, one per line, and nothing else. Single-image mode only. |
The three examples carried in the command’s own help text:
wp infyp optimize 42 --user=admin
wp infyp optimize 42 --field=caption --user=admin
wp infyp optimize --all --max-cost-usd=5 --yes --user=admin
Before a backfill starts, --all checks that no other job is active, counts the never-optimized images, and prints that count with the active model and the reminder that every image costs one AI call. Read that line as a floor rather than a total: the license that makes the command available also adds a caption and a description call per image, so a backfill runs at three calls each. The confirmation prompt follows unless you pass --yes.
$ wp infyp optimize --all --user=admin
412 unoptimized image(s); model: gemini-3.1-flash-lite-preview. Every image costs one AI call.
Start the backfill? [y/n] y
Job 12 created. Processing…
Optimizing 100% [============================] 0:07 / 0:00
Success: Job 12 completed: 412 processed, 412 succeeded, 0 failed. Estimated spend: $0.0309.
Progress is written to the job row after every image, so an interrupted run is not a lost run. Ctrl-C leaves a job behind that WP-Cron can finish, or that you drive yourself with wp infyp job run. Nothing starts from the top again.
–all is a different code path
With --all the command hands every image to the job runner, which optimizes the full field set and always saves. --field, --dry-run and --porcelain are read only on the single-image path and have no effect next to --all. It also queues the unoptimized filter only — for a job over specific IDs, use wp infyp job create --filter=ids.
A run that ends in any status other than completed exits with an error. When a job completes with failures, the summary prints twice: once as a warning pointing at wp infyp job status <id>, once as the success line.
wp infyp status Pro
Four lines, no options, no writes. It answers whether a run is worth starting.
$ wp infyp status
Images total: 4211
Unoptimized: 1000+
Active model: gemini-3.1-flash-lite-preview
Active job: yes (wp infyp job list)
Images total is what the plugin’s own listing query sees. Unoptimized counts images never optimized. Active model is the model ID that serves the next call. Active job answers whether a job is pending or running right now — the same check --all and job create run before they start.
Unoptimized stops counting at 1000
The count comes from a capped query, so a library with 4,000 never-optimized images reports 1000+. The same cap sets the total on a job created by --all, which is why a job’s processed count can pass its own total. The job keeps going until the images actually run out.
wp infyp job Pro
A job is a bulk run that lives on the server: one row, one cursor, one budget. wp infyp job is the lifecycle around it — create, list, inspect, drive, cancel. One job runs at a time, site-wide.
wp infyp job create [--filter=<unoptimized|ids>] [--image-ids=<ids>]
[--custom-prompt=<text>] [--clear-keywords]
[--max-cost-usd=<amount>] [--porcelain]
wp infyp job list [--format=<table|json|csv|yaml>]
wp infyp job status <id> [--format=<table|json>]
wp infyp job run <id>
wp infyp job cancel <id>
What each subcommand does, and where it refuses:
Subcommands
| Setting | Description |
|---|---|
create
Pro
|
Creates the job, schedules the first cron tick, and prints the ID plus the command to drive it manually. Fails with Another optimization job is already pending or running. when one is active, and with The job would contain no images. when the set is empty. –porcelain prints the ID alone. |
list
Pro
|
The 20 most recent jobs, newest first. Columns: id, status, filter, processed as n/total, failed, spent_usd, created. –format takes table, json, csv or yaml. |
status <id>
Pro
|
One job field by field, then up to 20 lines reading failed image #<id>: <error>. –format takes table or json; the JSON form omits the failure lines. |
run <id>
Pro
|
Executes exactly one batch in the foreground and prints the job state afterwards. A batch works for up to 20 seconds of wall clock. This is the primitive for sites with WP-Cron disabled. |
cancel <id>
Pro
|
Cancels a pending or running job. Anything else fails with Only pending or running jobs can be cancelled. Images already optimized stay optimized. |
list is the fastest way to find an ID and a state:
$ wp infyp job list
+----+-----------+-------------+-----------+--------+-----------+---------------------+
| id | status | filter | processed | failed | spent_usd | created |
+----+-----------+-------------+-----------+--------+-----------+---------------------+
| 12 | completed | unoptimized | 412/412 | 0 | 0.0309 | 2026-08-12 09:02:11 |
| 11 | halted | unoptimized | 88/1000 | 1 | 5.0021 | 2026-08-11 18:40:03 |
+----+-----------+-------------+-----------+--------+-----------+---------------------+
status adds what the list leaves out — the budget, the timestamps and the last error — and then names the images that failed with the error each one returned.
$ wp infyp job status 11
id: 11
status: halted
filter: unoptimized
total: 1000
processed: 88
succeeded: 87
failed: 1
spent_usd: 5.0021
max_cost: 5.0000
created_at: 2026-08-11 18:40:03
finished_at: 2026-08-11 18:44:57
last_error: Budget reached: estimated spend $5.0021 of $5.0000 cap. Costs are estimates from blended per-token pricing.
failed image #8395: Invalid image ID.
There is no –options flag
The free-form options object that extensions read — use_context for page context, for example — can only be set when a job is created over HTTP. See POST /jobs in the REST API reference. A job created on the command line always sends an empty options set.
Jobs created here are the same rows POST /jobs creates, and the two commands read the same store. They also appear under Image SEO → REST API in the Optimization Jobs panel, which shows the 20 most recent and is read-only — no start or cancel controls there, and no budget column. Cancel from the command line or over REST.
Cost budgets Pro
--max-cost-usd puts a ceiling on a job. It is the flag worth setting on a first run against a library you have not costed, and it works the same on wp infyp optimize --all and wp infyp job create.
After each image the runner adds that image’s recorded cost to the job’s spend, reading the rows the free plugin writes to its API usage table. Before starting the next image it compares spend against the cap. At or above it, the job moves to halted, the pending cron tick is cleared, and last_error records the arithmetic.
$ wp infyp optimize --all --max-cost-usd=5 --yes --user=admin
1000 unoptimized image(s); model: claude-sonnet-4-5. Every image costs one AI call.
Job 11 created. Processing…
Optimizing 8% [== ] 1:12 / 14:30
Error: Job 11 halted: 88 processed, 87 succeeded, 1 failed. Estimated spend: $5.0021. Last error: Budget reached: estimated spend $5.0021 of $5.0000 cap. Costs are estimates from blended per-token pricing.
Three things follow from measuring spend rather than predicting it. The cap is checked between images, so the final figure can pass it by the cost of one image. The figures are estimates from blended per-token pricing, not provider invoices. And a positive budget below 0.0001 is raised to 0.0001, the smallest amount the column stores.
A halted job does not resume
halted is terminal. wp infyp job run on a halted job returns The job is not in a runnable state., because only pending and running jobs are runnable. It also stops counting as an active job, so a new one can be created with a higher cap — and because that job re-queries the never-optimized images, the work the halted job finished is not repeated.
Next steps
The same jobs are reachable over HTTP, and the browser has a bulk runner of its own if a shell is not where you work.
REST API reference
The infyp/v1 routes, including POST /jobs with the options object WP-CLI cannot send.
Bulk Optimization (Pro)
The browser alternative — selection controls, live progress and pause or resume, driven by an open tab.
License Activation
Activate Pro so the wp infyp commands register at all.