Agent-skills blueprint
Use when creating, editing, or reviewing WordPress Playground blueprint JSON files. Triggers on mentions of blueprints, playground configuration, or requests to set up a WordPress demo environment.
git clone https://github.com/WordPress/agent-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/WordPress/agent-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/blueprint" ~/.claude/skills/wordpress-agent-skills-blueprint && rm -rf "$T"
skills/blueprint/SKILL.mdWordPress Playground Blueprints
Overview
A Blueprint is a JSON file that declaratively configures a WordPress Playground instance — installing plugins/themes, setting options, running PHP/SQL, manipulating files, and more.
Core principle: Blueprints are trusted JSON-only declarations. No arbitrary JavaScript. They work on web, Node.js, and CLI.
Quick Start Template
{ "$schema": "https://playground.wordpress.net/blueprint-schema.json", "landingPage": "/wp-admin/", "preferredVersions": { "php": "8.3", "wp": "latest" }, "steps": [{ "step": "login" }] }
Top-Level Properties
All optional. Only documented keys are allowed — the schema rejects unknown properties.
| Property | Type | Notes |
|---|---|---|
| string | Always |
| string | Relative path, e.g. |
| object | — title and author required |
| object | — both required when present |
| object | — only these two keys, nothing else. Networking defaults to |
| array | — auto-included when any step is present |
| object | Shorthand for . Values: string/boolean/number |
| array | Shorthand for steps. Strings = wp.org slugs |
| object | Shorthand for |
| boolean or object | = login as admin. Object = (both default to /) |
| array | Main execution pipeline. Runs after shorthands |
preferredVersions Values
- php: Major.minor only (e.g.
,"8.3"
), or"7.4"
. Patch versions like"latest"
are invalid. Check the schema for currently supported versions."7.4.1" - wp: Recent major versions (e.g.
,"6.7"
),"6.8"
,"latest"
,"nightly"
, or a URL to a custom zip. Check the schema for the full list."beta"
Shorthands vs Steps
Shorthands (
login, plugins, siteOptions, constants) are expanded and prepended to steps in an unspecified order. Use explicit steps when execution order matters.
Resource References
Resources tell Playground where to find files. Used by
installPlugin, installTheme, writeFile, writeFiles, importWxr, etc.
| Resource Type | Required Fields | Example |
|---|---|---|
| | |
| | |
| | |
| , | See below |
| , | |
| , | See below |
| | References a file within a blueprint bundle (e.g. ) |
| | Wraps another resource in a ZIP — use when a step expects a zip but your source isn't one (e.g. wrapping a resource pointing to a raw directory) |
git:directory — Installing from GitHub
{ "resource": "git:directory", "url": "https://github.com/WordPress/gutenberg", "ref": "trunk", "refType": "branch", "path": "/" }
- When using a branch or tag name for
, you must setref
(refType
|"branch"
|"tag"
|"commit"
). Without it, only"refname"
resolves reliably."HEAD"
selects a subdirectory (defaults to repo root).path
literal:directory — Inline File Trees
{ "resource": "literal:directory", "name": "my-plugin", "files": { "plugin.php": "<?php /* Plugin Name: My Plugin */ ?>", "includes": { "helper.php": "<?php // helper code ?>" } } }
uses nested objects for subdirectories — keys are filenames or directory names, values are plain strings (file content) or objects (subdirectories). Never use resource references as values.files- Do NOT use path separators in keys (e.g.
is wrong — use a nested"includes/helper.php"
object)."includes": { "helper.php": "..." }
Steps Reference
Every step requires
"step": "<name>". Any step can optionally include "progress": { "weight": 1, "caption": "Installing..." } for UI feedback.
Plugin & Theme Installation
{ "step": "installPlugin", "pluginData": { "resource": "wordpress.org/plugins", "slug": "gutenberg" }, "options": { "activate": true, "targetFolderName": "gutenberg" }, "ifAlreadyInstalled": "overwrite" }
{ "step": "installTheme", "themeData": { "resource": "wordpress.org/themes", "slug": "twentytwentyfour" }, "options": { "activate": true, "importStarterContent": true }, "ifAlreadyInstalled": "overwrite" }
- Use
/pluginData
— NOT the deprecatedthemeData
/pluginZipFile
.themeZipFile
/pluginData
accept any FileReference or DirectoryReference — a zip URL, athemeData
slug, awordpress.org/plugins
, or agit:directory
(noliteral:directory
wrapper needed).zip
controls activation. No need for a separateoptions.activate
/activatePlugin
step when usingactivateTheme
/installPlugin
.installTheme
:ifAlreadyInstalled
|"overwrite"
|"skip""error"
Activation (standalone)
Only needed for plugins/themes already on disk (e.g. after
writeFile/writeFiles):
{ "step": "activatePlugin", "pluginPath": "my-plugin/my-plugin.php" }
{ "step": "activateTheme", "themeFolderName": "twentytwentyfour" }
File Operations
{ "step": "writeFile", "path": "/wordpress/wp-content/mu-plugins/custom.php", "data": "<?php // code" }
data accepts a plain string (as shown above) or a resource reference (e.g. { "resource": "url", "url": "https://..." }).
{ "step": "writeFiles", "writeToPath": "/wordpress/wp-content/plugins/", "filesTree": { "resource": "literal:directory", "name": "my-plugin", "files": { "plugin.php": "<?php\n/*\nPlugin Name: My Plugin\n*/", "includes": { "helpers.php": "<?php // helpers" } } } }
requires a DirectoryReference (writeFiles
literal:directory or git:directory) as filesTree — not a plain object.
Other file operations:
mkdir, cp, mv, rm, rmdir, unzip.
Running Code
runPHP:
{ "step": "runPHP", "code": "<?php require '/wordpress/wp-load.php'; update_option('key', 'value');" }
GOTCHA: You must
require '/wordpress/wp-load.php'; to use any WordPress functions.
wp-cli:
{ "step": "wp-cli", "command": "wp post create --post_type=page --post_title='Hello' --post_status=publish" }
The step name is
wp-cli (with hyphen), NOT cli or wpcli.
runSql:
{ "step": "runSql", "sql": { "resource": "literal", "name": "q.sql", "contents": "UPDATE wp_options SET option_value='val' WHERE option_name='key';" } }
Site Configuration
{ "step": "setSiteOptions", "options": { "blogname": "My Site", "blogdescription": "A tagline" } }
{ "step": "defineWpConfigConsts", "consts": { "WP_DEBUG": true } }
{ "step": "setSiteLanguage", "language": "en_US" }
{ "step": "defineSiteUrl", "siteUrl": "https://example.com" }
Other Steps
| Step | Key Properties |
|---|---|
| , (default / ) |
| (no required props) |
| (FileReference) |
| |
| , — imports a full WordPress directory from a zip |
| |
| , |
| — runs the WP install wizard with given options |
| (no props) |
Common Patterns
Inline mu-plugin (quick custom code)
{ "step": "writeFile", "path": "/wordpress/wp-content/mu-plugins/custom.php", "data": "<?php\n// mu-plugins load automatically — no activation needed, no require wp-load.php\nadd_filter('show_admin_bar', '__return_false');" }
Inline plugin with multiple files
{ "step": "writeFiles", "writeToPath": "/wordpress/wp-content/plugins/", "filesTree": { "resource": "literal:directory", "name": "my-plugin", "files": { "my-plugin.php": "<?php\n/*\nPlugin Name: My Plugin\n*/\nrequire __DIR__ . '/includes/main.php';", "includes": { "main.php": "<?php // main logic" } } } }
Then activate it with a separate step:
{ "step": "activatePlugin", "pluginPath": "my-plugin/my-plugin.php" }
Plugin from a GitHub branch
{ "step": "installPlugin", "pluginData": { "resource": "git:directory", "url": "https://github.com/user/repo", "ref": "feature-branch", "refType": "branch", "path": "/" } }
Common Mistakes
| Mistake | Correct |
|---|---|
/ | / |
| |
Flat object as | Must be a or resource |
Path separators in keys | Use nested objects for subdirectories |
without | Always for WP functions |
| Invented top-level keys | Only documented keys work — schema rejects unknown properties |
| Inventing proxy URLs for GitHub | Use resource type |
Omitting with branch/tag | Required — only works without it |
Resource references in values | Values must be plain strings (content) or objects (subdirectories) — never resource refs |
or other invented feature keys | only supports and — use for debug mode |
in mu-plugin code | Only needed in steps — mu-plugins already run within WordPress |
Schema URL with domain | Must be , not |
Full Reference
This skill covers the most common steps and patterns. For the complete API, see:
- Blueprint docs: https://wordpress.github.io/wordpress-playground/blueprints
- JSON schema: https://playground.wordpress.net/blueprint-schema.json
Additional steps not covered above:
runPHPWithOptions (run PHP with custom ini settings), runWpInstallationWizard, and resource types vfs and bundled (for advanced embedding scenarios).
Blueprint Bundles
Bundles are self-contained packages that include a
blueprint.json along with all the resources it references (plugins, themes, WXR files, etc.). Instead of hosting assets externally, bundle them alongside the blueprint.
Bundle Structure
my-bundle/ ├── blueprint.json ← must be at the root ├── my-plugin.zip ← zipped plugin directory ├── theme.zip └── content/ └── sample-content.wxr
Plugins and themes must be zipped before bundling —
installPlugin expects a zip, not a raw directory. To create the zip from a plugin directory:
cd my-bundle zip -r my-plugin.zip my-plugin/
Referencing Bundled Resources
Use the
bundled resource type to reference files within the bundle:
{ "step": "installPlugin", "pluginData": { "resource": "bundled", "path": "/my-plugin.zip" }, "options": { "activate": true } }
{ "step": "importWxr", "file": { "resource": "bundled", "path": "/content/sample-content.wxr" } }
Creating a Bundle Step by Step
- Create the bundle directory and add
at its root.blueprint.json - Write your plugin/theme source files in a subdirectory (e.g.
).my-plugin/my-plugin.php - Zip the plugin directory:
zip -r my-plugin.zip my-plugin/ - Reference it in
usingblueprint.json
.{ "resource": "bundled", "path": "/my-plugin.zip" }
Full example — a bundle that installs a custom plugin:
dashboard-widget-bundle/ ├── blueprint.json ├── dashboard-widget.zip ← zip of dashboard-widget/ └── dashboard-widget/ ← plugin source (kept for editing) └── dashboard-widget.php
{ "$schema": "https://playground.wordpress.net/blueprint-schema.json", "landingPage": "/wp-admin/", "preferredVersions": { "php": "8.3", "wp": "latest" }, "steps": [ { "step": "login" }, { "step": "installPlugin", "pluginData": { "resource": "bundled", "path": "/dashboard-widget.zip" }, "options": { "activate": true } } ] }
Distribution Formats
| Format | How to use |
|---|---|
| ZIP file (remote) | Website: |
| ZIP file (local) | CLI: |
| Local directory | CLI: |
| Git repository directory | Point at a repo directory containing |
GOTCHA: Local directory bundles always need
--blueprint-may-read-adjacent-files for the CLI to read bundled resources. Without it, any "resource": "bundled" reference will fail with a "File not found" error. ZIP bundles don't need this flag — all files are self-contained inside the archive.
Testing Blueprints
Inline Blueprints (quick test, no bundles)
Minify the blueprint JSON (no extra whitespace), prepend
https://playground.wordpress.net/#, and open the URL in a browser:
https://playground.wordpress.net/#{"$schema":"https://playground.wordpress.net/blueprint-schema.json","preferredVersions":{"php":"8.3","wp":"latest"},"steps":[{"step":"login"}]}
Very large blueprints may exceed browser URL length limits; use the CLI instead.
Local CLI Testing
Interactive server (keeps running, opens in browser):
# Directory bundle — requires --blueprint-may-read-adjacent-files npx @wp-playground/cli server --blueprint=./my-bundle/ --blueprint-may-read-adjacent-files # ZIP bundle — self-contained, no extra flags needed npx @wp-playground/cli server --blueprint=./bundle.zip
Headless validation (runs blueprint and exits):
npx @wp-playground/cli run-blueprint --blueprint=./my-bundle/ --blueprint-may-read-adjacent-files
Testing with the wordpress-playground-server Skill
Use the
wordpress-playground-server skill to start a local Playground instance with --blueprint /path/to/blueprint.json, then verify the expected state with Playwright MCP. For directory bundles, pass --blueprint-may-read-adjacent-files as an extra argument.