govard-wordpress · git:20261008.2a8fb9e · 2026-10-08 · sha256 bfe49b5576ab895a

govard-wordpress git:20261008.2a8fb9eA

Immutable. This exact content never changes and is served at /api/v1/blob/bfe49b5576ab895a.

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

**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` runs inside the PHP container. `govard up` installs it when missing.

```bash
govard tool wp --info
govard tool wp core version
govard tool wp plugin list

# Alternative: open a shell, then run wp directly
govard sh
wp --info
```

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:

```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

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 (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

# Caching plugins expose their own wp subcommands; they only work once the plugin is installed
govard tool wp rocket clean --confirm
```

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
govard tool wp plugin list
govard tool wp theme list

govard tool wp plugin install query-monitor --activate
govard tool wp theme install twentytwentyfour --activate

govard tool wp plugin update --all
govard tool wp theme update --all
```

Must-use plugins live in `wp-content/mu-plugins` and are not managed by `wp plugin`.

## Rewrite & Core

```bash
# 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

govard tool wp core verify-checksums   # exits 1 if any core file was modified

# 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
```

`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 domain add|remove|list` manages the same list.

## Database

```bash
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'"

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

# Same thing through WP-CLI inside the container
govard tool wp db query "SHOW TABLES"
govard tool wp db export --add-drop-table
```

**Privacy filters match by table name, so they need the prefix.** `--no-noise` and `--no-pii` exclude WordPress tables (`users`, `usermeta`, `comments`, `commentmeta` for PII; a few cache/log tables for noise) with the table prefix prepended. Govard reads the literal `$table_prefix` from `wp-config.php`, locally and, for a remote, from the remote's own `wp-config.php` through the credentials probe. It never borrows the local prefix for a remote.

`--no-pii` fails closed: when no table can match (the remote's prefix cannot be read, or `$table_prefix` is built dynamically, for example from `getenv()`), the command is refused instead of writing a dump that still holds `wp_users`. `--no-noise` and `--plan` only warn. Still spot-check a dump before sharing it (`grep -c 'INSERT INTO `wp_users`'` must be 0). `db dump -e <remote> --file <path>` writes `<path>` on the **remote**; add `--local` to stream the dump into the project's `var/` directory.

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

```bash
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 |
| `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` |
| `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 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. The sandbox seed rewrites `siteurl`/`home` to its own URL; for any other target 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` uses `wp db export`/`wp db import` when the target has wp-cli, and otherwise a plain `mariadb-dump`/`mysqldump` that reads `wp-config.php` and hands the credentials to the client through a private option file (never on a command line). A target with none of the three is refused up front as a configuration error (exit `4`). Without the flag the `db:backup` row is reported as skipped, with the reason. 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}`.

### Rehearsing on the sandbox

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

- A seed that fails half way is retried by the next `sandbox up`; the seed creates the deploy tree before it changes ownership, and the readiness, seed and import steps use the MariaDB client names (`mariadb-admin`, `mariadb`, `mariadb-dump`) when the image has no `mysql*` ones.
- 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 (it forces a tty). 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 `<remote path>/.govard/snapshots/` (mode `0700`), which on a deploy layout is inside the served release, so the next deploy leaves it behind in the previous release directory. `snapshot restore` asks for confirmation; pass `-y` without a TTY.
- `govard open admin` opens `/wp-admin`, and `open admin -e sandbox` resolves the synthetic remote and uses its published port.

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        # 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

`--no-pii` refuses to run when the remote's table prefix cannot be read (see Database); without that flag the copy contains real users, so treat it accordingly:

```bash
govard bootstrap --clone -e staging --yes     # --yes (or -y) is required non-interactively
# or DB-only
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.