git:20261005.6d59fc7 to git:20261007.180e907

95 added, 79 removed. Audit A to A.

---
name: govard-wordpress
description: |
This skill should be used when the user asks to "clear WordPress cache", "run wp cli", "run wp commands", "flush rewrite rules", "manage WordPress plugins via govard", "wordpress wp-config", "audit WordPress", "lint WordPress", "govard audit", "deploy WordPress", or "govard deploy". Provides WordPress-specific Govard shortcuts. DEPENDENT on govard-toolbox for base commands.
compatibility: claude, codex, opencode, copilot, dsh
depends: [govard-toolbox, php-dev-core]
metadata:
audience: developers
workflow: wordpress
---
# Govard WordPress Commands
WordPress-specific shortcuts and commands for Govard environments.
## Related Skills
- **REQUIRED BACKGROUND:** Load `govard-toolbox` first — this skill only covers WordPress-specific shortcuts layered on top of Govard's base environment commands (`govard up`, `govard sh`, `govard db`, remote sync, Xdebug setup).
-
- **Docker requirement:** stack commands here (`govard up/down/sh`, `govard db`,
- `govard tool ...`) need Docker. On a host without it they exit `3` with
- `CAPABILITY_MISSING`; `govard audit run --checks integrity` still works and
- covers manifest/lock and Magento module/DI checks without a container.
-
+ **REQUIRED BACKGROUND:** Load `govard-toolbox` first. This skill only covers WordPress-specific behavior layered on top of Govard's base environment commands (`govard up`, `govard sh`, `govard db`, remote sync, Xdebug setup). For generic PHP (strict_types, PSR-12, PHPStan, security) see `php-dev-core`.
- For generic PHP (strict_types/PSR-12/PHPStan/Security) see php-dev-core.
+ **Docker requirement:** stack commands here need Docker. Check what a command needs with `govard capabilities`. Unlike some frameworks, WordPress has **no** container-free `govard audit run --checks integrity` (it fails with "does not support integrity audit").
## WP-CLI
- WP-CLI (`wp`) runs inside the PHP container via Govard:
+ `wp` runs inside the PHP container. `govard up` installs it when missing.
```bash
- # Any wp command
govard tool wp --info
govard tool wp core version
govard tool wp plugin list
- # Alternative: open shell
+ # Alternative: open a shell, then run wp directly
govard sh
wp --info
```
- Govard default for WordPress (verified 2026-08-28 `govard-test-wordpress`): `stack.php_version 8.0` (PHP 8.0.30), `web_root /`, `db mariadb 10.6`. Verify with `govard config get stack.php_version`. Note: request `--framework-version 6` currently installs WP 7.1 (latest); PHP/DB versions are template defaults and may differ per bootstrap.
+ Most `wp` commands that touch the database (`plugin list`, `theme list`, `cache flush`, `rewrite flush`) fail with "The site you have requested is not installed" until the site is installed. A checkout brought up with `govard up` has `wp-config.php` wired to the Govard database but an empty database, so installing is your step, not a red flag:
- Bedrock layout `web/wp/wp-includes/version.php` is auto-detected via `web/wp/wp-includes/version.php` (see `govard-toolbox` for provider details).
+ ```bash
+ govard tool wp core install --url=https://<project-domain> --title="Local" --admin_user=admin --admin_email=admin@example.com
+ govard tool wp core is-installed && govard db query "SHOW TABLES"
+ ```
+ (`govard bootstrap --fresh` runs the install itself.) Read the project's runtime values instead of assuming them:
+
+ ```bash
+ govard config get stack.php_version
+ govard config get stack.db_version
+ govard config get domain
+ ```
+
+ `govard bootstrap --framework-version <v>` is **pinned, not "latest"**: a bare `6` is normalised to `6.0` and that exact archive is downloaded from wordpress.org. Omit the flag to track latest. Bedrock layout (`web/wp/wp-includes/version.php`) is auto-detected (see `govard-toolbox` for provider details).
+
## Audit
- For generic PHP (strict_types/PSR-12/PHPStan/Security) see `php-dev-core`. For 4-framework matrix and `govard audit run --checks lint --lint-provider govard --mode project --format json` see `govard-toolbox` ## Audit.
+ Lint is the supported check: `govard audit run --checks lint --lint-provider govard --mode project --format json` (see `govard-toolbox` ## Audit for the cross-framework matrix). On WordPress:
+ - `--checks integrity` and `--checks profiler` are **not supported** (exit 1, "framework wordpress does not support ... audit").
+ - With Xdebug enabled (the default in many `.govard.yml` files) audit refuses to run: pass `--allow-xdebug` or run `govard config set stack.features.xdebug false` first.
+ - Linting a full WordPress tree can take minutes and exits 1 when it reports findings; scope with `--scope diff --base <ref>` for day-to-day use.
+
## Cache Management
```bash
- # Object cache flush (requires installed site; object-cache drop-in else no-op)
- govard tool wp cache flush # fresh DB without install returns "The site you have requested is not installed"
+ # Object cache (needs an installed site; a no-op without an object-cache drop-in)
+ govard tool wp cache flush
# Transients
govard tool wp transient delete --all
- # If using a caching plugin (e.g. WP Rocket, W3 Total Cache)
+ # Caching plugins expose their own wp subcommands; they only work once the plugin is installed
govard tool wp rocket clean --confirm
```
- Verified 2026-08-28: `cache flush` succeeds after `wp core install` (`Success: The cache was flushed`), fails pre-install.
-
- No Govard-level `frontend_sync` for WordPress — use `govard tool npm run watch` if the theme uses a build step.
+ There is no Govard-level `frontend_sync` for WordPress; use `govard tool npm run watch` if the theme has a build step.
## Plugin & Theme Management
```bash
- # List — requires installed site (verified: fresh govard-test-wordpress shows akismet 5.7.2 + hello 1.7.2 after install, fails pre-install)
govard tool wp plugin list
govard tool wp theme list
- # Install / activate
govard tool wp plugin install query-monitor --activate
govard tool wp theme install twentytwentyfour --activate
- # Update
govard tool wp plugin update --all
govard tool wp theme update --all
-
- # Must-use plugins are in wp-content/mu-plugins (not managed by wp plugin)
```
+ Must-use plugins live in `wp-content/mu-plugins` and are not managed by `wp plugin`.
+
## Rewrite & Core
```bash
- # Flush rewrite rules (after CPT/taxonomy changes) — requires installed site; fresh install with empty DB warns "Rewrite rules are empty"
+ # After CPT/taxonomy changes. Warns "Rewrite rules are empty" until a permalink structure is set.
govard tool wp rewrite flush
govard tool wp rewrite list
- # Verify core checksums
- govard tool wp core verify-checksums
+ govard tool wp core verify-checksums # exits 1 if any core file was modified
- # Search-replace (after domain change, e.g. staging sync)
- govard tool wp search-replace 'https://staging.example.com' 'https://wordpress.test' --all-tables
+ # After a domain change (e.g. staging sync); try --dry-run first
+ govard tool wp search-replace 'https://staging.example.com' 'https://<project-domain>' --all-tables
+ ```
- # Fresh install — Govard bootstrap creates wp-config.php but leaves DB empty (verified 2026-08-28: govard-test-wordpress required manual install)
- govard tool wp core install --url=https://wordpress.test --title="Local" --admin_user=admin --admin_email=admin@example.com
- # After install: govard tool wp core is-installed && govard tool wp db query "SHOW TABLES" shows wp_* tables
+ `wp-config.php` is generated (DB credentials, fresh salts, proxy/HTTPS support) only when it is **missing** at bootstrap time. `govard up` does not rewrite an existing `wp-config.php` or regenerate its salts. For multisite, add `extra_domains` in `.govard.yml` for each site domain, then `govard env up`:
+
+ ```yaml
+ extra_domains:
+ - shop.<project-domain>
```
- Govard injects `wp-config.php` salts automatically on `govard env up`. For multisite (`WP_ALLOW_MULTISITE true`), add `extra_domains` in `.govard.yml` for each site domain.
+ `govard domain add|remove|list` manages the same list.
## Database
```bash
- # Via WP-CLI
- govard tool wp db query "SHOW TABLES"
- govard tool wp db export --add-drop-table
-
- # Via Govard DB layer (auto-handles table_prefix)
+ govard db info # connection details, never the password
govard db query "SHOW TABLES"
govard db query "SELECT option_value FROM wp_options WHERE option_name='siteurl'"
- # Prefix note: default $table_prefix is wp_ — if .govard.yml sets table_prefix, Govard's
- # privacy filters (--no-pii/--no-noise) target prefixed tables automatically
- govard db query "SELECT * FROM wp_users LIMIT 5"
- ```
-
- Import/export also works via `govard db import --file backup.sql --drop` and `govard db dump --no-noise -e staging`.
+ govard db dump --file backup.sql
+ govard db import --file backup.sql --drop # --drop recreates the database first
+ govard db dump --no-noise --no-pii -e staging # privacy-filtered dump from a remote
- ## wp-config.php & Environment
+ # Same thing through WP-CLI inside the container
+ govard tool wp db query "SHOW TABLES"
+ govard tool wp db export --add-drop-table
+ ```
- Govard auto-generates salts and wires DB credentials into `wp-config.php` on `govard env up`:
+ **Privacy filters need `table_prefix` set, and a remote ignores it.** `--no-noise` and `--no-pii` exclude WordPress tables by name (`users`, `usermeta`, `comments`, `commentmeta` for PII; a few cache/log tables for noise) with the project's `table_prefix` prepended. Govard does not read the prefix from `wp-config.php` for WordPress. On a **local** dump the filters match only after `govard config set table_prefix wp_`; without it `wp_users` is dumped in full. Against a **remote** (`db dump -e <remote>`, `sync -s <remote> --db`, `bootstrap`, `db import --stream-db`) the configured `table_prefix` is not applied at all for WordPress: the filters name the unprefixed tables (`users`, `comments`), match nothing, and `wp_users` and `wp_comments` come through even with the prefix set.
```bash
- # Inspect current Govard DB credentials
- govard db info
- govard config get stack.db_version
-
- # Show wp-config.php salts section
- govard shell -c "grep -A2 AUTH_KEY wp-config.php | head -10"
+ govard config set table_prefix wp_
+ govard config get table_prefix
```
- - Do not commit `wp-config.php` salts — Govard regenerates them if missing.
- - For staging/prod overrides use `GOVARD_ENV=staging govard env up` (loads `.govard.staging.yml`).
- - `extra_domains` example for additional vhosts:
+ So never trust `--no-pii` on a WordPress remote. Verify every "sanitized" dump before sharing it (`grep -c 'INSERT INTO `wp_users`'` must be 0), and prefer importing first, then dumping locally with the prefix set. `db dump -e <remote> --file <path>` writes `<path>` on the **remote**; add `--local` to stream the dump into the project's `var/` directory.
- ```yaml
- # .govard.yml
- extra_domains:
- - shop.wordpress.test
- ```
+ ## Environment
+ - Do not commit `wp-config.php` secrets.
+ - For staging/prod overrides use `GOVARD_ENV=staging govard env up` (loads `.govard.staging.yml`).
+ - Xdebug: `govard debug on|off|status` (`off` rewrites `.govard.yml` and re-runs `env up`).
+ - `govard shell -c "..."` / `govard sh -c "..."` run one command in the PHP container.
+
## Deployment
- WordPress ships a deploy recipe for the **classic layout only** — core files and `wp-content/` in the repository root. A Bedrock layout (core in `vendor/`, docroot `web/`) and a content-only checkout are not supported.
+ WordPress ships a deploy recipe for the **classic layout only**: core files and `wp-content/` in the repository root. A Bedrock layout (core in `vendor/`, docroot `web/`) and a content-only checkout are not supported.
```bash
- govard deploy plan production # the resolved pipeline — no connection, no Docker
- govard deploy check production # preflight over ssh
- govard deploy production --yes
+ govard deploy plan production --branch main # resolved pipeline: no connection, no Docker
+ govard deploy check production --branch main # preflight over ssh
+ govard deploy production --branch main --yes
```
+ The remote needs a branch (`--branch`, `--revision`, `--tag`, or a configured `branch`) for `plan`, and a `deploy_path` (or an existing deploy layout) for `check`/`status`; otherwise they exit `4`. Add `--json` to `plan` for machine-readable output. See `govard deploy --help` for every flag.
+
| Step | Command on the target |
|---|---|
- | `build:vendors` | `composer install …` **only when** `composer.json` exists |
+ | `build:vendors` | `composer install ...` **only when** `composer.json` exists |
| `db:migrate` | `wp core update-db`, or `wp_upgrade()` through `wp-load.php` without wp-cli |
| `app:cache:flush` | `wp cache flush` + `wp rewrite flush --hard`, or the PHP equivalents |
| `maintenance:enable` / `disable` | writes/removes `.maintenance` and a marked `wp-content/maintenance.php` in the **served** path |
- | `db:backup` / restore | `wp db export` / `wp db import` — the only recipe besides Magento's with a dump |
+ | `db:backup` / restore | `wp db export` / `wp db import` |
| `deploy:verify` (`app`) | `wp core is-installed`, or `is_blog_installed()` without wp-cli |
Three steps are **hybrids**: `wp` when the target has wp-cli, a `wp-load.php` PHP bootstrap when it does not.
- - **Seed `shared/wp-config.php` before the first deploy.** `deploy:shared` links a shared entry only when it already exists, so an unseeded `shared/` leaves the release with the repository's `wp-config.php` — the one naming the development database. The `app` check then fails as unreachable.
- - Maintenance writes `time() + 86400`, not WordPress's own `time()`: a flag older than ten minutes expires, so a longer window would silently reopen the site mid-migration. The drop-in carries a marker, so a project's own maintenance page is kept.
- - `--db-backup` needs **wp-cli on the target**: unlike the three hybrids, `wp db export`/`wp db import` have no `wp-load.php` fallback.
- - **In artifact mode** the only build step is the guarded `composer install`, so the artifact carries `vendor/` when the project has a `composer.json`; the database steps still run on the target.
- - Shared: `wp-config.php` (file), `wp-content/uploads` (dir); writable: `wp-content/{uploads,cache,upgrade,languages}`. Sandbox adds `wp-cli`, `default-mysql-client`, extensions `mysqli curl gd intl mbstring xml zip`, and `mariadb` + `redis-server`.
+ - **Seed `shared/wp-config.php` before the first deploy.** `deploy:shared` links a shared entry only when it already exists, so an unseeded `shared/` leaves the release with the repository's `wp-config.php`, the one naming the development database. The first run fails at `db:migrate` ("Error establishing a database connection"), and the lock and release directory are kept. **Seeding afterwards does not repair that release:** `--resume` skips `deploy:shared` as already done (and `--from` does not re-run it), so link the shared file into the release by hand (`ln -sfn <deploy_path>/shared/wp-config.php <release>/wp-config.php`) before resuming, or start a fresh release. In one resumed run the unlinked release passed `db:migrate` and was **activated** before `app:cache:flush` failed with the same connection error, so an unseeded release can go live.
+ - `deploy:check` refuses a verify URL that redirects. A database copied from another environment keeps that environment's `siteurl`, so WordPress answers the verify URL with a 301 and the preflight stops. Point `siteurl`/`home` at the target URL (`wp option update`), or set `deploy.verify.follow_redirects: true`.
+ - Maintenance writes `time() + 86400`, not WordPress's own `time()`: a flag older than ten minutes expires, so a longer window would silently reopen the site mid-migration. The drop-in carries a marker, so a project's own maintenance page is kept. A deploy that fails after `maintenance:enable` leaves the flag and the lock behind; `--resume` (after `deploy unlock --force` if the lock is fresh) finishes the release and removes the flag.
+ - `--db-backup` needs **wp-cli on the target**: `wp db export`/`wp db import` have no `wp-load.php` fallback. Without wp-cli the deploy stops at `db:backup` and exits `127` (the failing command's status, not the documented `1`). Without the flag the `db:backup` row still prints a green tick but dumps nothing. With it the dump lands in `shared/backups/deploy/<release>/dump.sql`.
+ - `deploy rollback --with-db` restores the dump recorded by the release after the rollback target, and refuses (exit `1`) when that release has none, for example the newest one.
+ - `deploy unlock` refuses a fresh lock without `--force`. `deploy --resume` with nothing unfinished starts a new release, and says "already runs ... nothing to do" when the revision is already live (use `--force`).
+ - In artifact mode the only build step is the guarded `composer install`; the database steps still run on the target.
+ - Shared: `wp-config.php` (file), `wp-content/uploads` (dir); writable: `wp-content/{uploads,cache,upgrade,languages}`.
- > **Deploy preflight:** `govard deploy plan [remote]` prints this pipeline without executing anything or connecting (add `--json` for machine-readable output); `govard deploy check [remote]` runs the connectivity and release-layout preflight. Run both on the host.
+ ### Rehearsing on the sandbox
- Reference: <https://govard.ddtcorex.com/workflows/deployment#laravel-symfony-and-wordpress> · worked config: <https://govard.ddtcorex.com/workflows/deploy-case-studies#case-11-wordpress>.
+ `govard sandbox up` gives a disposable target with wp-cli, a MySQL client, the usual PHP extensions and `mariadb` + `redis-server`, so a deploy can be rehearsed with `govard deploy --remote sandbox --yes`. The origin environment must be running to seed.
+ - `--profile full` probes its database with `mysqladmin`, which the newer MariaDB image does not ship (only `mariadb-admin`). A stack on that image fails with "the sandbox database ... never answered" (exit `127`); pass `--db mariadb:<older series>` (after `sandbox down --purge`, because the volume refuses an older engine) or use `--profile php`. A seed that produces no shared tree can also stop at `chown ... .deployer: No such file`; a second `up` then succeeds without re-seeding.
+ - The seed copies the database but writes no `wp-config.php` and no shared tree for WordPress. Seed `shared/wp-config.php` yourself (the sandbox database user keeps the origin credentials; set `DB_HOST` to `localhost`), over `ssh -p <port> -i .govard/sandbox/id_ed25519 deployer@127.0.0.1`.
+ - `sandbox ssh` is interactive only and takes no command. Use `govard remote exec sandbox -- <cmd>`, which fails on the dangling `current` until the first deploy and again after `sandbox reset` (reset also wipes `shared/`, so re-seed `wp-config.php`).
+ - `govard remote exec`, `remote test`, `deploy plan|check|status|releases|rollback|unlock`, `db dump -e sandbox`, `snapshot create|list -e sandbox` and real `sync -s sandbox` (`--db`, `--file --path wp-content/themes`, `--media` which maps to `wp-content/uploads`) all work against it. `sync -s sandbox --db` needs a deployed `wp-config.php` on the target to detect credentials.
+ - `snapshot create -e <remote>` writes into `<current>/.govard/snapshots/` inside the served release, so the next deploy leaves it behind in the previous release directory.
+ - `govard open admin` opens `/admin`, not `/wp-admin` (local and remote), and `open admin -e sandbox` is refused as an unknown remote because `open` does not resolve the synthetic name; use a configured remote.
+
+ Reference: <https://govard.ddtcorex.com/workflows/deployment#laravel-symfony-and-wordpress> and <https://govard.ddtcorex.com/workflows/deploy-case-studies#case-11-wordpress>.
+
## Common Workflows
### After Pulling Code
```bash
- govard tool composer install
+ govard tool composer install # only if the project has composer.json
govard tool wp plugin update --all
govard tool wp rewrite flush
govard tool wp cache flush
```
- ### Sync from Staging (privacy-safe)
+ ### Sync from Staging
+ `--no-pii` and `--no-noise` do not protect `wp_users` against a remote (see Database), so treat the copy as containing real users:
+
```bash
- govard bootstrap --clone -e staging --no-pii --no-noise --yes
+ govard bootstrap --clone -e staging --yes # --yes (or -y) is required non-interactively
# or DB-only
- govard sync -s staging --db --no-pii --no-noise
- govard tool wp search-replace 'https://staging.example.com' 'https://wordpress.test' --all-tables
+ govard sync -s staging --db
+ govard tool wp search-replace 'https://staging.example.com' 'https://<project-domain>' --all-tables
```
+
+ A `bootstrap -e <remote>` run rewrites `siteurl`/`home` to the local domain after the import. Preview any of these with `--plan` first (`govard sync ... --plan`, `govard bootstrap ... --plan`); `sync --db --plan` still probes the remote over ssh for DB credentials. The clone plan lists Magento-only excludes (`app/etc/env.php`, `pub/static`) that mean nothing for WordPress.