wp-cli/extension-command
WP-CLI extension-command provides wp plugin and wp theme commands for managing WordPress extensions from the command line. Install from wordpress.org, activate or deactivate, update, delete, and handle multisite network activation, with bulk actions, exclusions, and force reactivation.
This is not a Composer library you pull into a Laravel app — it's the WP-CLI command package that provides wp plugin and wp theme. You consume it through the wp binary, either the shipped phar, the wordpress:cli Docker image, or wp-cli/wp-cli-bundle as a dev dependency in a Bedrock-style WordPress repo. Your Laravel code touches it through deploy scripts, Envoy tasks, or a queued Process call.
Minimal setup:
# Local or container
composer require --dev wp-cli/wp-cli-bundle
vendor/bin/wp --info
# Docker (WordPress stack)
docker compose run --rm wpcli plugin list --allow-root
Always run against a known root, since WP-CLI walks up from cwd:
wp plugin list --path=/var/www/html --format=json
Look here first, in this order:
wp help plugin — the authoritative command reference. The README is generated from these docblocks, so the CLI help is always the current source of truth.wp plugin list — inventory: name, status, version, update, update_version, auto_update.wp plugin get <slug> --format=json — per-plugin detail, including requires_wp, requires_php, requires_plugins.First real use case, an audit you can run on any environment in one line:
wp plugin list --fields=name,status,version,update,update_version --format=table
wp plugin list --update=available --field=name # just what's outdated
That single command replaces clicking through wp-admin and is the foundation for everything below.
Inventory and reporting into your tooling. Never scrape the table output. --format=json plus --field= are the stable interfaces:
wp plugin list --status=active --field=name # newline list, pipe-friendly
wp plugin list --status=active --format=json | jq -r '.[].name'
wp plugin get woocommerce --field=version # single scalar
wp plugin list --format=csv --fields=name,version,update
From Laravel, run these with Symfony\Component\Process or Illuminate\Process, pass --format=json, and json_decode stdout. Keep stderr separate for error surfacing.
Idempotent provisioning. Exit codes make this clean — is-installed and is-active return 0/1:
wp plugin is-installed akismet || wp plugin install akismet --activate
wp plugin is-active akismet || wp plugin activate akismet
Wrap it in a bash function or an Envoy macro and your environment bootstrap (Docker entrypoint, Forge/Envoyer deploy hook, test fixture setup) becomes rerunnable without a "already installed" wall of errors.
Deploy artifact installs. Build the plugin as a zip in CI, then install the artifact rather than pulling from wordpress.org:
wp plugin install ./build/my-plugin.zip --force --activate
Use --slug=my-plugin when the archive's top-level folder name doesn't match the slug you want in wp-content/plugins. Note this option cannot be combined with multiple plugins in one invocation.
Bulk maintenance, especially on multisite.
wp plugin update --all --minor # patch/minor only, skip majors
wp plugin update --all --dry-run # see what would change first
wp plugin activate $(wp plugin list --recently-active --field=name)
wp plugin activate bbpress --network # network-wide
wp plugin list --status=active-network
--recently-active is the useful trick for restoring state after a "deactivate everything to bisect a bug" session — it ignores the status filter and returns what was active before.
Dependency handling. Since plugin headers now declare Requires Plugins, let the command resolve it:
wp plugin install my-plugin --with-dependencies --activate
wp plugin install-dependencies my-plugin --activate # plugin already present
State restoration after config changes. wp plugin activate <slug> --force deactivates and reactivates to re-run activation hooks — the CLI equivalent of "deactivate/reactivate to flush rewrite rules," useful after changing permalinks or registering CPTs in a deploy.
CI caching. Downloads land in ~/.wp-cli/cache (or WP_CLI_CACHE_DIR). Cache that directory between CI runs and repeated wp plugin install calls become near-instant:
- uses: actions/cache@v4
with:
path: ~/.wp-cli/cache
key: wp-cli-${{ hashFiles('composer.lock') }}
Composer-managed plugins (Bedrock). If composer.json owns plugin versions, do not wp plugin install on deploy — it desyncs the lockfile and gets wiped by the next composer install. Restrict WP-CLI to state operations (activate, deactivate, list as verification) and let Composer handle installation.
Theme parity. Everything above has a wp theme equivalent (wp theme install, wp theme activate, wp theme list, wp theme update), so one wrapper class can serve both.
wp plugin delete does not deactivate or uninstall. It removes files only, potentially leaving active-plugin entries and orphaned options. The order you usually want is deactivate --uninstall, then delete.set -e or Laravel's Process::throw(), wp plugin is-active foo will abort your script whenever the plugin is inactive. Use explicit conditionals, or || true with a captured code.--all with --exclude is comma-separated and slug-based. wp plugin deactivate --all --exclude=hello,wordpress-seo. Easy to get wrong by passing space-separated names.hello vs hello/hello.php. Most commands take the slug; if a command misbehaves, check wp plugin get <slug> --field=file for the canonical relative path.list but can't be toggled. --status=must-use and --status=dropin filter for them. Dropins (e.g. object-cache.php) require --status=dropin to appear; activation commands will not touch them.--url. Running --network on a single site errors; running per-site operations on multisite without --url=https://subsite.example.com operates on the wrong option set. Network-active plugins show as active-network, not active, so a naive --status=active check will lie to you.wp plugin list calls wordpress.org for every plugin. In scripts where you only need status, add --skip-update-check. Symptom: a list call taking 10–30 seconds on a machine with a blocked outbound connection.--force overwrites local modifications. Never blanket wp plugin install $(wp plugin list --field=name) --force on an environment with hotfixed plugins — you silently delete the patch. Use it for artifact-based deploys, not for "update everything."--version=1.2.3 or an artifact install keeps resolving to an old file, clear it: wp cli cache clear, or delete ~/.wp-cli/cache.wp-envato-market-master) is normalized on install; if the resulting directory is wrong, pin it with --slug. Expect this when installing from archive/master.zip URLs.NO_COLOR=1 and WP_CLI_QUIET=1, and prefer --format=json over --format=table. Always parse stdout, and treat non-empty stderr as a warning channel, not necessarily a failure.--allow-root in containers, but watch file ownership. Running wp plugin install as root creates files the www-data user cannot update later. Match UIDs, or chown after install.wp <command> --debug shows the underlying HTTP requests, archive extraction paths, and PHP errors. This is the first thing to reach for when an install "succeeds" but the plugin isn't listed.--insecure is a last resort. It exists for TLS handshake failures in odd CI images and disables certificate validation entirely.--version=2.6.9 for a specific release, --version=dev for trunk from wordpress.org. Pin versions in seed and fixture scripts so test environments are reproducible.wp scaffold package-readme). Editing it directly is futile — change the command docblock and regenerate, and wp help <command> is where users actually find the option list.wp plugin update --all only handles wordpress.org-hosted extensions; anything else needs the zip/URL install path with --force.How can I help you explore Laravel packages today?