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

Supplemental practical experience from the acceptance testing perspective:

Local Testing Workflow

Use the Playground CLI to load a blueprint for local acceptance testing, without relying on the online environment:

# Start an HTTP server locally to serve the ZIP file
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 allows full acceptance testing before deployment.

Pitfalls and Notes

  1. Gutenberg Welcome Modal: Blocks automated test interactions. In Playwright tests, first close it using .components-modal__header button[aria-label="Close"].
  2. Playground baseURL must be 127.0.0.1, not localhost. Using localhost causes a mismatch with WordPress internal script src attributes, resulting in CORS errors.
  3. wp-china-yes is incompatible with Playground’s WASM-based PHP runtime (CSF framework crashes with exit code 255). Replace it universally 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 waitForTimeout.
  5. blueprint-url must be a full URL: The Playground engine does not resolve relative paths. When nginx redirects ?plugin=xxx, the blueprint-url parameter must be a complete URL starting 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 is correct.
  • No PHP errors observed; Chinese UI renders completely.
  • ACF Pro compatibility introduced in v1.1.1 (e.g., excluding internal post types like acf-field-group) requires further validation in an actual ACF-enabled environment.

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