WP Codebox recipes use the wp-codebox/workspace-recipe/v1 schema. A recipe is
a declarative sandbox setup plus ordered workflow steps. The CLI validates the
recipe before booting WordPress Playground, then returns a structured artifact
bundle after execution.
Use the generated schema and command catalog as the source of truth:
npm run wp-codebox -- schema recipe --json
npm run wp-codebox -- commands --jsonValidate recipes before using them in CI or documentation examples:
npm run wp-codebox -- recipe validate --recipe ./path/to/recipe.json --json
npm run wp-codebox -- recipe-run --recipe ./path/to/recipe.json --dry-run --json{
"schema": "wp-codebox/workspace-recipe/v1",
"runtime": {
"backend": "wordpress-playground",
"wp": "latest"
},
"inputs": {
"mounts": [
{
"source": "../my-plugin",
"target": "/wordpress/wp-content/plugins/my-plugin",
"mode": "readonly"
}
]
},
"workflow": {
"steps": [
{
"command": "wordpress.run-php",
"args": ["code=echo get_bloginfo( 'name' );"]
}
]
}
}Recipe field names are case-sensitive. The current recipe schema accepts these top-level fields:
schemadistributionruntimeinputsworkflowartifactsprobes
inputs accepts these fields:
mountsworkspacesworkspace_preloadsextra_pluginscomponent_manifestdependency_overlaysruntimeEnvsecretEnvpluginRuntimefixtureDatabasessiteSeedsstagedFilessourcePackagesagent_bundlesinheritinheritance
Use inputs.extra_plugins, not inputs.extraPlugins. The camelCase form is an
old docs/example drift and is not part of wp-codebox/workspace-recipe/v1.
Use inputs.agent_bundles, not inputs.agentBundles, for runtime agent bundle
imports.
Use inputs.workspace_preloads for generic agent-runtime/workspace-preload
artifact contracts. WP Codebox materializes declared repositories as sandbox
workspace mounts; callers own the policy that decides which artifacts to pass.
inputs.extra_plugins mounts additional WordPress plugins before workflow steps
run. Each entry requires source and may include slug, pluginFile,
activate, loadAs, and sha256.
{
"inputs": {
"extra_plugins": [
{
"source": "https://downloads.wordpress.org/plugin/bbpress.latest-stable.zip",
"pluginFile": "bbpress/bbpress.php",
"activate": false
},
{
"source": "../agents-api",
"slug": "agents-api",
"pluginFile": "agents-api/agents-api.php",
"activate": false,
"loadAs": "mu-plugin"
}
]
}
}Supported loadAs values:
plugin: mount below/wordpress/wp-content/plugins/<slug>and activate whenactivateis notfalse.mu-plugin: mount below/wordpress/wp-content/mu-plugins/wp-codebox-runtime/<slug>and load through WP Codebox's generated MU-plugin loader. Use this for sandbox runtime substrate, not user-visible plugins.
External HTTPS zip downloads are gated by WP_CODEBOX_ALLOW_NETWORK_DOWNLOADS=1.
Local paths are resolved relative to the recipe file.
inputs.sourcePackages materializes local recipe source directories below the
sandbox workspace. It is directory-only; use inputs.stagedFiles for single-file
staging. Each package declares a stable name, local source,
workspace-relative target, optional allow/deny path filters, and optional
provenance artifact registration.
{
"inputs": {
"sourcePackages": [
{
"name": "fixture-plugin",
"source": "./fixtures/plugin",
"target": "packages/fixture-plugin",
"allow": ["src*", "composer.json"],
"deny": ["src/secrets*"],
"artifact": true
}
]
}
}Targets are normalized under /workspace when they are not already absolute
workspace paths. Materialization uses WP Codebox prepared-source exclusions,
applies deny before allow, hashes the copied package, and writes
.wp-codebox-source-package.json into the staged package root with source,
filter, target, digest, and timestamp provenance. Validation emits blockers for
unsafe names, paths, and filters before runtime setup.
recipe build template compiles declarative source package input into generated
artifact declarations while preserving inputs.sourcePackages, so callers use
the same validation and run path as hand-written recipes without duplicate
mounts.
npm run wp-codebox -- recipe build template --options ./template-options.json --output ./recipe.jsonWorkflow steps are ordered command invocations:
{
"command": "wordpress.wp-cli",
"args": ["command=option get home"]
}Each args entry is a string. Command-specific argument names and repeatability
come from the command catalog:
npm run wp-codebox -- commands --jsonUse allowFailure: true or advisory: true for evidence-only workflow steps.
Failed advisory steps are reported in advisoryFailures and do not make an
otherwise successful recipe return success: false.
Recipes can pass REST profiling workloads to wordpress.bench with
workloads-json. A workload may use route_matrix to declare a bounded set of
REST requests; the runtime expands each route into the existing rest-request
step and returns normal benchmark scenarios/artifacts.
{
"command": "wordpress.bench",
"args": [
"plugin-slug=woocommerce",
"iterations=3",
"warmup-iterations=1",
"workloads-json=[{\"id\":\"rest-catalog\",\"source\":\"config\",\"route_matrix\":[{\"id\":\"products-list\",\"method\":\"GET\",\"path\":\"/wc/v3/products\",\"params\":{\"per_page\":10}}],\"artifacts\":{\"route-summary\":{\"path\":\"bench/rest-route-summary.json\",\"kind\":\"json\",\"source\":\"scenario-artifact\"}}}]"
]
}Use site seeds, fixture databases, or staged files for product-specific data
setup. Use artifacts on the workload for declared per-scenario outputs that
downstream lab tooling should extract from benchResults or an artifact bundle.
inputs.siteSeeds is the generic fixture import primitive. JSON fixture seeds
import through WordPress APIs for posts, options, terms, users, media, active
plugins, and active theme declarations. Those APIs can make semantic identifiers
stable, such as post slugs, term slugs, option names, user logins, plugin files,
and theme stylesheets. They cannot guarantee numeric primary keys.
Use deterministicIds to make that boundary explicit:
{
"inputs": {
"siteSeeds": [
{
"type": "fixture",
"name": "demo-content",
"source": "fixtures/content.json",
"format": "json",
"deterministicIds": {
"strategy": "platform-identifiers",
"onUnsupported": "block"
},
"scopes": {
"posts": { "slugs": ["home"] },
"options": { "names": ["blogname"] }
}
}
]
}
}When onUnsupported is block, WP Codebox reports a validation/runtime blocker
if the generic importer sees numeric id/ID fixture fields or a recipe requests
the numeric strategy. Custom importers may add format-specific support later,
but the generic contract stays honest about what platform APIs can guarantee.
Recipes may also attach reusable bootstrap declarations to a site seed:
{
"bootstrap": {
"multisite": {
"enabled": true,
"install": "subdomain",
"sites": [{ "domain": "example.test", "path": "/", "title": "Example" }]
},
"domains": [{ "domain": "example.test", "path": "/", "primary": true }]
}
}These are generic declarations for orchestrators and future runtime setup support. The current built-in fixture importer treats multisite/domain bootstrap as declared metadata and blocks executable fixture imports that require runtime setup not yet provided by WP Codebox.
{
"command": "wordpress.browser-actions",
"args": ["url=/", "steps-json=[...]"],
"advisory": true
}Hosts that need an authenticated browser session for a disposable WordPress
sandbox can use the playground package's fixture auth storage-state helpers
instead of baking product-specific site identifiers into recipes. The helper
contract is generic: resolve or create a named WordPress fixture user inside the
sandbox, mint short-lived WordPress admin cookies for declared browser origins,
and return a Playwright-compatible storageState envelope:
import { wordpressFixtureUserStorageStatePhpCode } from "@automattic/wp-codebox-playground"The emitted PHP returns wp-codebox/browser-auth-storage-state/v1:
{
"schema": "wp-codebox/browser-auth-storage-state/v1",
"kind": "wordpress-fixture-user-admin-auth",
"user": {
"id": 1,
"username": "wp-codebox-fixture-admin",
"email": "wp-codebox-fixture-admin@example.test",
"role": "administrator",
"created": true
},
"storageState": {
"cookies": [],
"origins": []
}
}Recipes can export that state as an artifact with the generic command:
{
"command": "wordpress.export-browser-storage-state",
"args": [
"browser-urls=http://127.0.0.1:9400,https://preview.example.test",
"user-json={\"username\":\"fixture-admin\",\"role\":\"administrator\"}"
]
}The command also accepts caller-produced state with storage-state=<json> or
storage-state=@./state.json. That value may be a raw Playwright
storageState object or the wp-codebox/browser-auth-storage-state/v1 envelope
shown above, so product-specific PHP bootstrap code can stay outside WP Codebox
and hand the resulting generic browser state to the exporter.
The command returns wp-codebox/browser-storage-state-export/v1 with
artifacts.storageState and artifacts.summary. The storage-state artifact is
the token-bearing Playwright JSON and is marked redactionRequired in
artifactRefs; the summary artifact and command output contain only
reviewer-safe metadata: schema/kind, user id/name/email/role/created flag,
cookie counts by host, origin count, and diagnostics. Product policy,
production identifiers, and cross-site account mapping stay outside WP Codebox.
wordpress.browser-probe and wordpress.browser-actions can import a reusable
storage state with storage-state=<json> or storage-state=@./state.json. The
value may be a raw Playwright storageState object or the
wp-codebox/browser-auth-storage-state/v1 envelope shown above. WP Codebox passes
the state directly to a fresh Playwright context and records only redacted
provenance in browser summaries: source type, optional schema/kind, cookie count,
cookie hosts, origin count, and diagnostics. Cookie values and localStorage values
are never written to browser evidence artifacts.
Use one browser authentication source per command. auth=wordpress-admin creates
short-lived in-memory cookies from the disposable WordPress sandbox; storage-state
imports caller-provided reusable state. Supplying both is rejected with structured
storage-state diagnostics rather than silently preferring one source.
recipe-run --json returns wp-codebox/recipe-run/v1. Browser command sidecars
are promoted into browserEvidence so callers can discover stable evidence
without hardcoding artifact paths or parsing command stdout.
The same output includes result, a normalized
wp-codebox/recipe-run-summary/v1 envelope for portable callers. It groups
common artifact refs, command stdout/stderr tails, run/runtime metadata, failure
phase and summary, and held-preview reviewer access. Consumers should prefer
result before scraping latest-runtime.json, commands.jsonl, command stdout,
or preview internals from the artifact bundle.
Each browserEvidence entry includes the workflow phase/index, command,
summary file, artifact file refs, summary payload, and scriptResult when the
browser command produced one. The same browser evidence is mirrored into
latest-runtime.json under the run artifact pointer.
When a browser command captures network evidence, WP Codebox writes the raw
network.jsonl stream and a derived waterfall.json artifact using the stable
wp-codebox/browser-waterfall/v1 schema. The waterfall is HAR-style JSON with
redacted URLs, request/response status, resource type, sizes, and timing fields;
both files are registered as redaction-required browser artifacts and surfaced
through browserEvidence.files.
--preview-hold-seconds <duration> records held-preview lifecycle metadata in the
artifact bundle and returns after recipe work finishes. Use
--preview-hold-blocking only for operator workflows that need the CLI process
to keep a live preview server open for the hold duration.
Hosts that expose recipe/task runner commands through WP Codebox should use the
core executeHostCommand() primitive instead of hand-rolled process wrappers. It
runs one command without shell expansion, enforces allowed working-directory and
environment policy, times out long-running work, terminates the spawned process
group, samples process-tree RSS, and returns a structured result with:
failureClassification:none,timeout,non_zero_exit, orsignal.commandSummary: a compact command line for human diagnostics.- Bounded
stdoutandstderrfields plusoutputTruncated. memorySamplesandpeakRssBytesfor runner watchdog evidence.- Optional
artifacts.stdout,artifacts.stderr, andartifacts.summaryrefs whenartifactsDirectoryis provided.
The summary artifact uses wp-codebox/host-command-summary/v1. Product-specific
orchestrators can wrap this primitive, but should keep WPCOM, CI-provider, and
deployment semantics outside the generic runner layer.
wordpress.phpunit is the lightweight WP Codebox equivalent of plugin PHPUnit
commands commonly run through wp-env run ... vendor/bin/phpunit. It boots a
disposable WordPress Playground runtime, mounts the tested plugin at
/wordpress/wp-content/plugins/<plugin-slug>, prepares the WordPress PHPUnit
contract, and captures command output in the artifact bundle.
The runtime provides:
WP_TESTS_DIRpointing at the configured WordPress tests library.WP_TESTS_CONFIG_FILE_PATHandWP_PHPUNIT__TESTS_CONFIGpointing at the generatedwp-tests-config.php.- An isolated SQLite test database via
DB_NAME=':memory:'. - A plugin working directory via
cwd=<sandbox path>, matching the practical role ofwp-env run --env-cwd. - Structured diagnostics in the recipe artifact bundle, including the raw test
result log collected from
/tmp/wp-codebox-phpunit-result.txt.
Use recipe build phpunit when generating recipes for plugin CI or offloaded lab
runners:
{
"pluginSlug": "woocommerce",
"pluginSource": "../woocommerce/plugins/woocommerce",
"cwd": "/wordpress/wp-content/plugins/woocommerce",
"autoloadFile": "/wp-codebox-vendor/autoload.php",
"testsDir": "/wp-codebox-vendor/wp-phpunit/wp-phpunit",
"bootstrapMode": "project",
"projectBootstrap": "tests/legacy/bootstrap.php",
"phpunitArgs": ["--filter", "WC_Checkout_Test::test_checkout"]
}npm run wp-codebox -- recipe build phpunit --options ./phpunit-options.json --output ./phpunit.recipe.json
npm run wp-codebox -- recipe-run --recipe ./phpunit.recipe.json --artifacts ./artifacts/phpunit --jsonTools that need to generate WordPress bench or PHPUnit recipes without shelling out to the CLI may import the supported recipe-builder module:
import {
buildWordPressBenchRecipe,
buildWordPressPhpunitRecipe,
} from "@automattic/wp-codebox-core/recipe-builders"The same subpath is exported by the root release package as
wp-codebox-workspace/recipe-builders, so callers can use one documented module
name for npm package installs and release-tarball installs without probing
packages/runtime-core/dist/index.js or other internal build paths. Local
checkout consumers should run npm run build first, then import the same
workspace package subpath through normal Node package resolution.
The CLI surface remains the stable process boundary for hosts that prefer JSON over an in-process import:
wp-codebox recipe build phpunit --options ./phpunit-options.json --output ./phpunit.recipe.json
wp-codebox recipe build bench --options ./bench-options.json --output ./bench.recipe.json
wp-codebox recipe build generic-ability-runtime-run --options ./ability-runtime-options.json --output ./ability-runtime.recipe.jsonAll builders return wp-codebox/workspace-recipe/v1 recipes. The generated
recipes should still be validated with wp-codebox recipe validate or
wp-codebox recipe-run --dry-run before execution in CI.
generic-ability-runtime-run is the stable preset for callers that need to run a
WordPress Ability in a disposable runtime with generic component/provider
contracts. The options JSON maps to buildGenericAbilityRuntimeRunRecipe and
accepts abilityId, optional abilityInput, components, providerPlugins,
providerPluginPaths, runtimeOverlays, runtimeEnv, secretEnv,
toolPolicy, expectedResultSchema, mounts, runtimeStackMounts,
stagedFiles, and verifySteps. The generated workflow invokes
wordpress.ability with runtime invocation metadata and, when
expectedResultSchema is supplied, recipe-run --json records the canonical
wp-codebox/generic-ability-runtime-run-result/v1 command output with the
ability result and discovered evidenceEnvelope.
For monorepos such as WooCommerce, set pluginSource to the directory that
should appear as wp-content/plugins/<plugin-slug> and set cwd to the same
sandbox directory a wp-env --env-cwd command would use. Relative cwd values
resolve inside the mounted plugin directory.
wordpress.browser-probe accepts repeated assert=<assertion> arguments.
Supported assertion forms are the current command contract:
exists:<selector>not-exists:<selector>visible:<selector>hidden:<selector>count:<selector><op><number>text:<selector> contains <text>attr:<selector>[name][=value]no-console-errorsno-page-errorsno-errorsrequest-count-by-host:<host><op><number>request-count-by-type:<type><op><number>total-transfer-size<op><number>- metric budgets such as
lcp_ms<=2500,fcp_ms<=1800,ttfb_ms<=800, andnav_duration_ms<=5000
Prefix an assertion with advisory: to record a failing assertion without
failing the command.
Do not use assert=script:passed equals true. That syntax is not supported by
the current WP Codebox browser assertion contract. Use wordpress.browser-actions
with an evaluate step and assert value when a page script must prove state:
{
"command": "wordpress.browser-actions",
"args": [
"url=/wp-admin/tools.php?page=my-plugin",
"steps-json=[{\"kind\":\"evaluate\",\"expression\":\"window.myPluginReady === true\",\"assert\":true}]"
]
}Recipes that use a browser-actions evaluate step require the runtime policy to
allow wordpress.browser-actions.evaluate in addition to
wordpress.browser-actions.
When editing docs or example recipes:
- Prefer generated schema output over hand-written field lists.
- Run
recipe validateagainst every recipe changed. - Run
recipe-run --dry-run --jsonwhen changing mounts, workspaces,extra_plugins, secrets, or workflow args. - Keep product-specific orchestration, scoring, PR creation, and deployment out of recipes. Parent control planes own those behaviors.