WP-CLI Commands
PerfLocale provides WP-CLI commands for managing languages, translations, strings, caches, and database integrity from the command line.
Namespace: wp perflocale
Languages
List Languages
wp perflocale languages listDisplays all configured languages in a table.
Options:
--format=<format>- Output format:table(default),json,csv,yaml,count,ids.
Example:
$ wp perflocale languages list
+----+------+--------+-----------+---------+--------+-----------+
| ID | Slug | Locale | Name | Default | Active | Direction |
+----+------+--------+-----------+---------+--------+-----------+
| 1 | en | en_US | English | No | Yes | ltr |
| 2 | fr | fr_FR | French | Yes | Yes | ltr |
| 3 | de | de_DE | German | No | Yes | ltr |
| 4 | ar | ar | Arabic | No | Yes | rtl |
+----+------+--------+-----------+---------+--------+-----------+
$ wp perflocale languages list --format=json
[{"ID":"1","Slug":"en","Locale":"en_US",...}]Add Language
wp perflocale languages add <locale> [--default]Adds a new language.
Arguments:
<locale>(required) - WordPress locale code (e.g.fr_FR,es_ES,ar).
Options:
--default- Set as the default language.
RTL languages (Arabic, Hebrew, Farsi, Urdu, etc.) are auto-detected.
Examples:
# Add French
$ wp perflocale languages add fr_FR
Success: Language fr_FR added with ID 7 (direction: ltr).
# Add Arabic (RTL auto-detected)
$ wp perflocale languages add ar
Success: Language ar added with ID 8 (direction: rtl).
# Add as default language
$ wp perflocale languages add en_US --defaultDelete Language
wp perflocale languages delete <slug>Deletes a language and unlinks its translations; the posts, terms and media themselves stay. Prompts for confirmation. Cannot delete the default language.
A language that still has content is refused. While any post of the language (a post, page, product or any other post type) is outside the Trash, the command stops before the confirmation prompt with the counts per post type, exits with an error, and changes nothing. Move those posts to the Trash first, or deactivate the language instead, on the Languages screen or with PUT /languages/{slug} and "is_active": false: an inactive language keeps its translations linked. Terms, media and block theme template copies in the language do not block the delete; the command warns that they will be unlinked from their translations.
Arguments:
<slug>(required) - Language slug (e.g.fr,de).
Example:
$ wp perflocale languages delete fr
Warning: 8 term(s), 5 media item(s) and 2 block theme template(s) or template part(s) use this language. They will be unlinked from their translations.
Delete language 'fr' (French)? [y/n] y
Success: Language 'fr' deleted.
$ wp perflocale languages delete de
Error: Language 'de' still has content (Posts: 12, Pages: 3, Products: 40). Move those posts to the Trash first, or deactivate the language instead (Languages screen, or REST PATCH is_active=false): an inactive language keeps its translations linked.Safety features:
- Refuses a language that still has posts outside the Trash, before the confirmation prompt.
- Warns about terms, media items and block theme templates that will be unlinked from their translations.
- Requires explicit confirmation (use
--yesto skip in scripts). - Blocks deletion of the default language.
- Reports a delete that did not happen as an error and exits with code 1: “The language could not be deleted; nothing was changed.” when the delete is rolled back, and “Could not check the content of language …; nothing was changed.” when its content cannot be counted.
Translate
Single Post
wp perflocale translate <post_id> --to=<lang> [--provider=<provider>]Translates a single post using machine translation.
Arguments:
<post_id>(required) - The post ID to translate.
Options:
--to=<lang>(required) - Target language slug.--provider=<provider>- Provider:deepl,google,microsoft,libretranslate,external_agency,wp_ai_client. Uses configured default if omitted.
Example:
$ wp perflocale translate 42 --to=de
Translating post 42 to de...
Success: Translated! New post ID: 99A password-protected post is sent when perflocale/mt/send_password_protected allows it (trigger cli_ids, default yes). When the filter refuses it, the command prints “Warning: Post 42: This post is password-protected and was not sent for machine translation.” and translates nothing.
Bulk Translation
wp perflocale translate --all --to=<lang> [--post-type=<type>] [--skip-existing]
wp perflocale translate --post-ids=<ids> --to=<lang> [--skip-existing]Translates multiple posts with a progress bar.
Options:
--all- Translate all published posts of the given type that are in the default language or have no language yet. Translations are never used as sources.--post-type=<type>- Post type for--all. Default:post.--post-ids=<ids>- Comma-separated list of post IDs.--to=<lang>(required) - Target language slug.--skip-existing- Skip posts that already have a translation in the target language.--include-meta- Also machine-translate registered meta fields (SEO titles/descriptions and, when enabled in settings, custom fields).--dry-run- Print a character/cost estimate for the selection (items, characters, monthly budget) without translating anything.--async- With--all: dispatch a resumable chunked background job instead of translating in-process. Track it under PerfLocale → Jobs orwp perflocale jobs list. Without--allthe command stops with an error.--provider=<provider>- Translation provider.
Examples:
# Translate all posts to German, skipping already translated
$ wp perflocale translate --all --to=de --post-type=post --skip-existing
Translating to de 100% [============================] 50 / 50
Success: Done. Translated: 38, Skipped: 12, Failed: 0.
# Translate specific posts to French
$ wp perflocale translate --post-ids=42,43,44 --to=fr
Translating to fr 100% [============================] 3 / 3
Success: Done. Translated: 3, Skipped: 0, Failed: 0.
# Translate all WooCommerce products
$ wp perflocale translate --all --to=fr --post-type=product --skip-existingNotes:
- Machine translation must be enabled - turn the feature on at PerfLocale → Addons, then configure it at Settings → Addons → Machine Translation.
- Failed translations are reported individually but don't stop the batch.
- A password-protected post is sent when
perflocale/mt/send_password_protectedallows it. With--post-idsthe trigger iscli_ids, which sends by default; with--all(also with--async) it iscli_all, which does not. A post the filter refuses counts as Skipped. - Progress bar shows real-time progress.
- A machine translation whose block structure does not match its source is not saved and counts as Failed, with the reason in the warning for that post (see Machine Translation).
- When the target language is deleted or deactivated while the command runs, the posts not yet done count as Failed. After a delete, a translation the command created for that language in its current batch of up to 500 posts is deleted again and counted as Failed; translations from earlier batches stay, and after a deactivation every translation already written stays. The warnings for these posts follow the progress bar. Renaming the language does not stop the run.
Find Damaged Block Markup
wp perflocale damaged-blocks [--lang=<slug>] [--post-type=<type>] [--batch-size=<number>] [--format=<format>]Lists translations whose content has damaged block markup: a block comment saved as visible text (<!-- wp:), block-comment text that is not a block comment (a cut-off opener), blocks opened and not closed, or a block that lost settings its source block has. A translation is listed only when it has more of a kind than its default-language source, so an article about block markup is not listed. Every translation is read, whatever its status, the Trash included. Changes no content or settings; reads in batches. See Machine Translation for how to repair a listed translation: translate its source_id again.
Options:
--lang=<slug>- Only translations in this language. The default language has no translations, so it lists nothing.--post-type=<type>- Only translations of this post type.--batch-size=<number>- Translations read per query. Default:200; a value outside 1 to 1000 is set to the nearest limit.--format=<format>-table(default),csvorjson. An empty JSON result is[].
Columns: post_id (the translation), language, source_id (its default-language source), issue (escaped block delimiter, block delimiter left as text, unbalanced block delimiters or lost block attributes) and spot (the byte offset of the first damaged spot and the next 60 bytes; for lost settings, their names first).
Examples:
# Every damaged translation
$ wp perflocale damaged-blocks
+---------+----------+-----------+-------------------------+-------------------------------------------------+
| post_id | language | source_id | issue | spot |
+---------+----------+-----------+-------------------------+-------------------------------------------------+
| 812 | de | 640 | escaped block delimiter | byte 1043: <!-- wp:group {"layout"... |
| 815 | de | 655 | lost block attributes | lost layout at byte 81: <!-- wp:group -->... |
+---------+----------+-----------+-------------------------+-------------------------------------------------+
# Translate both sources again into German (source_id column)
$ wp perflocale translate --post-ids=640,655 --to=de
# German pages as JSON, for a script (exit status 2 when something is listed)
$ wp perflocale damaged-blocks --lang=de --post-type=page --format=json
# Nothing found
$ wp perflocale damaged-blocks
Success: No translation has damaged block markup.Exit status: 0 when no translation is listed, 2 when translations are listed (after the list), 1 when the command cannot run, for example an unknown --format (“Unknown --format. Use table, csv or json.”), an unknown language (“Unknown language: xx”), or a database read that failed (“The scan stopped because a database read failed. Run the command again.”). A run that fails prints no rows.
Translations
List Translations
wp perflocale translations list <post_id> [--format=<format>]Shows all translations linked to a post.
Example:
$ wp perflocale translations list 42
+----------+---------+--------------------+---------+
| Language | Post ID | Title | Status |
+----------+---------+--------------------+---------+
| EN * | 42 | Hello World | publish |
| FR | 160 | Bonjour le monde | publish |
| DE | 165 | Hallo Welt | publish |
+----------+---------+--------------------+---------+The * marks the current post's language.
Create Translation
wp perflocale translations create <post_id> --to=<lang>Creates a new translation post (copies content from source).
Example:
$ wp perflocale translations create 42 --to=fr
Success: Translation created. New post ID: 200Delete Translation
wp perflocale translations delete <post_id> --lang=<slug>Permanently deletes a translation post. Prompts for confirmation.
Example:
$ wp perflocale translations delete 42 --lang=fr
Are you sure you want to proceed? Delete fr translation (post 200)? [y/n] y
Success: Translation deleted (post 200).Set Language
wp perflocale translations set-language <post_id> --lang=<slug>Changes the language of an existing post. Fails if the language is already used by a sibling in the same translation group. When the language actually changes, the post’s terms in translatable taxonomies are swapped for their translations in the new language. A term with no translation in that language, or with no language of its own, is kept as it is.
Example:
$ wp perflocale translations set-language 42 --lang=en
Success: Post 42 language set to en.
$ wp perflocale translations set-language 42 --lang=fr
Error: Failed to set language. The language may already be used by a sibling translation.Point Translated Templates at Their Menu and Pattern Translations
wp perflocale template-refs-backfill [--batch-size=<number>]Runs the one-time update job again, in the WP-CLI process: every template and template part translated into a language other than the default one, whose Navigation block or synced pattern still names the original-language menu or pattern that its original names too, is pointed at that menu’s or pattern’s translation in its language, when the translation exists and the Site Editor can open it (a published or draft menu; a published, draft or pending pattern). A menu or pattern chosen in the Site Editor is kept, also a deliberate switch back to the original. What a visitor sees does not change: the page already shows the translation. The run is recorded as done for the site. See Translating menus in block themes.
Options:
--batch-size=<number>- Templates and template parts read per query. Default:50; a value outside 1 to 500 is set to the nearest limit.
Example:
$ wp perflocale template-refs-backfill
Success: 24 template(s) and template part(s) read, 3 updated, 4 reference(s) pointed at a translation.Exit status: 0 when it finished; 1 when the templates and template parts could not be read (a database error, or no default language), or when one could not be saved, for example because it was edited at the same time. The error names the template or part and the counts so far, and the run is then not recorded as done; run the command again.
Strings
Scan for Strings
wp perflocale strings scan [--domain=<domain>] [--dir=<dir>]Scans PHP files for translatable strings (__(), _e(), _x(), _n(), _nx(), _n_noop()).
Options:
--domain=<domain>- Only scan strings for a specific text domain.--dir=<dir>- Directory to scan. Default: active theme directory. (Named--dirbecause--pathis wp-cli’s own global WordPress-root flag and never reaches the command.)
Examples:
# Scan active theme
$ wp perflocale strings scan
Scanning /var/www/html/wp-content/themes/flavor/flavor...
Success: Found 342 strings, inserted 15 new.
# Scan a specific plugin
$ wp perflocale strings scan --dir=/var/www/html/wp-content/plugins/woocommerce --domain=woocommerce
Scanning /var/www/html/wp-content/plugins/woocommerce...
Success: Found 4821 strings, inserted 4821 new.Performance:
- Batched inserts (500 per batch) - handles large codebases.
- Skips files over 2 MB to prevent memory exhaustion.
- Excludes
vendor/,node_modules/,.git/,tests/,build/,dist/.
Slugs
Backfill Slug Translations
wp perflocale slugs backfillPopulates the slug translation table for all existing term translation groups. This ensures that translated taxonomy URLs work correctly.
When to run:
- After migrating from another multilingual plugin.
- After creating term translations manually.
- If taxonomy archive URLs show 404 errors.
Example:
$ wp perflocale slugs backfill
Success: Backfilled 48 slug translation entries.Verify Slug Translations
wp perflocale slugs verifyChecks if all terms in translation groups have corresponding slug translation entries.
Example:
$ wp perflocale slugs verify
Success: All term translations have slug entries.
$ wp perflocale slugs verify
Warning: 12 terms are missing slug translations. Run 'wp perflocale slugs backfill' to fix.Cache
Flush Caches
wp perflocale cache flushFlushes PerfLocale’s caches in all three cache layers, and only those:
- L1: In-memory static cache.
- L2: PerfLocale’s entries in the WordPress object cache (Redis/Memcached if available). The rest of a persistent object cache, other plugins’ data and WordPress’s own, is kept.
- L3: PerfLocale’s transients (wp_options table).
State that is not a cache is kept: notices still waiting to be shown to a user, circuit breakers and rate-limit windows. The Clear Cache button in the Cache panel of PerfLocale → Dashboard does the same.
Example:
$ wp perflocale cache flush
Success: All PerfLocale caches flushed.Migrate
Migrate from another multilingual plugin
wp perflocale migrate <source> [--dry-run] [--yes] [--force-restart]Imports translation data from another multilingual plugin into PerfLocale’s own tables. The source plugin’s data is not modified or deleted - the importer is read-only on the source side and writes new rows on the PerfLocale side.
Arguments:
<source>(required) - one ofwpml,polylang,translatepress.
Options:
--dry-run- confirm the source is detected and importable without writing anything. When the import would run despite problems, for example content in a language with no PerfLocale language, the dry run ends with a warning naming what it would skip instead ofSuccess:, for every source. Problems that would refuse the import end with an error (exit status 1).--yes- skip the interactive “Are you sure?” prompt (use for scripting / CI).--force-restart- clear the migration source-map for this importer before the run. Use only after a deliberate DB restore to a known-good pre-migration state when you want fresh translation_groups allocated rather than reusing mappings from the prior run. Running this on a database whose translation_groups already point at the source plugin’s data WILL orphan those groups. The default (omit) is the safer choice; see the migration disaster-recovery section for details.
Examples:
# Confirm WPML data is detected.
$ wp perflocale migrate wpml --dry-run
Dry run - checking wpml data...
Success: Wpml data detected and ready to import. Run without --dry-run to proceed.
# Run the real import, prompt for confirmation.
$ wp perflocale migrate wpml
Migrate translation data from wpml? This cannot be undone. [y/n] y
Migrating from wpml...
Success: Migration complete. Imported: 234 posts, 89 terms, 1412 strings.
# Scripted (no prompt).
$ wp perflocale migrate polylang --yes
Migrating from polylang...
Success: Migration complete. Imported: 128 posts, 34 terms.
# A Polylang import that left items out ends with a warning, not Success (exit status 0).
$ wp perflocale migrate polylang --yes
Migrating from polylang...
Warning: 2 Polylang items were not imported: their posts or terms no longer exist. For example: post 24 (fi), term 49 (sv).
Warning: Migration finished. Imported: 49 posts, 21 terms. Some Polylang items were not imported and need attention (see above).
# Operator workflow: restored a clean pre-migration backup, want a fresh
# allocation rather than reusing the prior run’s source-map mappings.
$ wp perflocale migrate wpml --force-restart --yes
--force-restart: cleared 234 source-map row(s) for wpml.
Migrating from wpml...
Success: Migration complete. Imported: 234 posts, 89 terms, 1412 strings.In scripts, check the exit status instead of looking for Success:: an import that finished but left items out ends with Warning: and exit status 0; one that was refused or did not finish ends with Error: and exit status 1. A Polylang import that could not read or write part of the data ends with “The import did not finish: some data could not be read or written (see the warnings above). What it imported is kept. Run it again to import the rest.”
Per-source guides: WPML • Polylang • TranslatePress. For the shared pre-flight steps and safety model, see the migration overview.
Memory tuning on very large sites: all three importers chunk their source-table SELECTs. The default is fine up to tens of thousands of posts (32 MB peak in our 5,000 group × 3 language benchmark). On tighter memory_limit hosts you can shrink the chunk size with perflocale/migration/wpml/batch_size, perflocale/migration/polylang/batch_size, or perflocale/migration/translatepress/batch_size filters.
WooCommerce
Find Language Copies Whose Stock Differs
wp perflocale stock-drift [--product=<ids>] [--fix] [--from=<from>] [--yes] [--batch-size=<number>] [--format=<format>]Inventory Sync keeps the language copies of a product as one stock. This reads every product translation group and lists the groups whose copies hold different stock quantities: products that manage their own stock, and each published or private variation with its matching variation in every other language. Managed stock that allows backorders is compared too. Copies that do not manage their stock, copies excluded from the sync and products in the Trash are left out. Without --fix it changes no stock, content or settings; reads in batches. On multisite it checks the site --url names. Needs WooCommerce, the WooCommerce add-on and its stock sync setting.
Options:
--product=<ids>- Only these products: IDs of any language copy, comma-separated, up to 500 (not variations).--fix- Not available in this version: the command answers “Aligning stock is not available in this version.” and exits with status 1.--from=<from>- Used with--fixonly.--yes- Used with--fixonly.--batch-size=<number>- Translation groups read per query, 1 to 500. Default:200.--format=<format>-table(default),csvorjson. An empty JSON result is[].
Columns: one row per copy in a group that differs, the copy the others are compared with included: group (that copy: the default-language product or variation, or the lowest ID when no copy is in the default language), product_id, parent_id (the variable product of a variation, 0 for a product), language and stock.
Examples:
# Every copy group whose stock differs
$ wp perflocale stock-drift
+-------+------------+-----------+----------+-------+
| group | product_id | parent_id | language | stock |
+-------+------------+-----------+----------+-------+
| 210 | 210 | 0 | en | 12 |
| 210 | 344 | 0 | es | 2 |
| 210 | 351 | 0 | de | 12 |
+-------+------------+-----------+----------+-------+
Warning: 1 copy group has stock that differs.
# As JSON, for a script (exit status 2 when something differs)
$ wp perflocale stock-drift --format=json
# Nothing differs
$ wp perflocale stock-drift
Success: No copy group has stock that differs.Exit status: 0 when no copies differ, 2 when copies differ (they are listed first), 1 when the command cannot run: “Inventory Sync is not active on this site (WooCommerce, the WooCommerce add-on and its stock sync setting).”, “--batch-size must be between 1 and 500.”, an unknown --format, or a database read that failed. A run that fails prints no rows.
Database Migrations
Run pending schema migrations
wp perflocale db-migrateRuns every pending PerfLocale database upgrade step at once, without waiting for the next automatic try, and checks that every table, column and index is in the database. Idempotent - safe to run multiple times. Useful in CI/CD pipelines or headless environments: without it, the upgrade runs on the first admin, REST API or WP-CLI request after an update, or in the background worker (see The database upgrade did not finish).
A step that fails is printed as a warning with the database’s error, and the command exits non-zero with “The PerfLocale upgrade did not finish. Fix the cause above, then run this command again.” While another request is running the upgrade, it stops with “Another request is running the PerfLocale upgrade. Run this command again in a few minutes.”
Example:
$ wp perflocale db-migrate
Success: PerfLocale database migrations completed successfully.On SQLite (WordPress Playground, and hosts that use the SQLite Database Integration), the command also runs the one-time repair of string translation groups to the end, which otherwise runs as a background job in small steps. It prints its progress, and the success line then says what it changed (“SQLite string groups repaired: …”). While the background job is working on the repair, the command stops with an error that says to run it again later.
Health Check
Run Database Integrity Checks
wp perflocale health-check [--fix]Runs ten database integrity checks. Without --fix it only reports and writes nothing.
Options:
--fix- Automatically fix found issues.
Checks performed, in this order:
- Post or term links replaced by a string import - imported links of a post or term group that name the ID of a string instead. Each is listed (the first 20) with its group, language, the ID it names, the reason and the group’s other members.
--fixremoves the ones that name no post or term (counted again under orphaned links); the others name an existing post or term, so--fixleaves them for you to check by hand. - Strings without their own translation group - strings whose group is missing, is not a string group, or is shared with another string, with the first 20 string IDs.
--fixgives each one its own group and moves its links there, one string at a time; a string whose change fails is rolled back and reported. Translation files are then compiled again when strings are served from files, andperflocale/strings/changedfires with the originhealth_check. - Orphaned post links - Translation links pointing to deleted posts.
- Orphaned term links - Translation links pointing to deleted terms.
- Invalid language references - Links referencing deleted languages.
- Widow translation groups - Non-string groups with zero remaining links. String-type groups are preserved (they are valid with no links until a translation is saved).
- Orphan string groups - String-type groups whose owning row in the strings table is gone.
- Groupless orphan links - Translation links whose parent group row no longer exists.
- String links without a translation - a string’s translation link in a language with no translation text, left when a save or a machine translation run stopped between its two writes. Reported only:
--fixleaves them; type the translation on the Strings screen, or run machine translation with skip existing. - Strings whose text disagrees with its hash -
--fixrepairs a string whose text was only entity-encoded. Text that was stripped cannot be rebuilt; the next string scan adds it again from the source.
The --fix flag removes the offending rows directly and then flushes all PerfLocale caches, so stale cache entries can't resurrect the deleted rows. On SQLite, while the one-time repair of string translation groups (see Database Migrations) has not finished, --fix leaves check 2 to it: “Not fixed: the SQLite string-group repair has not finished. Run wp perflocale db-migrate, then this check again.” When every check passes, the command ends with “All ten orphan, duplicate and string-integrity checks passed.”
Examples:
# Report only
$ wp perflocale health-check
Running PerfLocale health checks...
Warning: Post or term links replaced by a string import: 1
Post group #812, de: the link names #57 (no such post); the group's other members: #640.
--fix removes the ones that name no post or term (counted under orphaned links below).
Strings without their own translation group: 0 ✓
Warning: Orphaned post links: 1
Orphaned term links: 0 ✓
Invalid language references: 0 ✓
Warning: Widow translation groups (non-string): 2
Orphan string groups: 0 ✓
Orphan links (missing group): 0 ✓
String links without a translation: 0 ✓
Strings matching their hash: all ✓
Warning: Found 3 issues. Run with --fix to repair.
# Auto-fix
$ wp perflocale health-check --fix
Running PerfLocale health checks...
Post or term links replaced by a string import: 0 ✓
Warning: Strings without their own translation group: 2 (group missing: 2, not a string group: 0, shared: 0)
String ids: 101, 102.
Fixed: gave 2 string(s) their own translation group (0 link(s) moved, 1 link(s) added).
Orphaned post links: 0 ✓
Orphaned term links: 0 ✓
Invalid language references: 0 ✓
Warning: Widow translation groups (non-string): 2
Fixed: removed 2 widow groups.
Orphan string groups: 0 ✓
Orphan links (missing group): 0 ✓
String links without a translation: 0 ✓
Strings matching their hash: all ✓
Success: Fixed 4 issues.Status
Translation Completeness Overview
wp perflocale status [--format=<format>]Displays translation completeness per language.
Options:
--format=<format>- Output format:table(default),csv,json,yaml,count.
Example:
$ wp perflocale status
+--------------+-----------+------------+-------+---------+--------------+-------+-------+
| Language | Published | Translated | Draft | Pending | Needs Update | Empty | Total |
+--------------+-----------+------------+-------+---------+--------------+-------+-------+
| English (en) | 12 | 0 | 2 | 0 | 0 | 0 | 14 |
| French (fr) | 14 | 0 | 0 | 0 | 0 | 0 | 14 |
| German (de) | 8 | 0 | 1 | 0 | 0 | 5 | 14 |
| Arabic (ar) | 3 | 0 | 0 | 0 | 0 | 11 | 14 |
+--------------+-----------+------------+-------+---------+--------------+-------+-------+
$ wp perflocale status --format=json
[{"Language":"English (en)","Published":12,...}]Export & Import
Export a single site
wp perflocale export <file>Writes a single JSON envelope with the current site’s PerfLocale settings and translation data - every section of the export screen: settings, roles & capabilities, languages, translation links, string translations, slug translations, content hashes, media, menus, item flags and add-on data (see Export & Import). The envelope embeds a format_version field so importers refuse payloads written by a newer format. Settings whose names mark them as credentials (API keys, tokens, secrets, passwords and the like, in the main settings and in each add-on’s settings) are left out, so a backup never carries API keys.
$ wp perflocale export /tmp/perflocale-backup.json
Success: Exported to /tmp/perflocale-backup.json (1433.6 KB).Import into a single site
wp perflocale import <file> [--mode=<mode>] [--yes] [--force] [--addon-data=<choices>] [--batch-size=<rows>]Options:
--mode=merge(default) - additive; existing rows are kept, new rows are inserted. A row that points at a parent the same bundle carries is re-pointed at the row that parent actually landed on. Post and term IDs are never re-mapped - see what travels between sites. Merge never changes this site’s configuration: when the file carries settings, add-on settings, the add-on list or roles, they are counted as skipped and the command printsWarning: Settings, add-on settings, the add-on list and roles in the file were not applied: merge mode keeps this site's configuration. Run the import with --mode=replace to apply them (a file that carries only those sections deletes no rows).--mode=replace- clears this site’s rows for every section the file declares (scoped to the current site on multisite) and then writes the envelope verbatim, applying the file’s settings, add-on settings, add-on list and roles. This site’s own domains, provider addresses, uninstall choice and paused queue are kept, and a warning names any whose value in the file differed (see Merge vs. replace). A file that carries only the Settings and Roles & Capabilities sections owns no table, so it deletes no rows. Asks “Replace mode deletes this site's PerfLocale rows for every section the file declares, then loads the file. Continue?” first.--yes- skip the confirmation prompt of--mode=replace.--force- import a bundle that is refused because its recorded site address is not this site’s (see Import refused). Use it only when this site is a copy of the exporting one and the post and term IDs still match (a staging clone, a domain migration, or this site’s own backup that recorded a different address). A file that records no address is not checked; one whose recorded address cannot be read counts as a mismatch. With--mode=replacea foreign bundle is refused when it declares any section other than Settings or Roles & Capabilities, carrying rows for it or not, because replace deletes this site’s rows for those sections first. The command exits non-zero on a refusal. Same meaning asnetwork-import --force.--addon-data=<choices>- the add-on data to import, as the Add-on data to import checkboxes on the import screen choose it: a comma-separated list ofaddon_data(the Add-on data checkbox) and the keys of the add-ons’ own checkboxes (<owner>/<group>, as the file’saddon_groupsnames them), ornone. Default: all the add-on data the file carries. Add-on data whose add-on is not active on this site is skipped and named in a warning. Any other value stops the command with “Import failed: Unknown --addon-data value: …”.--batch-size=<rows>- rows the importer reads and checks per piece, a whole number from 50 to 5000, for this run, in place of theperflocale/import/batch_sizefilter (default 500). Larger pieces need fewer look-up queries; smaller ones keep peak memory lower. Any other value stops the command with “Invalid --batch-size: use a whole number from 50 to 5000.”
# Restore from backup, additive
wp perflocale import /tmp/perflocale-backup.json
# Replace this site’s rows for every section the envelope declares
wp perflocale import /tmp/perflocale-backup.json --mode=replace --yes
# This site is a copy of the site the file came from
wp perflocale import /tmp/staging.json --force
# Only the add-on data of the shared Add-on data checkbox
wp perflocale import /tmp/perflocale-backup.json --addon-data=addon_data
# Larger pieces on a site with plenty of memory
wp perflocale import /tmp/perflocale-backup.json --batch-size=2000Output. What the import left out, kept or changed on purpose is printed as one Warning: line per note, for example the settings Replace kept or the rows it skipped with their reason. The import ends with Success: Import complete. Imported: N, Skipped: N., or with Error: Import finished with N error(s). Imported: N, Skipped: N. after one warning per error. Only one data import runs on a site at a time: while another one runs, the import is refused with “Import refused: another import is running on this site. Wait for it to finish (PerfLocale → Jobs lists background imports), then import the file again.”
Exit code. import, po-import and network-import exit non-zero when the import reports errors, so a restore script can rely on the exit code alone - a malformed file ends in an error, not Success:. Ordinary duplicate-key merge skips are not errors and do not affect the exit code.
Replace is all-or-nothing. The wipe and the reload run inside one transaction: any row the database refuses rolls the whole import back. The settings, add-on settings, disabled-add-on list and role grants the file carries are applied only after the tables are committed, so a failed import changes none of them. You keep your pre-import state and get an error naming the table, and the row when it can be named.
Stored exactly as the file has it. The command restores string translations, languages and translated slugs byte for byte, whichever user it runs as. That differs from the import form on Settings → Export & Import, which cleans those values for an account without the unfiltered_html capability (Export & Import).
Export string translations as PO
wp perflocale po-export <file> --lang=<slug> [--domain=<domain>]Writes a gettext PO file for one language. Optional --domain filters to a single text domain (default: all domains).
Each plural string is written as one gettext plural entry with every form the language has, so po-import of the file restores all forms. When the export cannot pair a plural text with its singular, or the language has fewer plural forms than a string stores, the command warns before its success line: “2 plural string(s) have plural forms that are not in this file: their singular string could not be identified, or the language has fewer plural forms. A --replace import of this file removes those forms; a merge import keeps them. First ids: 418, 422”.
$ wp perflocale po-export /tmp/fr.po --lang=fr
Success: PO exported to /tmp/fr.po (58.3 KB).
$ wp perflocale po-export /tmp/fr-myplugin.po --lang=fr --domain=my-pluginImport string translations from PO
wp perflocale po-import <file> --lang=<slug> [--replace]Options:
--replace- wipes every existing translation for this language first (in one transaction), then imports the file; msgids absent from the PO file end up untranslated. Without it, the import is an upsert: new msgids are inserted and existing msgids are updated with the file's translation.
Robust to \r\n line endings, and skips fuzzy and obsolete entries. A file with nothing it can import - every translation marked fuzzy, or only extra plural forms (msgstr[2] and up) - is refused with an error, the command exits non-zero, and nothing is changed. Translations are stored byte for byte, whichever user the command runs as; the Import PO button on PerfLocale → Strings is the path that filters HTML for an account without unfiltered_html (String Translation).
wp perflocale po-import /tmp/fr.po --lang=fr
wp perflocale po-import /tmp/fr.po --lang=fr --replaceExport a multisite network
wp perflocale network-export <file> [--include-inactive]Multisite only. Walks every site in the network and writes a single envelope keyed by blog ID, marked network: true. Inactive sites are skipped unless --include-inactive is passed.
wp perflocale network-export /tmp/network.json
wp perflocale network-export /tmp/network.json --include-inactiveImport a multisite network
wp perflocale network-import <file> [--mode=<mode>] [--yes] [--site=<id>] [--force] [--batch-size=<rows>]Options:
--mode=merge(default) or--mode=replace- same semantics as single-siteimport, applied per site. In merge mode the warning about settings, add-on settings, the add-on list and roles is printed per site, prefixed with the source site’s ID. Every note and error of the import is prefixed the same way (Warning: Site <id>: …).--yes- skip the confirmation prompt of--mode=replace(“Replace mode will WIPE matching data on every targeted site. Continue?”), for scripted runs.--site=<id>- pick one sourcesite_idslice; the current blog receives the data. Required when loading a network envelope outside multisite.--force- import a slice even when its recordedsite_urldoesn't match the target blog (by default mismatched slices are skipped with a warning). Use after a domain migration when the blog IDs are known-correct. Addresses compare as host, port and path with the scheme ignored, against the target blog’shomeoption, withhome_url()accepted as well: an http-to-https move still matches, two installs that differ only by port do not.--batch-size=<rows>- rows the importer reads and checks per piece, from 50 to 5000, for this run; as forimport.
The file is read in pieces, slice by slice, so its size does not decide the memory the import needs. The limits of perflocale/import/quality_limits apply as they do to import.
# Across the whole network (multisite), without the prompt
wp perflocale network-import /tmp/network.json --mode=replace --yes
# Restore one slice on a non-multisite target
wp perflocale network-import /tmp/network.json --site=42Addons
Inspection-focused subcommands for the Addon System. list, info, orphans, doctor, and errors (without --clear) are read-only; migrate, reset-version, enable, disable, and reset-quarantine write. There is no uninstall subcommand by design.
List Addons
wp perflocale addon list [--format=<format>] [--fields=<fields>] [--all]Options:
--format=table|json|csv|yaml|ids|count(default:table).--fields=<comma-separated>- pick specific columns. Available:id, name, bundled, booted, disabled, compatible, has_schema, schema_target, schema_stored, has_uninstall, has_custom, manifest. Default: all.--all- include orphan manifests (addons whose class is gone but whose manifest is still in the database).
The default table view exposes disabled and booted alongside bundled so you can see lifecycle state in one place without running doctor separately.
wp perflocale addon list
wp perflocale addon list --format=json --all
wp perflocale addon list --fields=id,disabled --format=csv
wp perflocale addon list --format=ids # plain id list for shell loopsInspect a Single Addon
wp perflocale addon info <id> [--format=<format>]Shows the addon's full manifest plus a live PurgePlan preview (counts only - no DB mutation). Use this to verify that a custom addon's tables, options, and capabilities are namespaced correctly before relying on the uninstall contract.
wp perflocale addon info my-addon
wp perflocale addon info my-addon --format=jsonList Orphans
wp perflocale addon orphansLists addon IDs that still have a manifest row but whose registering class is no longer loaded - typically because the addon's plugin was deactivated or deleted before PerfLocale itself was uninstalled.
Run Addon Migrations
wp perflocale addon migrate [<id>] [--force]Options:
<id>- optional; migrate just this addon (default: all).--force- reset the stored schema version to0and re-run every migration step. Dangerous.
wp perflocale addon migrate
wp perflocale addon migrate my-addon
wp perflocale addon migrate my-addon --forceMigration / Uninstall Errors
wp perflocale addon errors [<id>] [--clear]Tail the addon migration / uninstall error log. Pass an addon ID to filter, or --clear to empty the log (optionally for one addon).
wp perflocale addon errors
wp perflocale addon errors my-addon
wp perflocale addon errors --clear
wp perflocale addon errors my-addon --clearReset Stored Schema Version
wp perflocale addon reset-version <id> <version> [--yes]Manual recovery only - sets the stored schema version for one addon to a specific value. Setting it lower than the addon's current target causes the next migration pass to re-run every step in between. Confirms before writing unless --yes is passed.
wp perflocale addon reset-version my-addon 0
wp perflocale addon reset-version my-addon 2 --yesHealth Summary
wp perflocale addon doctor [--format=<format>]One-shot operator summary: counts of booted / disabled / quarantined / version-mismatched addons in a single table with the affected IDs. The first command to run when an addon isn't behaving as expected.
wp perflocale addon doctor
wp perflocale addon doctor --format=jsonEnable / Disable
wp perflocale addon enable <id>
wp perflocale addon disable <id>Mirror of the per-card Enable / Disable button on the Addons admin page. Writes the addon ID into the perflocale_disabled_addons option and flushes the bootable-addons transient so the change applies on the very next request. Works for both bundled (in-plugin) and external addons - disabling a bundled addon takes effect immediately, so only disable one if you genuinely don't want that integration.
wp perflocale addon disable my-addon
wp perflocale addon disable woocommerce
wp perflocale addon enable my-addonRead / Write Addon Settings
wp perflocale addon settings get <addon-id> <key> [--default=<val>] [--format=scalar|json]
wp perflocale addon settings set <addon-id> <key> <value> [--type=string|bool|int|float|json]
wp perflocale addon settings list <addon-id> [--format=table|json|csv|yaml]Get, set, and list values for an addon’s settings without going through the admin UI. settings set coerces the value to the supplied --type (default string) and returns an error if the addon’s entry would exceed the 16 KiB per-addon cap.
'storage' => 'global' fields. Some addons (the bundled WooCommerce addon is the canonical example) keep their settings in the main perflocale_settings option instead of in the addon-storage option, because the runtime + REST APIs read from there. For those fields the CLI does the right thing automatically:
settings gettransparently reads fromperflocale_settings, so the value you see is the value the runtime actually uses.settings setwrites throughSettings::update()rather than into addon storage, so the full invalidation chain runs (config-cache flush, cron reschedules, settings-updated listeners). Prefer it overwp option patch update perflocale_settings <key> <value>, which changes the stored value but leaves all of that stale. If a sanitizer coerces the value - or an environment variable / PHP constant overrides it - the write still happens and the command warns you with the effective value.- A
settings setof a global-storage field that is not saved stops with an error that names the key and the cause: “<key> not saved: another settings save was running. Run the command again.”, “<key> not saved: the database refused to store the option perflocale_settings, so the value may be too long or hold characters it cannot store. Check the value, then run the command again.” (with a line on where the database error is) or “<key> not saved: a filter on the option perflocale_settings kept the stored value.” A newmt_agency_urlmust start withhttps://, or be anhttp://address on a host added to theperflocale/mt/trusted_hostsfilter (External agency). - An add-on-storage
settings setthat is not saved says why: “Could not save '<addon>.<key>': another add-on settings save was running. Run the command again.”, that the add-on’s settings would be larger than the size stored per add-on, that the database refused the optionperflocale_addon_settings, or that a filter on it kept the stored value. settings listshows addon-storage AND global-storage fields together with astoragecolumn on every row, so you can see exactly where each setting lives.
# Addon-storage field (lives in perflocale_addon_settings):
wp perflocale addon settings get my-addon batch_size
wp perflocale addon settings set my-addon batch_size 50 --type=int
wp perflocale addon settings set my-addon limits '{"max":100}' --type=json
# Global-storage field (lives in perflocale_settings):
wp perflocale addon settings get woocommerce wc_email_translation # reads live value
wp perflocale addon settings set woocommerce wc_email_translation false --type=bool
# → Success: Saved wc_email_translation to the global perflocale_settings option.
# settings list shows both kinds, with a `storage` column:
wp perflocale addon settings list woocommerce
# key value storage
# wc_email_translation true global
# wc_sync_stock true global
# ...Reset Failure Quarantine
wp perflocale addon reset-quarantine <id>Clear an addon's failure counter after the underlying boot error has been fixed. Quarantined addons (3 consecutive boot failures) are otherwise skipped until manually cleared - this is the manual-clear path.
wp perflocale addon reset-quarantine my-addonBackground Jobs
Inspect and control the background-processing queue from the command line. See the Background Jobs doc for the full feature reference.
Namespace: wp perflocale jobs
List Jobs
wp perflocale jobs list [--status=<status>] [--format=<format>]Lists all jobs in the active-jobs index.
Options:
--status=<status>- Filter by status:queued,running,complete,failed,canceled.--format=<format>- Output format:table(default),json,csv,yaml.
Example:
$ wp perflocale jobs list
+--------------------------------------+--------------+----------+----------+--------------------+
| id | type | status | progress | updated_at |
+--------------------------------------+--------------+----------+----------+--------------------+
| 5fc94964-29f6-4697-8c4e-64a8ffb5c72d | string_scan | running | 47% | 12 seconds ago |
| 9d291e3c-3a41-47ab-b3e6-3ba00e88e55a | data_import | queued | 0% | 8 seconds ago |
+--------------------------------------+--------------+----------+----------+--------------------+
$ wp perflocale jobs list --status=failed --format=json
[{"id":"...","type":"bulk_translate","status":"failed",...}]Get Job Details
wp perflocale jobs get <id> [--format=<format>]Prints the full state of one job: type, hook, engine, status, progress, args, log ring buffer, error message, result payload, attempts, created_at / completed_at / updated_at, created_by user id.
Arguments:
<id>(required) - Job UUID (visible injobs list).
Options:
--format=<format>- Defaultyaml(one key:value per line);jsonfor machine-readable.
Cancel a Job
wp perflocale jobs cancel <id>Cancels a queued or running job. Sets status to canceled and unschedules the worker event. A worker that's currently mid-execute() aborts cooperatively on its next progress callback - the delay until that happens depends on the job's batch size (most jobs tick every batch, so the lag is usually brief, but a large batch can hold it open for a few seconds). Very short jobs that complete before any progress tick may still finish naturally.
Refuses jobs that are already complete / failed / canceled with a warning. Canceling an XLIFF import also deletes its stored file.
Retry a Job
wp perflocale jobs retry <id>Re-enqueues a failed or canceled job. Resets status to queued, clears the error and result fields, schedules a new worker event with the original args + hook.
Refuses non-terminal jobs (queued / running / complete) with a warning.
Run a Job in WP-CLI
wp perflocale jobs run <id>Runs one job in this WP-CLI process until it ends, without a web request’s time limit: a job that runs in slices, such as a bulk translation or an XLIFF import, runs each slice here, with no time or item limit by default. A failed or canceled job is retried first. Each slice and each retry starts at the time the worker planned for it, for example after a wait a translation provider asked for, and a line such as Job 5fc94964-…: 120 of 500 done; next run in 0 s. is printed between them. The job runs with its owner’s rights, as in any worker.
While the command runs, WP-Cron and Action Scheduler leave the job to it, so DISABLE_WP_CRON is not needed. Should the process stop, the job goes back to them within 5 minutes. The command ends with success when the job completes, and with an error when it fails, is canceled, or is taken over by another worker. It refuses a job that is already complete, a job another wp perflocale jobs run holds (“Wait for it to end; its hold lapses 300 seconds after it stops.”), and a paused queue (“The job queue is paused. Unpause it first: wp perflocale jobs unpause”). When the queue is paused while it runs, the command stops with an error and the job keeps its place.
$ wp perflocale jobs run 5fc94964-29f6-4697-8c4e-64a8ffb5c72d
Success: Job 5fc94964-29f6-4697-8c4e-64a8ffb5c72d complete.Delete a Job
wp perflocale jobs delete <id>Permanently removes a job from the active-jobs index, deletes its per-job state row, and deletes the export file the job produced, or the stored file of a failed XLIFF import, if it still exists. Only allowed on jobs that have already finished (complete, failed, canceled) - running/queued jobs must be canceled first so the runner has a chance to unschedule cleanly. Matches the same restriction as the REST DELETE /jobs/{id} endpoint.
Pause / Unpause the Queue
wp perflocale jobs pause
wp perflocale jobs unpauseToggles the background_paused setting. While paused, new dispatches are accepted but workers re-queue them every 5 minutes (configurable) instead of running them, and a job that is already running stops at its next progress step and is re-queued the same way (see Pause Queue). Operator brake when something is mis-dispatching.
Run Garbage Collection
wp perflocale jobs gcRuns the daily GC sweep right now without waiting for the cron tick. Reports how many jobs were pruned. Performs:
- Stuck-job sweep - queued/running jobs with no
updated_atbump in 6+ hours are marked failed. - Terminal-state prune - complete/failed/canceled jobs older than 24h are deleted.
- Stale lock sweep -
perflocale_job_lock_*andperflocale_type_lock_*rows with past expiry are deleted.
Resume Surviving Jobs
wp perflocale jobs resumeManually trigger the post-reactivation resume sweep that normally runs automatically on activation. Walks the active-jobs index, finds queued/running rows that have no scheduled worker event, and re-enqueues each one. Useful if the auto-scheduled perflocale_resume_jobs cron event was lost (e.g. DISABLE_WP_CRON + no external trigger between activate and the operator noticing). Reports the count re-enqueued.
CLI runs synchronously - admin honors thresholds
The Background Thresholds setting in Settings → Performance only gates admin-triggered operations: when an operator clicks Scan, Import, Migrate, etc., or when an external client hits the REST endpoint, the dispatcher compares the work size against the threshold and routes large operations through the queue.
CLI commands deliberately bypass that gate and run the work inline in the foreground, regardless of size. This matches Unix conventions for command-line tools: progress streams to stdout, errors stream to stderr, exit code reflects success, and chained scripts (wp ... && next-step) behave as expected. Deploy scripts, CI/CD pipelines, and ssh-only servers all rely on this synchronous behavior.
Operations affected: strings scan, translate, export, import, po-import, migrate <plugin>. They will not queue jobs even on very large workloads - you'll see the work happen live and the command will return when it's done. The one explicit opt-out is translate --all --async, which dispatches a resumable site_translate job and returns immediately.
If you need to dispatch a large operation through the queue from a CLI context (e.g. a server cron that should kick off a job and exit immediately so it doesn't tie up the cron worker), use the dispatcher directly:
wp eval '\PerfLocale\Background\Dispatcher::dispatch(
new \PerfLocale\Background\Jobs\BulkTranslateJob(),
[ "source_ids" => [ 1, 2, 3 ], "target_lang_ids" => [ 2 ] ]
);'That returns a job UUID immediately and exits; the queued job runs on the next worker tick (WP-Cron or Action Scheduler, whichever is active).
Circuit Breakers
Inspect and manage PerfLocale's circuit breakers (MT providers, webhooks, FX APIs, geo-IP services). Full subsystem reference: Reliability & Circuit Breakers.
List Breakers
wp perflocale breakers list [--format=<format>]Options:
--format-table(default),json,csv,yaml,count. Machine-readable formats always emit valid parseable output, even when no breakers are tripped.
Examples:
$ wp perflocale breakers list
Success: No active breakers - system healthy.
$ wp perflocale breakers list --format=json | jq '.[] | select(.state=="open") | .key'
"mt_deepl"
"webhook_550e8400-e29b"
$ wp perflocale breakers list --format=count
0Status of One Breaker
wp perflocale breakers status <key> [--format=<format>]Returns the full state (state, failures, reason, first/last/opened timestamps, cooldown remaining) for a single breaker. Returns the initial "closed" shape if the key has never tripped.
$ wp perflocale breakers status mt_deepl
+---------------------+---------------------+
| field | value |
+---------------------+---------------------+
| key | mt_deepl |
| state | open |
| failures | 5 |
| reason | 401_invalid_key |
| opened_at | 2026-05-26 10:42:18 |
| cooldown_remaining | 271s |
+---------------------+---------------------+Reset a Breaker
wp perflocale breakers reset <key>
wp perflocale breakers reset --allForce-closes one breaker (or all of them) immediately. The next call to the underlying service is allowed through; subsequent failures will re-trip the breaker on the normal threshold. Useful when an operator has fixed the upstream problem (e.g. rotated an API key) and doesn't want to wait the full cooldown.
$ wp perflocale breakers reset mt_deepl
Success: Breaker 'mt_deepl' was OPEN and is now closed.
$ wp perflocale breakers reset --all
Success: Reset 3 breaker(s).Scripting Tips
Suppress Confirmation Prompts
Use --yes to skip all confirmation prompts (useful in scripts and CI):
wp perflocale languages delete fr --yes
wp perflocale translations delete 42 --lang=fr --yesCombine with WordPress CLI
# Translate all published pages to German
wp perflocale translate --all --to=de --post-type=page --skip-existing
# Check health and fix issues
wp perflocale health-check --fix
# Full maintenance routine
wp perflocale cache flush && wp perflocale slugs backfill && wp perflocale health-check --fixJSON Output for Parsing
# Get languages as JSON for scripting
wp perflocale languages list --format=json | jq '.[].Slug'
# Get translation status as JSON
wp perflocale status --format=json | jq '.[] | select(.Published < .Total)'