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
json
{
"$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 "https://playground.wordpress.net/blueprint-schema.json"
|
| string | Relative path, e.g. |
| object | { title, author, description?, categories? }
— title and author required |
| object | — both required when present |
| object | { networking?: boolean, intl?: boolean }
— 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. , ), or . Patch versions like are invalid. Check the schema for currently supported versions.
- wp: Recent major versions (e.g. , ), , , , or a URL to a custom zip. Check the schema for the full list.
Shorthands vs Steps
Shorthands (
,
,
,
) are expanded and prepended to
in an
unspecified order. Use explicit steps when execution order matters.
Resource References
Resources tell Playground where to find files. Used by
,
,
,
,
, etc.
| Resource Type | Required Fields | Example |
|---|
| | { "resource": "wordpress.org/plugins", "slug": "woocommerce" }
|
| | { "resource": "wordpress.org/themes", "slug": "astra" }
|
| | { "resource": "url", "url": "https://example.com/plugin.zip" }
|
| , | See below |
| , | { "resource": "literal", "name": "file.txt", "contents": "hello" }
|
| , | See below |
| | References a file within a blueprint bundle (e.g. { "resource": "bundled", "path": "/plugin.zip" }
) |
| | 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
json
{
"resource": "git:directory",
"url": "https://github.com/WordPress/gutenberg",
"ref": "trunk",
"refType": "branch",
"path": "/"
}
- When using a branch or tag name for , you must set ( | | | ). Without it, only resolves reliably.
- selects a subdirectory (defaults to repo root).
literal:directory — Inline File Trees
json
{
"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.
- Do NOT use path separators in keys (e.g. is wrong — use a nested
"includes": { "helper.php": "..." }
object).
Steps Reference
Every step requires
. Any step can optionally include
"progress": { "weight": 1, "caption": "Installing..." }
for UI feedback.
Plugin & Theme Installation
json
{
"step": "installPlugin",
"pluginData": { "resource": "wordpress.org/plugins", "slug": "gutenberg" },
"options": { "activate": true, "targetFolderName": "gutenberg" },
"ifAlreadyInstalled": "overwrite"
}
json
{
"step": "installTheme",
"themeData": { "resource": "wordpress.org/themes", "slug": "twentytwentyfour" },
"options": { "activate": true, "importStarterContent": true },
"ifAlreadyInstalled": "overwrite"
}
- Use / — NOT the deprecated / .
- / accept any FileReference or DirectoryReference — a zip URL, a slug, a , or a (no wrapper needed).
- controls activation. No need for a separate / step when using /.
- : | |
Activation (standalone)
Only needed for plugins/themes already on disk (e.g. after
/
):
json
{ "step": "activatePlugin", "pluginPath": "my-plugin/my-plugin.php" }
json
{ "step": "activateTheme", "themeFolderName": "twentytwentyfour" }
File Operations
json
{ "step": "writeFile", "path": "/wordpress/wp-content/mu-plugins/custom.php", "data": "<?php // code" }
accepts a plain string (as shown above) or a resource reference (e.g.
{ "resource": "url", "url": "https://..." }
).
json
{
"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 (
or
) as
— not a plain object.
Other file operations:
,
,
,
,
,
.
Running Code
runPHP:
json
{ "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:
json
{ "step": "wp-cli", "command": "wp post create --post_type=page --post_title='Hello' --post_status=publish" }
The step name is
(with hyphen), NOT
or
.
runSql:
json
{ "step": "runSql", "sql": { "resource": "literal", "name": "q.sql", "contents": "UPDATE wp_options SET option_value='val' WHERE option_name='key';" } }
Site Configuration
json
{ "step": "setSiteOptions", "options": { "blogname": "My Site", "blogdescription": "A tagline" } }
json
{ "step": "defineWpConfigConsts", "consts": { "WP_DEBUG": true } }
json
{ "step": "setSiteLanguage", "language": "en_US" }
json
{ "step": "defineSiteUrl", "siteUrl": "https://example.com" }
Other Steps
| Step | Key Properties |
|---|
| , (default / ) |
| (no required props) |
| (FileReference) |
importThemeStarterContent
| |
| , — imports a full WordPress directory from a zip |
| request: { url, method?, headers?, body? }
|
| , |
| — runs the WP install wizard with given options |
| (no props) |
Common Patterns
Inline mu-plugin (quick custom code)
json
{
"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
json
{
"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:
json
{ "step": "activatePlugin", "pluginPath": "my-plugin/my-plugin.php" }
Plugin from a GitHub branch
json
{
"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 require '/wordpress/wp-load.php';
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 constants: { "WP_DEBUG": true }
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:
Additional steps not covered above:
(run PHP with custom
settings),
, and resource types
and
(for advanced embedding scenarios).
Blueprint Bundles
Bundles are self-contained packages that include a
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 —
expects a zip, not a raw directory. To create the zip from a plugin directory:
bash
cd my-bundle
zip -r my-plugin.zip my-plugin/
Referencing Bundled Resources
Use the
resource type to reference files within the bundle:
json
{
"step": "installPlugin",
"pluginData": {
"resource": "bundled",
"path": "/my-plugin.zip"
},
"options": { "activate": true }
}
json
{
"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.
- Write your plugin/theme source files in a subdirectory (e.g. ).
- Zip the plugin directory:
zip -r my-plugin.zip my-plugin/
- Reference it in 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
json
{
"$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: https://playground.wordpress.net/?blueprint-url=https://example.com/bundle.zip
|
| ZIP file (local) | CLI: npx @wp-playground/cli server --blueprint=./bundle.zip
|
| Local directory | CLI: npx @wp-playground/cli server --blueprint=./my-bundle/ --blueprint-may-read-adjacent-files
|
| 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
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):
bash
# 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):
bash
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.