Self-hosted WordPress Playground Implementation: One-Click Plugin Preview via ?plugin=xxx

Background

WordPress Playground is a tool that runs a full WordPress instance entirely within the browser, powered by WebAssembly—no server required. The official site, playground.wordpress.net, supports the ?plugin=xxx query parameter to directly load and preview plugins hosted on WordPress.org.

We self-host WordPress Playground at play.wenpai.net, and need to implement identical functionality—but with plugins sourced from our own repository instead of WordPress.org.

Core Concept: Blueprint

A Blueprint is WordPress Playground’s configuration file (in JSON format), defining the initial state of a Playground instance:

{
  "$schema": "https://playground.wordpress.net/blueprint-schema.json",
  "landingPage": "/wp-admin/options-general.php?page=wpslug",
  "preferredVersions": { "php": "8.3", "wp": "latest" },
  "steps": [
    { "step": "setSiteLanguage", "language": "zh_CN" },
    { "step": "installPlugin", "pluginData": { "resource": "url", "url": "https://play.wenpai.net/plugins/wpslug-1.1.1.zip" } },
    { "step": "activatePlugin", "pluginPath": "wpslug/wpslug.php" },
    { "step": "login", "username": "admin", "password": "password" }
  ]
}

Playground natively supports the blueprint-url query parameter. Visiting /playground.html?blueprint-url=/path/to/blueprint.json loads the specified configuration.

Implementation Plan

1. nginx Redirect Rule

Add the following rule to the nginx configuration to rewrite ?plugin=xxx into ?blueprint-url=:

location = / {
    if ($arg_plugin) {
        return 302 /playground.html?blueprint-url=/play/blueprints/$arg_plugin.json;
    }
    try_files $uri $uri/ =404;
}

Effect:

  • https://play.wenpai.net/?plugin=wpslug → 302 redirect → /playground.html?blueprint-url=/play/blueprints/wpslug.json

2. Declarative Plugin Configuration

Place a blueprint.json file in the root directory of each plugin repository, declaring Playground metadata:

{
  "id": "wpslug",
  "name": "Wenpai Slug",
  "tag": "SEO",
  "icon": "link",
  "desc": "More user-friendly and aesthetically pleasing permalinks for posts, pages, and media.",
  "landingPage": "/wp-admin/options-general.php?page=wpslug",
  "pluginPath": "wpslug/wpslug.php",
  "blogname": "Wenpai Slug Demo Site"
}

3. Automated Synchronization Script

The sync-plugins.sh script scans all repositories under our organization that contain a blueprint.json file and automatically:

  • Downloads the latest release ZIP (falling back to GitHub archive URL if no release asset exists);
  • Generates a valid Playground blueprint JSON file using metadata from blueprint.json;
  • Updates the plugins.json file used to render the homepage plugin listing;
  • Runs weekly via CI, but can also be triggered manually.

4. Automated Deployment

A Forgejo Actions workflow monitors changes to the play/ directory and automatically deploys updates to the production server via rsync.

Full Workflow

To add a new plugin to Playground:

  1. Add blueprint.json to the root of the plugin’s repository;
  2. Publish a GitHub release (including a ZIP asset);
  3. Wait for automatic sync (or trigger it manually);
  4. Visit https://play.wenpai.net/?plugin=<plugin-id> to preview the plugin instantly.

References

A few practical lessons learned from the acceptance testing perspective:

Local Testing Workflow

Use the Playground CLI to load a blueprint for local acceptance testing—no dependency on production environments:

# Start a local HTTP server to serve ZIP files
cd ~/play/plugins && python3 -m http.server 8888 &

# Launch Playground and load the blueprint
npx @wp-playground/cli server --port=9400 --login \
    --blueprint=/path/to/blueprint.json

In the blueprint, set the installPlugin URL to point to your local ZIP file (e.g., http://127.0.0.1:8888/xxx.zip)—this enables full end-to-end validation before deployment.

Pitfalls and Notes

  1. The Gutenberg welcome modal blocks automated test interactions. Playwright tests must first close it via .components-modal__header button[aria-label="Close"].
  2. Playground’s baseURL must use 127.0.0.1, not localhost. Using localhost causes CORS issues due to mismatched script src values between Playground and WordPress internal scripts.
  3. wp-china-yes is incompatible with Playground’s WASM-based PHP runtime (the CSF framework crashes with exit code 255). Replace it across all blueprints with the wenpai-accelerate.php mu-plugin instead.
  4. Playground’s networkidle event never fires. In Playwright, substitute it with waitUntil: 'load' plus an explicit waitForTimeout.
  5. The blueprint-url must be a full, absolute URL: The Playground engine does not resolve relative paths. When nginx redirects ?plugin=xxx, the blueprint-url must begin with https:// (a fix has been reported and is underway).

WPSlug v1.1.1 Acceptance Results (Local Playground CLI)

  • Settings page loads correctly; version number confirmed as 1.1.1.
  • Chinese title “测试中文标题自动转拼音” → slug ce-shi-zhong-wen-biao-ti-zi-dong-zhuan-pin-yin; pinyin conversion works correctly.
  • No PHP errors observed; Chinese UI renders fully and correctly.
  • ACF Pro compatibility introduced in v1.1.1 (e.g., excluding internal post types like acf-field-group) remains pending verification in an actual ACF-enabled environment.

Note: The live shortcut ?plugin=wpslug currently fails to load due to nginx using a relative path for blueprint-url. This issue has been reported to DevOps for resolution. As a workaround, use the full URL directly: playground.html?blueprint-url=https://play.wenpai.net/play/blueprints/wpslug.json.