
A live demo is the best “Try it free” button a plugin can have. Visitors click once and get a working WordPress site in their browser, with your plugin active and sample data ready. No install, no signup, no risk.
WordPress Playground makes this possible, but if you host the files yourself (and not on WordPress.org), there is one step that trips most people up: CORS. This guide walks through the whole setup, step by step, using Nginx. If you use Cloudflare or Apache, there are alternatives for that step too.
How it works
Playground runs WordPress inside the visitor’s browser. You control what it loads with a small JSON file called a blueprint. The blueprint tells Playground which plugin to install, which files to add and which page to open first. You then link to Playground with your blueprint URL, and it builds the demo on the fly.
You need three files on your own server:
blueprint.json, the instructions- The plugin ZIP, for example
my-plugin-1.0.0.zip - An optional ZIP of sample data, for example uploads for a media plugin
We will use Media Library Recovery as the example. Its demo needs images in the uploads folder that are not in the Media Library, so we add a ZIP of sample uploads.
Step 1: Prepare the plugin ZIP
Build a normal installable ZIP with the plugin folder at the top level, like the one you would upload in Plugins > Add New. Include any compiled CSS and JavaScript, because Playground does not run your build.
plugin-name-1.0.0.zip
plugin-name-1.0.0/
plugin-name.php
assets/dist/...
inc/...
Only use a free or demo version here. Anything in a public Playground blueprint can be downloaded by anyone who reads the URL.
Step 2: Prepare the sample data (optional)
If your plugin needs content to show off, put it in a ZIP. For Media Library Recovery, we zipped the files from a
local site’s wp-content/uploads folder, keeping the year/month folders. The files are in the
folder, but not in the database, which is the exact case the plugin recovers.
mlr-uploads.zip
2024/03/bus-ride.jpg
2024/03/city-bicycle.png
2025/01/...
Keep it small. Playground downloads it every time someone opens the demo, so a few megabytes is plenty.
Step 3: Write the blueprint
Create blueprint.json. This one logs in as admin, installs and activates the plugin, unzips the sample
uploads and opens the plugin’s main page.
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"landingPage": "/wp-admin/admin.php?page=plugin_slug",
"preferredVersions": { "php": "8.2", "wp": "latest" },
"phpExtensionBundles": ["kitchen-sink"],
"features": { "networking": true },
"steps": [
{
"step": "login",
"username": "admin",
"password": "password"
},
{
"step": "installPlugin",
"pluginZipFile": {
"resource": "url",
"url": "https://example.com/playground/plugin-name-1.0.0.zip"
},
"options": { "activate": true }
},
{
"step": "unzip",
"zipFile": {
"resource": "url",
"url": "https://example.com/playground/mlr-uploads.zip"
},
"extractToPath": "/wordpress/wp-content/uploads"
}
]
}
Change landingPage to the admin page you want visitors to see first. You can copy the slug from the URL
of your plugin’s settings page.
Step 4: Upload the three files
Create a folder on your server, for example https://example.com/playground/, and upload the blueprint,
the plugin ZIP and the sample data ZIP into it. Open each URL in your browser to check that it downloads or loads.
If you try the demo now, it will fail. Browsers block Playground from reading files on another domain unless that server allows it. That permission is the CORS header, and we add it next.
Step 5: Add the CORS header with Nginx
Open your site’s Nginx configuration and add a location block for the folder, inside the server block of
your domain:
location /playground/ {
add_header Access-Control-Allow-Origin "*" always;
}
Test the configuration and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
The always keyword makes Nginx send the header on every response, not just successful ones. If your
config already has a block for static files, such as location ~* \.(zip|json)$, that block can win over
the one above. In that case, add the same add_header line inside it.
Alternative A: Cloudflare Snippet
If your domain is behind Cloudflare and you cannot edit the server config, add the header at the edge with a Snippet.
- In the Cloudflare dashboard, select your domain and go to Rules > Snippets > Create Snippet.
- Name it
playground_corsand paste this code:
export default {
async fetch(request) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set("Access-Control-Allow-Origin", "*");
return newResponse;
},
};
- Under Snippet rule, choose a custom filter expression and enter:
starts_with(http.request.uri.path, "/playground/")
- Click Deploy.
Do not use both Nginx and Cloudflare to set the same header. A duplicated Access-Control-Allow-Origin
header is rejected by browsers.
Alternative B: .htaccess on Apache or LiteSpeed
On Apache or LiteSpeed hosting, create a file named .htaccess inside the playground folder
with this content:
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
No restart is needed. The rule applies to every file in that folder right away.
Step 6: Check the header
Before you open Playground, confirm the header is sent for all three files. Run this for each one:
curl -I -H "Origin: https://playground.wordpress.net" https://example.com/playground/blueprint.json
Look for Access-Control-Allow-Origin: * in the response. If it is missing, see the troubleshooting list
at the end.
Step 7: Launch the demo
Your demo link is the Playground address plus your blueprint URL:
https://playground.wordpress.net/?blueprint-url=https://example.com/playground/blueprint.json
Open it. After a few seconds you should land on your plugin’s page, logged in and ready to try. Then use the same link as the target of a “Live Demo” button on your site:
<a href="https://playground.wordpress.net/?blueprint-url=https://example.com/playground/blueprint.json" target="_blank" rel="noopener">Live Demo</a>
Troubleshooting
- Header missing in curl: the config was not reloaded, or another
locationblock is handling the files. Add theadd_headerline there. - Header present but the demo still fails: open the browser console. A message about a duplicate
Access-Control-Allow-Originmeans two layers set it. Keep only one. - Old files load after an update: purge the cache for those files in Cloudflare, or rename the ZIP with the new version number and update the blueprint.
- Plugin does not activate: make sure the plugin folder is at the top level of the ZIP and the compiled assets are included.
- Blueprint 404: check the file name and that the URL opens in a normal browser tab.
Can I protect the plugin ZIP?
Not really. Playground downloads the ZIP from the visitor’s browser, so the file has to be public. Use this setup for free or demo versions only, and keep paid versions out of public blueprints.

