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.

install
source · Clone the upstream repo
git clone https://github.com/WordPress/agent-skills
Claude Code · Install into ~/.claude/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"
manifest: skills/blueprint/SKILL.md
source content

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

PropertyTypeNotes
$schema
stringAlways
"https://playground.wordpress.net/blueprint-schema.json"
landingPage
stringRelative path, e.g.
/wp-admin/
meta
object
{ title, author, description?, categories? }
— title and author required
preferredVersions
object
{ php, wp }
— both required when present
features
object
{ networking?: boolean, intl?: boolean }
only these two keys, nothing else. Networking defaults to
true
extraLibraries
array
["wp-cli"]
— auto-included when any
wp-cli
step is present
constants
objectShorthand for
defineWpConfigConsts
. Values: string/boolean/number
plugins
arrayShorthand for
installPlugin
steps. Strings = wp.org slugs
siteOptions
objectShorthand for
setSiteOptions
login
boolean or object
true
= login as admin. Object =
{ username?, password? }
(both default to
"admin"
/
"password"
)
steps
arrayMain execution pipeline. Runs after shorthands

preferredVersions Values

  • php: Major.minor only (e.g.
    "8.3"
    ,
    "7.4"
    ), or
    "latest"
    . Patch versions like
    "7.4.1"
    are invalid. Check the schema for currently supported versions.
  • wp: Recent major versions (e.g.
    "6.7"
    ,
    "6.8"
    ),
    "latest"
    ,
    "nightly"
    ,
    "beta"
    , or a URL to a custom zip. Check the schema for the full list.

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 TypeRequired FieldsExample
wordpress.org/plugins
slug
{ "resource": "wordpress.org/plugins", "slug": "woocommerce" }
wordpress.org/themes
slug
{ "resource": "wordpress.org/themes", "slug": "astra" }
url
url
{ "resource": "url", "url": "https://example.com/plugin.zip" }
git:directory
url
,
ref
See below
literal
name
,
contents
{ "resource": "literal", "name": "file.txt", "contents": "hello" }
literal:directory
name
,
files
See below
bundled
path
References a file within a blueprint bundle (e.g.
{ "resource": "bundled", "path": "/plugin.zip" }
)
zip
inner
Wraps another resource in a ZIP — use when a step expects a zip but your source isn't one (e.g. wrapping a
url
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
    ref
    , you must set
    refType
    (
    "branch"
    |
    "tag"
    |
    "commit"
    |
    "refname"
    ). Without it, only
    "HEAD"
    resolves reliably.
  • path
    selects a subdirectory (defaults to repo root).

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 ?>"
    }
  }
}
  • files
    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.
  • Do NOT use path separators in keys (e.g.
    "includes/helper.php"
    is wrong — use a nested
    "includes": { "helper.php": "..." }
    object).

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
    /
    themeData
    NOT the deprecated
    pluginZipFile
    /
    themeZipFile
    .
  • pluginData
    /
    themeData
    accept any FileReference or DirectoryReference — a zip URL, a
    wordpress.org/plugins
    slug, a
    git:directory
    , or a
    literal:directory
    (no
    zip
    wrapper needed).
  • options.activate
    controls activation. No need for a separate
    activatePlugin
    /
    activateTheme
    step when using
    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"
      }
    }
  }
}

writeFiles
requires a DirectoryReference (
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

StepKey Properties
login
username?
,
password?
(default
"admin"
/
"password"
)
enableMultisite
(no required props)
importWxr
file
(FileReference)
importThemeStarterContent
themeSlug?
importWordPressFiles
wordPressFilesZip
,
pathInZip?
— imports a full WordPress directory from a zip
request
request: { url, method?, headers?, body? }
updateUserMeta
userId
,
meta
runWpInstallationWizard
options?
— runs the WP install wizard with given options
resetData
(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

MistakeCorrect
pluginZipFile
/
themeZipFile
pluginData
/
themeData
"step": "cli"
"step": "wp-cli"
Flat object as
writeFiles.filesTree
Must be a
literal:directory
or
git:directory
resource
Path separators in
files
keys
Use nested objects for subdirectories
runPHP
without
wp-load.php
Always
require '/wordpress/wp-load.php';
for WP functions
Invented top-level keysOnly documented keys work — schema rejects unknown properties
Inventing proxy URLs for GitHubUse
git:directory
resource type
Omitting
refType
with branch/tag
ref
Required — only
"HEAD"
works without it
Resource references in
literal:directory
files
values
Values must be plain strings (content) or objects (subdirectories) — never resource refs
features.debug
or other invented feature keys
features
only supports
networking
and
intl
— use
constants: { "WP_DEBUG": true }
for debug mode
require wp-load.php
in mu-plugin code
Only needed in
runPHP
steps — mu-plugins already run within WordPress
Schema URL with
.org
domain
Must be
playground.wordpress.net
, not
playground.wordpress.org

Full Reference

This skill covers the most common steps and patterns. For the complete API, see:

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

  1. Create the bundle directory and add
    blueprint.json
    at its root.
  2. Write your plugin/theme source files in a subdirectory (e.g.
    my-plugin/my-plugin.php
    ).
  3. Zip the plugin directory:
    zip -r my-plugin.zip my-plugin/
  4. Reference it in
    blueprint.json
    using
    { "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

FormatHow to use
ZIP file (remote)Website:
https://playground.wordpress.net/?blueprint-url=https://example.com/bundle.zip
ZIP file (local)CLI:
npx @wp-playground/cli server --blueprint=./bundle.zip
Local directoryCLI:
npx @wp-playground/cli server --blueprint=./my-bundle/ --blueprint-may-read-adjacent-files
Git repository directoryPoint
blueprint-url
at a repo directory containing
blueprint.json

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.