All notable changes to this project are documented in this file by a CI job
that runs on every NPM release. The file follows the Keep a Changelog
format.
The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @brandonpayton @flexseth @ironnysh @josevarghese
+ + + +The following contributors merged PRs in this release:
+ + + +@brandonpayton
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel
+ + + +@wp-playground/wordpress-builds
package. (#1343)The following contributors merged PRs in this release:
+ + + +@adamziel
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @brandonpayton @juanmaguitar @mho22
+ + + +iframes
are responsive. (#1267)The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @brandonpayton @ironnysh @peeranat-dan
+ + + +shorthand
alternatives of Blueprint steps. (#1261)The following contributors merged PRs in this release:
+ + + +@adamziel @dd32 @ironnysh @kozer
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @artpi @bph @brandonpayton @eliot-akira @flexseth @ironnysh @kirjavascript
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @brandonpayton
+ + + +The following contributors merged PRs in this release:
+ + + +@bgrgicak @brandonpayton
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @brandonpayton @emmanuel-ferdman @fluiddot
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @brandonpayton @seanmorris
+ + + +--disable-all
configuration option in PHP compile process. (#1132)The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @brandonpayton @flexseth @jblz @mho22
+ + + +themeFolderName
. (#1119)The following contributors merged PRs in this release:
+ + + +@0aveRyan @adamziel @bgrgicak @brandonpayton @ironnysh @mho22 @seanmorris @StevenDufresne
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @eliot-akira @reimic @renatho
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @dd32 @desrosj @johnbillion @mho22
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @mho22
+ + + +crypto.randomUUID
dependency in favor of a custom function. (#1016)The following contributors merged PRs in this release:
+ + + +@adamziel @bgrgicak @jdevalk @sejas @stoph
+ + + +crypto
to Polyfills improving Blueprint compatibility for Node. (#1000)The following contributors merged PRs in this release:
+ + + +@adamziel @sejas
+ + + +The following contributors merged PRs in this release:
+ + + +@adamziel @bph @ironnysh @marcarmengou @mho22 @rowasc @seanmorris @swissspidy @tyrann0us
+ + + +– Breaking: Remoddsaved the PHPBrowser class (##1302)
+ + + +– Added CHANGELOG.md to keep track of notable changes (##1302)
+]]>WordPress Playground exposes a simple API that you can use to configure the Playground in the browser.
+ + + +It works by passing configuration options as query parameters to the Playground URL. For example, to install the pendant theme, you would use the following URL:
+ + + +https://playground.wordpress.net/?theme=pendant
+
+
+
+
+You can go ahead and try it out. The Playground will automatically install the theme and log you in as an admin. You may even embed this URL in your website using an <iframe>
tag:
+
+
+
+| Option | Default Value | Description |
| ---------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| php
| 8.0
| Loads the specified PHP version. Supported values: 7.0
, 7.1
, 7.2
, 7.3
, 7.4
, 8.0
, 8.1
, 8.2
, 8.3
, latest
|
| wp
| latest
| Loads the specified WordPress version. Supported values: The last three major WordPress versions. As of April 4, 2024, that's 6.3
, 6.4
, 6.5
. You can also use these values: latest
, nightly
, beta
|
| blueprint-url
| | The URL of the Blueprint that will be used to configure this Playground instance. |
| php-extension-bundle
| | Loads a bundle of PHP extensions. Supported bundles: kitchen-sink
(for finfo, gd, mbstring, iconv, openssl, libxml, xml, dom, simplexml, xmlreader, xmlwriter), light
(saves 6MB of downloads, loads none of the above extensions) |
| networking
| yes
or no
| Enables or disables the networking support for Playground. Defaults to no
|
| plugin
| | Installs the specified plugin. Use the plugin name from the plugins directory URL, e.g. for a URL like https://wordpress.org/plugins/wp-lazy-loading/
, the plugin name would be wp-lazy-loading
. You can pre-install multiple plugins by saying plugin=coblocks&plugin=wp-lazy-loading&…
. Installing a plugin automatically logs the user in as an admin |
| theme
| | Installs the specified theme. Use the theme name from the themes directory URL, e.g. for a URL like https://wordpress.org/themes/disco/
, the theme name would be disco
. Installing a theme automatically logs the user in as an admin |
| url
| /wp-admin/
| Load the specified initial page displaying WordPress |
| mode
| seamless
, browser
, or browser-full-screen
| Displays WordPress on a full-page or wraps it in a browser UI |
| lazy
| | Defer loading the Playground assets until someone clicks on the "Run" button |
| login
| yes
| Logs the user in as an admin. Set to no
to not log in. |
| multisite
| no
| Enables the WordPress multisite mode. |
| storage
| | Selects the storage for Playground: none
gets erased on page refresh, browser
is stored in the browser, and device
is stored in the selected directory on a device. The last two protect the user from accidentally losing their work upon page refresh. |
| import-site
| | Imports site files and database from a zip file specified by URL. |
| import-wxr
| | Imports site content from a WXR file specified by URL. It uses the WordPress Importer, so the default admin user must be logged in. |
| site-slug
| | Selects which site to load from browser storage. This must be used in combination with storage=browser
|
For example, the following code embeds a Playground with a preinstalled Gutenberg plugin, and opens the post editor:
+ + + +
+
+
+
+
+:::info CORS policy
+ + + +To import files from a URL, such as a site zip package, they must be served with Access-Control-Allow-Origin
header set. For reference, see: Cross-Origin Resource Sharing (CORS).
:::
+ + + +The following additional query parameters may be used to pre-configure the GitHub export form:
+ + + +gh-ensure-auth
: If set to yes
, Playground will display a modal to ensure theghexport-repo-url
: The URL of the GitHub repository to export to.ghexport-pr-action
: The action to take when exporting (create or update).ghexport-playground-root
: The root directory in the Playground to export from.ghexport-repo-root
: The root directory in the repository to export to.ghexport-content-type
: The content type of the export (plugin, theme, wp-content, custom-paths).ghexport-plugin
: Plugin path. When the content type is plugin
, pre-select the plugin to export.ghexport-theme
: Theme directory name. When the content type is theme
, pre-select the theme to export.ghexport-path
: A path relative to ghexport-playground-root
. Can be provided multiple times. When thecustom-paths
, it pre-populates the list of paths to export.ghexport-commit-message
: The commit message to use when exporting.ghexport-allow-include-zip
: Whether to offer an option to include a zip file in the GitHubyes
.:::info Looking for the official Playground website?
+ + + +WordPress Playground website moved to wordpress.org/playground/. The site you're at now hosts the documentation.
+ + + +:::
+ + + +👋 Hi! Welcome to WordPress Playground documentation. Playground is an online tool to experiment and learn about WordPress – learn more in the overview section.
+ + + +The documentation consists of two major sections:
+ + + +This site (Documentation) is where you will find all the information you need to start using Playground. To learn more about what this fantastic tool, read Introduction to Playground: running WordPress in the browser
+ + + +import APIList from '@site/docs/_fragments/_api_list.mdx';
+ + + +WordPress Playground is an open-source project and welcomes all contributors from code to design, and from documentation to triage. Don't worry, you don't need to know WebAssembly to contribute!
+ + + +#meta-playground
channel in Slack (see the WordPress Slack page for signup information)As with all WordPress projects, we want to ensure a welcoming environment for everyone. With that in mind, all contributors are expected to follow our Code of Conduct.
+ + + +WordPress Playground is free software, and is released under the terms of the GNU General Public License version 2 or (at your option) any later version. See LICENSE.md. for complete license.
+ + + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
+]]>import ThisIsQueryApi from '@site/docs/_fragments/_this_is_query_api.md';
+ + + +WordPress Playground can help you with any of the following:
+ + + +import TOCInline from '@theme/TOCInline';
+ + + +This page will guide you through each of these. Oh, and if you're a visual learner – here's a video:
+ + + + + + + +Every time you visit the official demo on playground.wordpress.net, you get a fresh WordPress site.
+ + + +You can then create pages, upload plugins, themes, import your own site, and do most things you would do on a regular WordPress.
+ + + +It's that easy to start!
+ + + +The entire site lives in your browser and is scraped when you close the tab. Want to start over? Just refresh the page!
+ + + +:::info WordPress Playground is private
+ + + +Everything you build stays in your browser and is not sent anywhere. Once you're finished, you can export your site as a zip file. Or just refresh the page and start over!
+ + + +:::
+ + + +You can upload any plugin or theme you want in /wp-admin/.
+ + + +To save a few clicks, you can preinstall plugins or themes from the WordPress plugin directory by adding a plugin
or theme
parameter to the URL. For example, to install the coblocks plugin, you can use this URL:
https://playground.wordpress.net/?plugin=coblocks
+ + + +Or this URL to preinstall the pendant
theme:
https://playground.wordpress.net/?theme=pendant
+ + + +You can also mix and match these parameters and even add multiple plugins:
+ + + +https://playground.wordpress.net/?plugin=coblocks&plugin=friends&theme=pendant
+ + + +:::info Plugin directory doesn't work in WordPress Playground
+ + + +Plugins must be installed manually because your WordPress site doesn't send any data to the internet. You won't be able to navigate the WordPress plugin directory inside /wp-admin/
. The Query API method may seem to contradict that, but behind the scenes it uses the same plugin upload form as you would.
:::
+ + + +To keep your WordPress Playground site for longer than a single browser session, you can export it as a zip file.
+ + + +Use the "Export" button in the top bar:
+ + + + + + + +The exported file contains the complete site you've built. You could host it on any server that supports PHP and SQLite. All WordPress core files, plugins, themes, and everything else you've added to your site are in there.
+ + + +The SQLite database file is also included in the export, you'll find it wp-content/database/.ht.sqlite
. Keep in mind that files starting with a dot are hidden by default on most operating systems so you might need to enable the "Show hidden files" option in your file manager.
You can restore the site you saved by using the import button in WordPress Playground:
+ + + + + + + +The easiest way is to use the version switcher on the official demo site:
+ + + + + + + +:::info Test your plugin or theme
+ + + +Compatibility testing with so many WordPres and PHP versions was always a pain. WordPress Playground makes this process effortless – use it to your advantage!
+ + + +:::
+ + + +You can also use the wp
and php
query parameters to open Playground with the right versions already loaded:
:::info Major versions only
+ + + +You can specify major versions like wp=6.2
or php=8.1
and expect the most recent release in that line. You cannot, however, request older minor versions so neither wp=6.1.2
nor php=7.4.9
will work.
:::
+ + + +You can import a WordPress export file by uploading a WXR file in /wp-admin/.
+ + + +You can also use JSON Blueprints. See getting started with Blueprints to learn more.
+ + + +This is different from the import feature described above. The import feature exports the entire site, including the database. This import feature imports a WXR file into an existing site.
+ + + +WordPress Playground is programmable which means you can build WordPress apps, setup plugin demos, and even use it as a zero-setup local development environment.
+ + + +To learn more about developing with WordPress Playground, check out the development quick start section.
+]]>WordPress Playground is under active development and has some limitations you should keep in mind when running it and developing with it.
+ + + +You can track the status of these issues on the Playground Project board.
+ + + +Playground disables network connections by default, blocking access to wp.org assets (themes, plugins, blocks, or patterns) in wp-admin
. You can still upload zipped plugin and theme files from your device or enable the option via the Query API or Blueprints API.
As Playground streams rather than serves WordPress, all database changes and uploads will be gone when you refresh the page. To avoid losing your work, either export your work before or enable storage in the browser/device via the Query API or the UI.
+ + + +Playground renders WordPress in an iframe
so clicking links with target="_top"
will reload the page you’re working on.
Also, JavaScript popups originating in the iframe
may not always display.
Playground supports running PHP code in Blueprints using the runPHP
step. To run WordPress-specific PHP functions, you’d need to first require wp-load.php:
{
+ "step": "runPHP",
+ "code": ""
+}
+
+
+
+
+You can execute wp-cli
commands via the Blueprints wp-cli
step. However, since Playground runs in the browser, it doesn't support the full array of available commands. While there is no definite list of supported commands, experimenting in the online demo will help you assess what's possible.
Like all WordPress projects, Playground uses GitHub to manage code and track issues. The main repository is at https://github.com/WordPress/wordpress-playground and the Playground Tools repository is at https://github.com/WordPress/playground-tools/.
+ + + +:::info Contribute to Playground Tools
+ + + +This guide includes links to the main repository, but all the steps and options apply for both. If you're interested in the plugins or local development tools—start there.
+ + + +:::
+ + + +Browse the list of open issues to find what to work on. The Good First Issue
label is a recommended starting point for first-time contributors.
Be sure to review the following resources before you begin:
+ + + +Fork the Playground repository and clone it to your local machine. To do that, copy and paste these commands into your terminal:
+ + + +git clone -b trunk --single-branch --depth 1
+
+# replace `YOUR-GITHUB-USERNAME` with your GitHub username:
+git@github.com:YOUR-GITHUB-USERNAME/wordpress-playground.git
+cd wordpress-playground
+npm install
+
+
+
+
+Create a branch, make changes, and test it locally by running the following command:
+ + + +npm run dev
+
+
+
+
+Playground will open in a new browser tab and refresh automatically with each change.
+ + + +When your'e ready, commit the changes and submit a Pull Request.
+ + + +:::info Formatting
+ + + +We handle code formatting and linting automatically. Relax, type away, and let the machines do the work.
+ + + +:::
+ + + +WordPress Multisite has a few restrictions when run locally. If you plan to test a Multisite network using Playground's enableMultisite
step, make sure you either change wp-now
's default port or set a local test domain running via HTTPS.
To change wp-now
's default port to the one supported by WordPress Multisite, run it using the --port=80
flag:
npx @wp-now/wp-now start --port=80
+
+
+
+
+There are a few ways to set up a local test domain, including editing your hosts
file. If you're unsure how to do that, we suggest installing Laravel Valet and then running the following command:
valet proxy playground.test http://localhost:5400 --secure
+
+
+
+
+Your dev server is now available on https://playground.test.
+ + + +If you're using VS Code and have Chrome installed, you can debug Playground in the code editor:
+ + + +F5
/fn
+F5
.Playground logs PHP errors in the browser console after every PHP request.
+]]>WordPress Playground was created as a programmable tool. Below you'll find a few examples of what you can do with it. Each discussed API is described in detail in the APIs section:
+ + + +import TOCInline from '@theme/TOCInline';
+ + + +Playground can be embedded on your website using the HTML <iframe>
tag as follows:
+
+
+
+Every visitor will get their own private WordPress instance for free. You can then customize it using one of the Playground APIs.
+ + + +import PlaygroundWpNetWarning from '@site/docs/_fragments/_playground_wp_net_may_stop_working.md';
+ + + +WordPress Playground provides three APIs you can use to control the iframed website. All the examples in this section are built using one of these:
+ + + +import APIList from '@site/docs/_fragments/_api_list.mdx';
+ + + +Learn more about each of these APIs in the APIs overview section.
+ + + +import ThisIsQueryApi from '@site/docs/_fragments/_this_is_query_api.md';
+ + + +You can install plugins and themes from the WordPress directory with only URL parameters. For example this iframe would come with the coblocks
and friends
plugins preinstalled as well as the pendant
theme.
+
+
+
+What if your plugin is not in the WordPress directory?
+ + + +You can still showcase it on Playground by using JSON Blueprints. For example, this Blueprint would download and install a plugin and a theme from your website and also import some starter content:
+ + + +{
+ "steps": [
+ {
+ "step": "installPlugin",
+ "pluginZipFile": {
+ "resource": "url",
+ "url": "https://your-site.com/your-plugin.zip"
+ }
+ },
+ {
+ "step": "installTheme",
+ "themeZipFile": {
+ "resource": "url",
+ "url": "https://your-site.com/your-theme.zip"
+ }
+ },
+ {
+ "step": "importWxr",
+ "pluginZipFile": {
+ "resource": "url",
+ "url": "https://your-site.com/starter-content.wxr"
+ }
+ }
+ ]
+}
+
+
+
+
+See getting started with Blueprints to learn more.
+ + + +See the live example of Gutenberg PR previewer.
+ + + +You can use Playground as a Pull Request previewer if:
+ + + +Those zip bundles aren't any different from regular WordPress Plugins, which means you can install them in Playground using the JSON Blueprints API. Once you exposed an endpoint like https://your-site.com/pull-request-1234.zip, the following Blueprint will do the rest:
+ + + +{
+ "steps": [
+ {
+ "step": "installPlugin",
+ "pluginZipFile": {
+ "resource": "url",
+ "url": "https://your-site.com/pull-request-1234.zip"
+ }
+ }
+ ]
+}
+
+
+
+
+The official Playground demo uses this technique to preview pull requests from the Gutenberg repository:
+ + + +import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +
"landingPage": "/wp-admin/plugins.php?test=42test",
"steps": [
{
"step": "login",
"username": "admin",
"password": "password"
},
{
"step": "mkdir",
"path": "/wordpress/pr"
},
{
"step": "writeFile",
"path": "/wordpress/pr/pr.zip",
"data": {
"resource": "url",
"url": "/plugin-proxy.php?org=WordPress&repo=gutenberg&workflow=Build%20Gutenberg%20Plugin%20Zip&artifact=gutenberg-plugin&pr=60819",
"caption": "Downloading Gutenberg PR 47739"
},
"progress": {
"weight": 2,
"caption": "Applying Gutenberg PR 47739"
}
},
{
"step": "unzip",
"zipPath": "/wordpress/pr/pr.zip",
"extractToPath": "/wordpress/pr"
},
{
"step": "installPlugin",
"pluginZipFile": {
"resource": "vfs",
"path": "/wordpress/pr/gutenberg.zip"
}
}
]
}} />
A live plugin demo with a configurable PHP and WordPress makes an excellent compatibility testing environment.
+ + + +With the Query API, you'd simply add the php
and wp
query parameters to the URL:
+
+
+
+With JSON Blueprints, you'd use the preferredVersions
property:
{
+ "preferredVersions": {
+ "php": "7.4",
+ "wp": "6.1"
+ }
+}
+
+
+
+
+The JavaScript API provides the run()
method which you can use to run PHP code in the browser:
+
+
+
+Combine that with a code editor like Monako or CodeMirror, and you'll get live code snippets like in this article!
+]]>WordPress Playground consists of the following high-level components:
+ + + +Visit each section to learn more about the specific parts of the architecture.
+ + + +WordPress Playground uses NX, a build system designed for monorepos.
+ + + +The dependencies between Playground packages and projects are too complex for a bundler like Webpack, and NX handles this complexity much better:
Dependency graph
To learn more, head over to the NX developer docs.
+ + + +WordPress Playground includes several NPM packages, a VS Code extension, WordPress plugins, a web app, and other GitHub releases, all managed across two monorepos: the main wordpress-playground and Playground Tools.
+ + + +We use Lerna to build, manage, and publish all JavaScript/TypeScript packages. Lerna handles everything simultaneously: it increments the version number, sets a new tag, and publishes the modified packages to npm
.
The published packages share the same version number, so when updating a single package, Lerna bumps the version number of all dependent packages.
+]]>WordPress Playground is an open-source project that welcomes all contributors—from code to design, documentation to triage.
+ + + +Want to help sort through open issues and resolve potential bugs? Here's how:
+ + + +WordPress Playground is an online platform that allows you to experiment and learn about WordPress without affecting your live website. It's a virtual sandbox where you can play around with different features, designs, and settings in a safe and controlled environment.
+ + + +When you first start using WordPress Playground, you'll be provided with a separate space where you can create and customise your own WordPress website. This space is completely isolated from your actual website.
+ + + +Within the WordPress Playground, you can explore various themes. You can choose from a wide range of themes and see how they look on your site. You can also modify the colors, fonts, layouts, and other visual elements to create a unique design.
In addition to themes, you can experiment with plugins too. With WordPress Playground, you can install and test different plugins to see how they work and what they can do for your site. This allows you to explore and understand the capabilities of WordPress without worrying about breaking anything.
Another great feature of WordPress Playground is the ability to create and edit content. You can write blog posts, create pages and add media like images and videos to your site. This helps you understand how to organize and structure your content effectively.
+ + + +The content you create is limited to the Playground on your device and disappears once you leave it, so you are free to explore and play without risking breaking any actual site.
+ + + +Overall, WordPress Playground provides a risk-free environment for beginners to learn and get hands-on experience with WordPress. It helps you to gain confidence and knowledge before making changes to your live website.
+ + + +Web applications like WordPress have long-relied on server technologies to run logic and to store data.
+ + + +Using those technologies has meant either running an web server connected to the internet or using those technologies in a desktop service or app (sometimes called a "WordPress local environment") that either leans on a virtual server with the technologies installed or the underlying technologies on the current device.
+ + + +Playground is a novel way to stream server technologies -- and WordPress (and WP-CLI) -- as files that can then run in the browser.
+ + + +The WordPress you see when you open Playground in your browser is a WordPress that should function like any WordPress, with a few limitations and the important exception that it's not a permanent server with an internet address which will limit connections to some third-party services (automation, sharing, analysis, email, backups, etc.) in a persistient way.
+ + + +The loading screen and progress bar you see on Playground includes both the streaming of those foundational technologies to your browser and configuration steps (examples) from WordPress Blueprints, so that a full server, WordPress software, Theme & Plugin solutions and configuration instructions can be streamed over-the-wire.
+ + + +While many WordPress solutions may require internet connectivity to interact with social networks, live feeds and other internet services, those kind of connections could be limited in Playground. However, by enabling network connectivity in the Customize Playground settings modal (example URL w/ query parameter), you can mostly wire-up internet connectivity to the WordPress in Playground.
+]]>import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +Blueprints are JSON files for setting up your very own WordPress Playground instance. For example:
+ + + +{
+ "$schema": "https://playground.wordpress.net/blueprint-schema.json",
+ "landingPage": "/wp-admin/",
+ "preferredVersions": {
+ "php": "8.0",
+ "wp": "latest"
+ },
+ "phpExtensionBundles": ["kitchen-sink"],
+ "steps": [
+ {
+ "step": "login",
+ "username": "admin",
+ "password": "password"
+ }
+ ]
+}
+
+
+
+
+There are three ways to use Blueprints:
+ + + +Blueprints are just JSON. You don't need a development environment, any libraries, or even JavaScript knowledge. You can write them in any text editor.
+ + + +However, if you do have a development environment, that's great! You can use the Blueprint JSON schema to get autocompletion and validation.
+ + + +Blueprints fetch any resources you declare for you. You don't have to worry about managing multiple fetch()
calls or waiting for them to finish. You can just declare a few links and let Blueprints handle and optimize the downloading pipeline.
Because Blueprints can be pasted in the URL, you can embed or link to a Playground with a specific configuration. For example, clicking this button will open a Playground with PHP 7.4 and a pendant theme installed:
+ + + +
"php": "7.4",
"wp": "latest"
},
"steps": [
{
"step": "installTheme",
"themeZipFile": {
"resource": "wordpress.org/themes",
"slug": "pendant"
},
"options": {
"activate": true
}
}
]
}} />
Blueprints are just JSON. Running other people's Blueprints doesn't require the element of trust. Since Blueprints cannot execute arbitrary JavaScript, they are limited in what they can do.
+ + + +With Blueprints, WordPress.org plugin directory may be able to offer live previews of plugins. Plugin authors will just write a custom Blueprint to preconfigure the Playground instance with any site options or starter content they may need.
+ + + +Blueprints work both on the web and in node.js. You can run them both in the same JavaScript process, and through a remote Playground Client. They are the universal language of configuration. Where you can run Playground, you can use Blueprints.
+ + + +Enhance your understanding of Blueprints with these resources:
+ + + +The steps
property of a Blueprint is an array of steps to run. For example this Blueprint logs the user in as an admin:
import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +
{
"step": "login",
"username": "admin",
"password": "password"
}
]
}} />
Each step is an object that contains a step
property that specifies the type of step to run. The rest of the properties depend on the type of step. Learn and try each step type below.
import BlueprintStep from '@site/src/components/BlueprintsAPI/BlueprintStep';
import { BlueprintSteps, getStepAPI } from '@site/src/components/BlueprintsAPI/model';
import UpdateTopLevelToc from '@site/src/components/UpdateTopLevelToc';
tocItems={
BlueprintSteps
.map(name => ({
value: getStepAPI(name).stepId,
id: name,
level: 2
}))
} />
WordPress Playground comes with a JavaScript API client that grants you full control over your WordPress.
+ + + +:::info API here doesn't mean "REST API"
+ + + +WordPress Playground is a browser-based application.
The term API here refers to a set of functions you can
call inside JavaScript. This is not a network-based REST API.
:::
+ + + +To use the JavaScript API, you'll need:
+ + + +<iframe>
element@wp-playground/client
package (from npm or a CDN)Here's the shortest example of how to use the JavaScript API in a HTML page:
+ + + +import JSApiShortExample from '@site/docs/_fragments/_js_api_short_example.mdx';
+ + + +:::info /remote.html is a special URL
+ + + +/remote.html
is a special URL that loads the Playground
API endpoint instead of the demo app with the browser UI. Read more about the difference between /
and /remote.html
and on this page.
:::
+ + + +Now that you have a client
object, you can use it to control the website inside the iframe. There are three ways to do that:
For quick testing and debugging, the JavaScript API client is exposed as window.playground
by both index.html
and remote.html
.
> await playground.listFiles("/")
+(6) ['tmp', 'home', 'dev', 'proc', 'internal', 'wordpress']
+
+
+
+
+Note that in index.html
, playground
is a Proxy object and you won't get any autocompletion from the browser. In remote.html
,
however, playground
is a class instance and you will benefit from browser's autocompletion.
import ThisIsQueryApi from '@site/docs/_fragments/_this_is_query_api.md';
+ + + +WordPress Playground exposes a few APIs that you can use to interact with the Playground:
+ + + +import TOCInline from '@theme/TOCInline';
+ + + +Basic operations can be done by adjusting the URL, for example here's how you can preinstall a coblocks plugin:
+ + + +https://playground.wordpress.net/?plugin=coblocks
+ + + +Or a theme:
+ + + +https://playground.wordpress.net/?theme=pendant
+ + + +<ThisIsQueryApi /> Once you have a URL that you like, you can embed it in your website using an iframe:
+ + + +
+
+
+
+If you need more control over your Playground, you can use JSON Blueprints. For example, here's how to create a post and install a plugin:
+ + + +import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +{ "steps": [ { "step": "login" }, { "step": "installPlugin", "pluginZipFile": { "resource": "wordpress.org/plugins", "slug": "friends" } }, { "step": "runPHP", "code": \
include 'wordpress/wp-load.php';
wp_insert_post(array(
'post_title' => 'Post title',
'post_content' => 'Post content',
'post_status' => 'publish',
'post_author' => 1
));
`
}
]
} } blueprint={{ "steps": [ { "step": "login" }, { step: 'installPlugin', pluginZipFile: { resource: 'wordpress.org/plugins', slug: 'friends', }, }, { "step": "runPHP", "code":
include 'wordpress/wp-load.php';
wp_insert_post(array(
'post_title' => 'Post title',
'post_content' => 'Post content',
'post_status' => 'publish',
'post_author' => 1
));
`
}
]
}} />
Learn more about JSON Blueprints.
+ + + +The @wp-playground/client
package provides a JavaScript API you can use to fully control your Playground instance. Here's a very example of what you can do:
import JSApiShortExample from '@site/docs/_fragments/_js_api_short_example.mdx';
+ + + +Learn more about the JavaScript API.
+]]>WordPress, as a PHP application, can run on PHP WebAssembly. However, there are a few caveats.
+ + + +First, WordPress requires MySQL. However, there isn't a WebAssembly version of MySQL you could run in the browser. WordPress Playground, therefore, ships PHP with the native SQLite driver and leans on SQLite.
+ + + +But how can WordPress run on a different database?
+ + + +Behind the scenes, the official SQLite Database Integration plugin intercepts all MySQL queries and rewrites them in SQLite dialect. The x.0 release ships a new WordPress Playground-informed translation layer that allows WordPress on SQLite to pass 99% of the WordPress unit test suite.
+ + + +WordPress Playground ships a bundled WordPress that you can use in the browser. It's optimized for size, and the installation wizard is run for you.
+ + + +In Node.js, you'll typically want to mount WordPress from a disk directory.
+]]>:::danger Work in Progress
+ + + +WordPress Playground is under active development and the features described in this document
may break or change without a warning.
import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +Let's see some cool things you can do with Blueprints.
+ + + +
{
"step": "installPlugin",
"pluginZipFile": {
"resource": "wordpress.org/plugins",
"slug": "coblocks"
}
},
{
"step": "installTheme",
"themeZipFile": {
"resource": "wordpress.org/themes",
"slug": "pendant"
}
}
]
}} />
{ "steps": [ { "step": "runPHP", "code": "<?php include 'wordpress/wp-load.php'; wp_insert_post(array( 'post_title' => 'Post title', 'post_content' => 'Post content', 'post_status' => 'publish', 'post_author' => 1 )); " } ] }
}
blueprint={{
"steps": [
{
"step": "runPHP",
"code": <?php include 'wordpress/wp-load.php'; wp_insert_post(array( 'post_title' => 'Post title', 'post_content' => 'Post content', 'post_status' => 'publish', 'post_author' => 1 ));
}
]
}} />
Here: Switch on the "new admin views" feature.
+ + + +{ "steps": [ { "step": "runPHP", "code": "<?php require '/wordpress/wp-load.php'; update_option( 'gutenberg-experiments', array( 'gutenberg-dataviews' => true ) );" } ] }
}
blueprint={{
"steps": [
{
"step": "runPHP",
"code": " true ) );"
}
]
}} />
{
"step": "installPlugin",
"pluginZipFile": {
"resource": "url",
"url": "https://your-site.com/your-plugin.zip"
}
},
{
"step": "installTheme",
"themeZipFile": {
"resource": "url",
"url": "https://your-site.com/your-theme.zip"
}
},
{
"step": "importWxr",
"file": {
"resource": "url",
"url": "https://your-site.com/starter-content.wxr"
}
},
{
"step": "setSiteOptions",
"options": {
"some_required_option_1": "your_favorite_values",
"some_required_option_2": "your_favorite_values"
}
}
]
}} />
"phpExtensionBundles": [
"kitchen-sink"
],
"features": {
"networking": true
},
"steps": [
{
"step": "login"
}
]
}} />
Use the writeFile
step to add code to a mu-plugin that runs on every request.
"phpExtensionBundles": [
"kitchen-sink"
],
"features": {
"networking": true
},
"steps": [
{
"step": "login"
},
{
"step": "writeFile",
"path": "/wordpress/wp-content/mu-plugins/rewrite.php",
"data": "set_permalink_structure('/%postname%/'); $wp_rewrite->flush_rules(); } );"
}
]
}} />
"steps": [
{
"step": "login",
"username": "admin",
"password": "password"
},
{
"step": "installPlugin",
"pluginZipFile": {
"resource": "wordpress.org/plugins",
"slug": "interactive-code-block"
}
},
{
"step": "runPHP",
"code": " 'WordPress Playground block demo!','post_content' => '<!-- wp:wordpress-playground/playground /-->', 'post_status' => 'publish', 'post_type' => 'post',]);"
}
]
}} />
You can share your own Blueprint examples in this dedicated wiki.
+ + + +Playground only ships with a few recent WordPress releases. If you need to use an older version, this Blueprint can help you: change the version number in "url": "https://playground.wordpress.net/plugin-proxy.php?url=https://wordpress.org/wordpress-5.9.9.zip"
from 5.9.9
to the release you want to load.
Note: the oldest supported WordPress version is 5.9.9
, following the SQLite integration plugin.
"steps": [
{
"step": "writeFile",
"path": "/tmp/wordpress.zip",
"data": {
"resource": "url",
"url": "https://playground.wordpress.net/plugin-proxy.php?url=https://wordpress.org/wordpress-5.9.9.zip",
"caption": "Downloading the WordPress Release"
}
},
{
"step": "importWordPressFiles",
"wordPressFilesZip": {
"resource": "vfs",
"path": "/tmp/wordpress.zip"
},
"pathInZip": "/wordpress",
"progress": {
"weight": 20,
"caption": "Importing the WordPress release"
}
},
{
"step": "runPHP",
"code": "},
{
"step": "login",
"username": "admin",
"password": "password"
}
]
}} />
This is called Query API and you can learn more about it here.
+]]>@wp-now/wp-now
is a CLI tool to spin up a WordPress site with a single command. Similarly to the VS Code extension, it uses a portable WebAssembly version of PHP and SQLite. No Docker, MySQL, or Apache are required.
Navigate to your plugin or theme directory and start wp-now
with the following commands:
cd my-plugin-or-theme-directory
+npx @wp-now/wp-now start
+
+
+
+
+wp-content
directory with optionsYou can also start wp-now
from any wp-content
folder. The following example passes parameters for changing the PHP and WordPress versions and loading a blueprint file.
cd my-wordpress-folder/wp-content
+npx @wp-now/wp-now start --wp=6.4 --php=8.0 --blueprint=path/to/blueprint.json
+
+
+
+
+:::info Documentation
+ + + +wp-now
is maintained in a different GitHub repository, Playground Tools. You can find the latest documentation in the dedicated README file.
:::
+ + + +Alternatively, you can install @wp-now/wp-now
globally to load it from any directory:
npm install -g @wp-now/wp-now
+cd my-plugin-or-theme-directory
+wp-now start
+
+]]>Resource References allow you use external files in Blueprints
+ + + +In the installPlugin
step in the example above, we reference the https://downloads.wordpress.org/plugins/friends.latest-stable.zip
file by using the wordpress.org/plugins
resource reference.
The following resource references are available:
+ + + +import TOCInline from '@theme/TOCInline';
+ + + +The URLReference resource is used to reference files that are stored on a remote server. The URLReference resource is defined as follows:
+ + + +type URLReference = {
+ resource: 'url';
+ url: string;
+};
+
+
+
+
+To use the URLReference resource, you need to provide the URL of the file. For example, to reference a file named "index.html" that is stored on a remote server, you can create a URLReference as follows:
+ + + +{
+ "resource": "url",
+ "url": "https://example.com/index.html"
+}
+
+
+
+
+The CoreThemeReference resource is used to reference WordPress core themes. The CoreThemeReference resource is defined as follows:
+ + + +type CoreThemeReference = {
+ resource: 'wordpress.org/themes';
+ slug: string;
+ version?: string;
+};
+
+
+
+
+To use the CoreThemeReference resource, you need to provide the slug of the theme. For example, to reference the "Twenty Twenty-One" theme, you can create a CoreThemeReference as follows:
+ + + +{
+ "resource": "wordpress.org/themes",
+ "slug": "twentytwentyone"
+}
+
+
+
+
+The CorePluginReference resource is used to reference WordPress core plugins. The CorePluginReference resource is defined as follows:
+ + + +type CorePluginReference = {
+ resource: 'wordpress.org/plugins';
+ slug: string;
+ version?: string;
+};
+
+
+
+
+To use the CorePluginReference resource, you need to provide the slug of the plugin. For example, to reference the "Akismet" plugin, you can create a CorePluginReference as follows:
+ + + +{
+ "resource": "wordpress.org/plugins",
+ "slug": "akismet"
+}
+
+
+
+
+The VFSReference resource is used to reference files that are stored in a virtual file system (VFS). The VFS is a file system that is stored in memory and can be used to store files that are not part of the file system of the operating system. The VFSReference resource is defined as follows:
+ + + +type VFSReference = {
+ resource: 'vfs';
+ path: string;
+};
+
+
+
+
+To use the VFSReference resource, you need to provide the path to the file in the VFS. For example, to reference a file named "index.html" that is stored in the root of the VFS, you can create a VFSReference as follows:
+ + + +{
+ "resource": "vfs",
+ "path": "/index.html"
+}
+
+
+
+
+The LiteralReference resource is used to reference files that are stored as literals in the code. The LiteralReference resource is defined as follows:
+ + + +type LiteralReference = {
+ resource: 'literal';
+ name: string;
+ contents: string | Uint8Array;
+};
+
+
+
+
+To use the LiteralReference resource, you need to provide the name of the file and its contents. For example, to reference a file named "index.html" that contains the text "Hello, World!", you can create a LiteralReference as follows:
+ + + +{
+ "resource": "literal",
+ "name": "index.html",
+ "contents": "Hello, World!"
+}
+
+]]>You can mount a directory from the browser to Playground using the window.showDirectoryPicker
API. Check the Browser compatibility before using this API.
window.showDirectoryPicker().then(function (directoryHandle) {
+ window.parent.postMessage({
+ type: 'mount-directory-handle',
+ directoryHandle,
+ mountpoint: '/wordpress/wp-content/uploads/markdown/',
+ });
+});
+
+]]>A Blueprint can contain the following properties:
+ + + +Here's a Blueprint that uses all of them:
+ + + +import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +
"preferredVersions": {
"php": "7.4",
"wp": "6.5"
},
"phpExtensionBundles": ["kitchen-sink"],
"features": {
"networking": true
},
"steps": [
{
"step": "login",
"username": "admin",
"password": "password"
}
]
}} />
JSON files can be tedious to write and easy to get wrong. To help with that, Playground provides a JSON schema file that you can use to get autocompletion and validation in your editor:
+ + + +{
+ "$schema": "https://playground.wordpress.net/blueprint-schema.json",
+ "landingPage": "/wp-admin/",
+ // ...
+}
+
+
+
+
+The preferredVersions
property, unsurprisingly, declares the preferred of PHP and WordPress versions to use. It can contain the following properties:
php
(string): The preferred PHP version to use. Defaults to "latest". Only accepts major versions, like "7.4" or "8.0". Minor versions like "7.4.1" are not supported.wp
(string): Loads the specified WordPress version. Supported values: The last three major WordPress versions—minor versions, like 6.5.1
, are not supported. As of April 4, 2024, that's 6.3
, 6.4
, 6.5
. You can also use these values: latest
(default), nightly
, or beta
.The phpExtensionBundles
property is an array of PHP extension bundles to load. The following bundles are supported:
light
: Default choice. Saves 6MB of downloads, loads none of the extensions below.kitchen-sink
: Installs gd
, mbstring
, iconv
, openssl
, libxml
, xml
, dom
, simplexml
, xmlreader
, xmlwriter
The features
property is used to enable or disable certain features of the Playground. It can contain the following properties:
networking
: Defaults to false
. Enables or disables the networking support for Playground. If enabled, wp_safe_remote_get
and similar WordPress functions will actually use fetch()
to make HTTP requests. If disabled, they will immediately fail instead.Asyncify lets synchronous C or C++ code interact with asynchronous JavaScript. Technically, it saves the entire C call stack before yielding control back to JavaScript, and then restores it when the asynchronous call is finished. This is called stack switching.
+ + + +Networking support in the WebAssembly PHP build is implemented using Asyncify. When PHP makes a network request, it yields control back to JavaScript, which makes the request, and then resumes PHP when the response is ready. It works well enough that PHP build can request web APIs, install composer packages, and even connect to a MySQL server.
+ + + +Stack switching requires wrapping all C functions that may be found at a call stack at a time of making an asynchronous call. Blanket-wrapping of every single C function adds a significant overhead, which is why we maintain a list of specific function names:
+ + + +https://github.com/WordPress/wordpress-playground/blob/15a660940ee9b4a332965ba2a987f6fda0c159b1/packages/php-wasm/compile/Dockerfile#L624-L632
+ + + +Unfortunately, missing even a single item from that list results in a WebAssembly crash whenever that function is a part of the call stack when an asynchronous call is made. It looks like this:
+ + + + + + + +Asyncify can auto-list all the required C functions when built without ASYNCIFY_ONLY
, but that auto-detection is overeager and ends up listing about 70,000 C functions which increases the startup time to 4.5s. That's why we maintain the list manually.
If you are interested in more details, see GitHub issue 251.
+ + + +Pull Request 253 adds a fix-asyncify
command that runs a specialized test suite and automatically adds any identified missing C functions to the ASYNCIFY_ONLY
list.
If you run into a crash like the one above, you can fix it by:
+ + + +packages/php-wasm/node/src/test/php-asyncify.spec.ts
npm run fix-asyncify
Eventually, V8 will likely handle stack switching for us and remove this problem entirely. Issue 134 tracks the status of that effort.
+ + + +Here's a relevant note from @fgmccabe:
+ + + +++]]>The current implementation in V8 is essentially 'experimental status'. We have arm64 and x64 implementations.
+
The next steps are to implement on 32 bit arm/intel. That requires us to solve some issues that we did not have to solve so far.
As for node.js, my guess is that it is already in node, behind a flag.
To remove the flag requirement involves getting other implementations. The best estimate for that is towards the end of this year; but it obviously depends on resources and funding.
In addition, it would need further progress in the standardization effort; but, given that it is a 'small' spec, that should not be a long term burden.
Hope that this helps you understand the roadmap :)
WordPress Playground's documentation site is maintained by volunteers like you, who'd love your help.
+ + + +All documentation-related topics are labeled documentation
. Browse the list of open issues to find one you'd like to work on. Alternatively, if you believe something is missing from the current documentation, open an issue to discuss your suggestion.
You can contribute by opening an issue in the project repository and describing what you'd like to add or change.
+ + + +If you feel up to it, write the content in the issue description, and the project contributors will take care of the rest.
+ + + +If you are familiar with markdown, you can propose changes and new documentation pages by submitting a Pull Request.
+ + + +The documentation is stored in Playground's GitHub repository, under /packages/docs/site/docs
.
That's it! You've just contributed to the WordPress Playground documentation.
+ + + +This approach means you don't need to clone the repository, set up a local development environment, or run any commands.
+ + + +The downside is that you won't be able to preview your changes. Keep reading to learn how to review your changes before submitting a Pull Request.
+ + + +Clone the repository and navigate to the directory on your device. Now run the following commands:
+ + + +npm install
+npm run build:docs
+npm run dev:docs
+
+
+
+
+The documentation site opens in a new browser tab and refreshes automatically with each change. Continue to edit the relevant file in your code editor and test the changes in real-time.
+]]>Scopes keep your app working when you open it in two different browser tabs.
+ + + +The Service Worker passes the intercepted HTTP requests to the PHPRequestHandler for rendering. Technically, it sends a message through a BroadcastChannel
which then gets delivered to every browser tab where the application is open. This is undesirable, slow, and leads to unexpected behaviors.
Unfortunately, the Service Worker cannot directly communicate with the relevant Worker Thread – see PR #31 and issue #9 for more details.
+ + + +Scopes enable each browser tab to:
+ + + +BroadcastChannel
messages with a different idTechnically, a scope is a string included in the PHPRequestHandler.absoluteUrl
. For example:
/index.php
would be available at http://localhost:8778/wp-login.php
/index.php
would be available at http://localhost:8778/scope:96253/wp-login.php
The service worker is aware of this concept and will attach the /scope:
found in the request URL to the related BroadcastChannel
communication.
A worker thread initiated with a scoped absoluteUrl
is said to be scoped:
import {
+ PHP,
+ setURLScope,
+ exposeAPI,
+ parseWorkerStartupOptions,
+} from '@php-wasm/web';
+
+// Don't use the absoluteURL directly:
+const absoluteURL = 'http://127.0.0.1'
+
+// Instead, set the scope first:
+const scope = Math.random().toFixed(16)
+const scopedURL = setURLScope(absoluteURL, scope).toString()
+
+const { phpVersion } = parseWorkerStartupOptions<{ phpVersion?: string }>();
+const php = await PHP.load('8.0', {
+ requestHandler: {
+ documentRoot: '/',
+ absoluteUrl: scopedSiteUrl
+ }
+});
+
+// Expose the API to app.ts:
+const [setApiReady, ] = exposeAPI( php );
+setApiReady();
+
+]]>WordPress Playground in the browser is all about links and iframes. Regardless of which API you choose, you will use it in one of the following ways:
+ + + +You can customize WordPress Playground by modifying the https://playground.wordpress.net/ link. You can, for example, create a post, request a specific plugin, or run any PHP code.
+ + + +To prepare such a link, use either the Query API (easy) or the JSON Blueprints API (medium).
+ + + +Once it's ready, simply post it on your site. It makes a great "Try it yourself" button in a tutorial, for example.
+ + + +<iframe>
WordPress Playground can be embedded in your app using an <iframe>
:
+
+
+
+To customize that Playground instance, you can:
+ + + +The JavaScript API gives you the most control, but it is also the least convenient option as it requires loading the Playground Client library.
+ + + +import PlaygroundWpNetWarning from '@site/docs/_fragments/_playground_wp_net_may_stop_working.md';
+ + + +The following Playground APIs are available in the browser:
+ + + +import APIList from '@site/docs/_fragments/_api_list.mdx';
+ + + +The following Playground APIs are available in Node.js:
+ + + +These APIs are very similar to their web counterparts, but, unsurprisingly, they are not based or links or iframes.
+]]>The WordPress Playground VS Code extension and wp-now streamline the process of setting up a local WordPress environment. WordPress Playground powers both—no Docker, MySQL, or Apache required.
+ + + +Keep reading to learn how to use these tools for local development when contributing to WordPress. Please note that the extension and the NPM package are under development, and not all Make WordPress teams are fully supported.
+ + + +If you’re a visual learner, here’s a step-by-step video tutorial. If you prefer reading at your own pace, skip to the written instructions below.
+ + + + + + + +The Visual Studio Code Playground extension is a friendly zero-setup development environment.
+ + + +@wp-now/wp-now
is a CLI tool that allows you to spin up a WordPress site with a single command. No Docker, MySQL, or Apache are required.
wp-now
requires Node.js and NPM. If you haven’t yet, download and install both before you begin.
Depending on the Make WordPress team you contribute to, you may need a different Node.js version than the one you have installed. You can use Node Version Manager (NVM) to switch between versions. Find the installation guide here.
+ + + +You don’t have to install wp-now
on your device to use it. Navigate to your plugin or theme directory and start wp-now
with the following commands:
cd my-plugin-or-theme-directory
+npx @wp-now/wp-now start
+
+
+
+
+git clone git@github.com:WordPress/gutenberg.git
+cd gutenberg
+npm install
+npm run dev
+
+
+
+
+:::info
+ + + +If you’re unsure about the steps listed above, visit the official Gutenberg Project Contributor Guide. Note that in this case, wp-now
replaces wp-env
.
:::
+ + + +Open a new terminal terminal tab, navigate to the Gutenberg directory, and start WordPress using wp-now
:
cd gutenberg
+npx @wp-now/wp-now start
+
+
+
+
+When you’re ready, commit and push your changes to your forked repository on GitHub and open a Pull Request on the Gutenberg repository.
+ + + +# copy the branch-name from GitHub #
+git checkout branch-name
+git pull
+npm install
+npm run dev
+
+# In a different terminal inside the Gutenberg directory *
+npx @wp-now/wp-now start
+
+
+
+
+You don’t need a local development environment to test Gutenberg PRs—use Playground to do it directly in the browser.
+ + + +You can translate supported WordPress Plugins by loading the plugin you want to translate and use Inline Translation. If the plugin developers have added the option, you'll find the Translate Live link on the top right toolbar of the translation view. You can read more about this exciting new option on this Polyglots blog post.
+ + + +Have a question or an idea for a new feature? Found a bug? Something’s not working as expected? We’re here to help:
+ + + +On a high level, WordPress Playground works in web browsers as follows:
+ + + +index.html
file on playground.wordpress.net loads the remote.html
file via an <iframe src="/remote.html">
.remote.html
starts a Worker Thread and a ServiceWorker and sends back the download progress information.remote.html
creates an <iframe src="/index.php">
, and the Service Worker forwards the index.php
request to the Worker Thread where the WordPress homepage is rendered.Visually, it looks like this:
+ + + + + + + +The @php-wasm/web
is built on top of the following ideas:
A good error message tells the user what to do next. Any ambiguity in errors thrown by Playground public APIs will prompt the developers to open issues.
+ + + +Consider a network error, for example—can we infer the type of error and display a relevant message summarizing the next steps?
+ + + +We handle code formatting and linting automatically. Relax, type away, and let the machines do the work.
+ + + +Playground aims to keep the narrowest possible API scope.
+ + + +Public APIs are easy to add and hard to remove. It only takes one PR to introduce a new API, but it may take a thousand to remove it, especially if other projects have already consumed it.
+ + + +Blueprints are the primary way to interact with Playground. These JSON files describe a set of steps that Playground executes in order.
+ + + +Blueprint steps should be concise and focused. They should do one thing and do it well.
+ + + +Blueprints should be intuitive and straightforward.
+ + + +slug
instead of path
.WordPress Playground build the PHP interpreter to WebAssembly using Emscripten and a dedicated pipeline.
+ + + + + + + +Building PHP to WebAssembly is very similar to building vanilla PHP. The wasm build required adjusting a function signature here, forcing a config variable there, and applying a few small patches, but there's relatively few adjustments involved.
+ + + + + + + +However, vanilla PHP builds aren't very useful in the browser. As a server software, PHP doesn't have a JavaScript API to pass the request body, upload files, or populate the php://stdin
stream. WordPress Playground had to build one from scratch. The WebAssembly binary comes with a dedicated PHP API module written in C and a JavaScript PHP class that exposes methods like writeFile() or run().
Because every PHP version is just a static .wasm file, the PHP version switcher is actually pretty boring. It simply tells the browser to download, for example, php_7_3.wasm
instead of, say, php_8_2.wasm
.
When it comes to networking, WebAssembly programs are limited to calling JavaScript APIs. It is a safety feature, but also presents a challenge. How do you support low-level, synchronous networking code used by PHP with the high-level asynchronous APIs available in JavaScript?
+ + + +In Node.js, the answer involves a WebSocket to TCP socket proxy, Asyncify, and patching deep PHP internals like php_select. It's complex, but there's a reward. The Node.js-targeted PHP build can request web APIs, install composer packages, and even connect to a MySQL server.
+ + + +In the browser, networking is supported to a limited extent. Network calls initiated using wp_safe_remote_get
, like the ones in the plugin directory or the font library, are translated into fetch()
calls and succeed if the remote server sends the correct CORS headers. However, a full support for arbitrary HTTPS connection involves opening a raw TCP socket which is not possible in the browser. There is an open GitHub issue that explores possible ways of addressing this problem.
The web bundler Dockerfile turns a vanilla WordPress into a browser-optimized one:
+ + + +Build a new bundle with nx bundle-wordpress playground-wordpress-builds --wp-version=<version>
, e.g.:
nx bundle-wordpress playground-wordpress-builds --wp-version=6.1
+
+
+
+
+The bundler outputs:
+ + + +packages/playground/wordpress-builds/public/wp-6.1.zip
– zipped WordPress filespackages/playground/wordpress-builds/public/wp-6.1/
– a directory with static assets for the specified WordPress versionsConsult the web bundler Dockerfile for more details (like the list of supported WordPress versions) and modify it to customize the default WordPress installation.
+]]>As a WebAssembly project, you can also use WordPress Playground in Node.js.
+ + + +If you need low-level control over the underlying WebAssembly PHP build, take a look at the @php-wasm/node
package which ships the PHP WebAssembly runtime. This package is at the core of all WordPress Playground tools for Node.js.
:::info API reference
+ + + +Consult the complete list of Classes, Functions, Interfaces, and Type Aliases.
+ + + +:::
+ + + +import PHPWASMNode from '@php-wasm/node/\README.md';
+ + + +You can use Blueprints in one of two ways:
+ + + +The easiest way to start using Blueprints is to paste one into the URL "fragment" on WordPress Playground website, e.g. https://playground.wordpress.net/#{"preferredVersions...
.
For example, to create a Playground with specific versions of WordPress and PHP you would use the following Blueprint:
+ + + +{
+ "$schema": "https://playground.wordpress.net/blueprint-schema.json",
+ "preferredVersions": {
+ "php": "7.4",
+ "wp": "6.5"
+ }
+}
+
+
+
+
+And then you would go tohttps://playground.wordpress.net/#{"preferredVersions": {"php":"7.4", "wp":"6.5"}}
.
You won't have to paste links to follow along. We'll use code examples with a "Try it out" button that will automatically run the examples for you:
+ + + +import BlueprintExample from '@site/src/components/Blueprints/BlueprintExample.mdx';
+ + + +
"php": "7.4",
"wp": "6.5"
}
}} />
Some tools, including GitHub, might not format the Blueprint correctly when pasted into the URL. In such cases, encode your Blueprint in Base64 and append it to the URL. For example, that's the above Blueprint in Base64 format: eyIkc2NoZW1hIjogImh0dHBzOi8vcGxheWdyb3VuZC53b3JkcHJlc3MubmV0L2JsdWVwcmludC1zY2hlbWEuanNvbiIsInByZWZlcnJlZFZlcnNpb25zIjogeyJwaHAiOiAiNy40Iiwid3AiOiAiNi41In19
.
To run it, go to https://playground.wordpress.net/#eyIkc2NoZW1hIjogImh0dHBzOi8vcGxheWdyb3VuZC53b3JkcHJlc3MubmV0L2JsdWVwcmludC1zY2hlbWEuanNvbiIsInByZWZlcnJlZFZlcnNpb25zIjogeyJwaHAiOiAiNy40Iiwid3AiOiAiNi41In19
+ + + +When your Blueprint gets too wieldy, you can load it via the ?blueprint-url
query parameter in the URL, like this:
Note that the Blueprint must be publicly accessible and served with the correct Access-Control-Allow-Origin
header:
Access-Control-Allow-Origin: *
+
+
+
+
+You can also use Blueprints with the JavaScript API using the startPlaygroundWeb()
function from the @wp-playground/client
package. Here's a small, self-contained example you can run on JSFiddle or CodePen:
+]]>You can specify some steps
using a shorthand
syntax. The following steps
are currently supported:
login
Use
+ + + + "login": true,
+
+
+
+
+Or
+ + + +{
+ "step": "login",
+ "username": "admin",
+ "password": "password"
+}
+
+
+
+
+plugins
(replaces the installPlugin
step)
Use
+ + + + "plugins": [
+ "hello-dolly",
+ "https://raw.githubusercontent.com/adamziel/blueprints/trunk/docs/assets/hello-from-the-dashboard.zip"
+ ]
+
+
+
+
+Or
+ + + +[
+ {
+ "step": "installPlugin",
+ "pluginZipFile": {
+ "resource": "wordpress.org/plugins",
+ "slug": "hello-dolly"
+ }
+ },
+ {
+ "step": "installPlugin",
+ "pluginZipFile": {
+ "resource": "url",
+ "url": "https://raw.githubusercontent.com/adamziel/blueprints/trunk/docs/assets/hello-from-the-dashboard.zip"
+ }
+ }
+]
+
+
+
+
+siteOptions
Use
+ + + + "siteOptions": {
+ "blogname": "My first Blueprint"
+ }
+
+
+
+
+Or
+ + + + "step": "setSiteOptions",
+ "options": {
+ "blogname": "My first Blueprint"
+ }
+
+
+
+
+defineWpConfigConsts
(constants
only)
Use
+ + + +{
+ "step": "defineWpConfigConsts",
+ "consts": {
+ "WP_DISABLE_FATAL_ERROR_HANDLER": true,
+ "WP_DEBUG": true,
+ "WP_DEBUG_DISPLAY": true
+ }
+}
+
+
+
+
+Or
+ + + + {
+ "step": "defineWpConfigConsts",
+ "consts": {
+ "WP_DISABLE_FATAL_ERROR_HANDLER": true
+ }
+ },
+ {
+ "step": "defineWpConfigConsts",
+ "consts": {
+ "WP_DEBUG": true
+ }
+ },
+ {
+ "step": "defineWpConfigConsts",
+ "consts": {
+ "WP_DEBUG_DISPLAY": true
+ }
+ }
+
+
+
+
+The shorthand
syntax and the step
syntax correspond to each other. Every step
specified with the shorthand
syntax is added to the top of the steps
array in arbitrary order.
:::info Which should you choose?
+ + + +shorthands
when brevity is your main concern.steps
when you need more control over the execution order.:::
+]]>The build pipeline lives in a Dockerfile
. It was originally forked from seanmorris/php-wasm
In broad strokes, that Dockerfile
:
build-essential
)sqlite3
.php_wasm.c
– a convenient API for JavaScript.php.wasm
file and one or more JavaScript loaders, depending on the configuration.php.js
output into an ESM module with additional features.To find out more about each step, refer directly to the Dockerfile.
+ + + +To build all PHP versions, run nx recompile-php:all php-wasm-web
(or php-wasm-node
) in the repository root. You'll find the output files in packages/php-wasm/php-web/public
. To build a specific version, run nx recompile-php:all php-wasm-node --PHP_VERSION=8.0
.
PHP is built with several extensions listed in the Dockerfile
.
Some extensions, like zip
, can be turned on or off during the build. Others, like sqlite3
, are hardcoded.
If you need to turn off one of the hardcoded extensions, feel free to open an issue in this repo. Better yet, this project needs contributors. You are more than welcome to open a PR and author the change you need.
+ + + +The C API exposed to JavaScript lives in the php_wasm.c
file. The most important functions are:
void phpwasm_init()
– It creates a new PHP context and must be called before running any PHP code.int phpwasm_run(char *code)
– Runs a PHP script and writes the output to /tmp/stdout and /tmp/stderr. Returns the exit code.void phpwasm_refresh()
– Destroy the current PHP context and starts a new one. Call it after running one PHP script and before running another.Refer to the inline documentation in php_wasm.c
to learn more.
The build is configurable via the Docker --build-arg
feature. You can set them up through the build.js
script, just run this command to get the usage message:
nx recompile-php php-wasm-web
+
+
+
+
+Supported build options:
+ + + +PHP_VERSION
– The PHP version to build, default: 8.0.24
. This value must point to an existing branch of the https://github.com/php/php-src.git repository when prefixed with PHP-
. For example, 7.4.0
is valid because the branch PHP-7.4.0
exists, but just 7
is invalid because there's no branch PHP-7
. The PHP versions that are known to work are 7.4.*
and 8.0.*
. Others likely work as well but they haven't been tried.EMSCRIPTEN_ENVIRONMENT
– web
or node
, default: web
. The platform to build for. When building for web
, two JavaScript loaders will be created: php-web.js
and php-webworker.js
. When building for Node.js, only one loader called php-node.js
will be created.WITH_LIBXML
– yes
or no
, default: no
. Whether to build with libxml2
and the dom
, xml
, and simplexml
PHP extensions (DOMDocument
, SimpleXML
, ..).WITH_LIBZIP
– yes
or no
, default: yes
. Whether to build with zlib
, libzip
, and the zip
PHP extension (ZipArchive
).WITH_NODEFS
– yes
or no
, default: no
. Whether to include the Emscripten's NODEFS JavaScript library. It's useful for loading files and mounting directories from the local filesystem when running php.wasm from Node.js.WordPress requires MySQL. However, there isn't a WebAssembly version of MySQL you could run in the browser. WordPress Playground therefore ships PHP with the native SQLite driver and leans on SQLite.
+ + + +But how can WordPress run on a different database?
+ + + +Behind the scenes, the official SQLite Database Integration plugin intercepts all MySQL queries and rewrites them in SQLite dialect. The 2.0 release ships a new WordPress Playground-informed translation layer that allows WordPress on SQLite to pass 99% of the WordPress unit test suite.
+]]>The PHP module has its own filesystem separate from your computer's filesystem. It is provided by Emscripten's FS library and the default APIs is low-level and cumbersome to use. The PHP
JavaScript class shipped with WordPress Playground wraps it with a more convenient higher-level API.
In general, WordPress Playground uses an in-memory virtual filesystem.
+ + + +However, in Node.js, you can also mount a real directory from the host filesystem into the PHP filesystem.
+ + + +Here's how to interact with the filesystem in WordPress Playground:
+ + + +// Recursively create a /var/www directory
+php.mkdirTree('/var/www');
+
+console.log(php.fileExists('/var/www/file.txt'));
+// false
+
+php.writeFile('/var/www/file.txt', 'Hello from the filesystem!');
+
+console.log(php.fileExists('/var/www/file.txt'));
+// true
+
+console.log(php.readFile('/var/www/file.txt'));
+// "Hello from the filesystem!
+
+// Delete the file:
+php.unlink('/var/www/file.txt');
+
+
+
+
+For more details consult the BasePHP class directly – it has some great documentation strings.
+]]>Start a zero-setup development environment using the VS Code extension, and develop your plugin or theme locally without installing Apache or MySQL.
+ + + +:::info Documentation
+ + + +The VS Code extension is maintained in a different GitHub repository, Playground Tools. You can find the latest documentation in the dedicated README file.
+ + + +:::
+ + + +The extension ships with a portable WebAssembly version of PHP and sets up WordPress to use SQLite. Once installed, all you have to do is click the Start WordPress Server button in VS Code:
+ + + +import Image from '@theme/IdealImage';
import vsCodeScreenshot from '@site/static/img/start-wordpress-server.png';
The PlaygroundClient
object implements the UniversalPHP
interface. All the methods from that interface are also available in Node.js and same-process PHP instances (Playground runs PHP in a web worker).
Broadly speaking, you can use the client to perform three types of operations:
+ + + +The two methods you can use to run PHP code are:
+ + + +run()
- runs PHP code and returns the outputrequest()
- makes an HTTP request to the websiteIn Node.js, you can also use the cli()
method to run PHP in a CLI mode.
import TSDocstring from '@site/src/components/TSDocstring';
+ + + +The API client also allows you to change the php.ini file:
+ + + +await setPhpIniEntries(client, {
+ display_errors: 'On',
+ error_reporting: 'E_ALL',
+});
+
+
+
+
+The client
object provides you with a low-level API for managing files and directories in the PHP filesystem:
await client.mkdirTree('/wordpress/test');
+// Create a new PHP file
+await client.writeFile(
+ '/wordpress/test/index.php',
+ ` echo "Hello, world!
+";
+ // List all the files in current directory
+ print_r(glob(__DIR__ . '/*'));
+ `
+);
+// Create files named 1, 2, and 3
+await client.writeFile('/wordpress/test/1', '');
+await client.writeFile('/wordpress/test/2', '');
+await client.writeFile('/wordpress/test/3', '');
+// Remove the file named 1
+await client.unlink('/wordpress/test/1');
+// Navigate to our PHP file
+await client.goTo('/test/index.php');
+
+
+
+
+For a full list of these methods, consult the PlaygroundClient interface.
+ + + +You can pass messages from PHP to JavaScript using the post_message_to_js()
function. It accepts one argument:
$data
(string) – Data to pass to JavaScript.For example, here's how you would send a message with a JSON-encoded post ID and title:
+ + + +const php = await PHP.load('8.0');
+
+php.onMessage(
+ // The data is always passed as a string
+ function (data: string) {
+ // Let's decode and log the data:
+ console.log(JSON.parse(data));
+ }
+);
+
+// Now that we have a listener in place, let's
+// dispatch a message:
+await php.run({
+ code: ` post_message_to_js(
+ json_encode([
+ 'post_id' => '15',
+ 'post_title' => 'This is a blog post!'
+ ])
+ ));
+ `,
+});
+
+// You will see the following output in the console:
+// { post_id: '15', post_title: 'This is a blog post!' }
+
+
+
+
+In Node.js, you also have access to the cli()
method that runs PHP in a CLI mode:
// Run PHP in a CLI mode
+client.cli(['-r', 'echo "Hello, world!";']);
+// Outputs "Hello, world!"
+
+
+
+
+Once cli() method finishes running, the PHP instance is no* longer usable and should be discarded. This is because PHP internally cleans up all the resources and calls exit().
+]]>A Service Worker is used to handle the HTTP traffic using the in-browser PHPRequestHandler.
+ + + +Imagine your PHP script renders the following page in the iframe viewport:
+ + + +
+
+ John's Website
+
+
+ Homepage
+ Blog
+ Contact
+
+
+
+
+
+
+When the user clicks, say the Blog
link, the browser would normally send a HTTP request to the remote server to fetch the /blog
page and then display it instead of the current iframe contents. However, our app isn't running on the remote server. The browser would just display a 404 page.
Enter Service Workers – a tool to intercept the HTTP requests and handle them inside the browser:
+ + + + + + + +The main application living in /index.html
is responsible for registering the service worker.
Here's the minimal setup:
+ + + +/app.js:
+ + + +import { registerServiceWorker } from '@php-wasm/web';
+
+function main() {
+ await registerServiceWorker(
+ phpClient,
+ "default", // PHP instance scope
+ "/sw.js", // Must point to a valid Service Worker implementation.
+ "1" // Service worker version, used for reloading the script.
+ );
+
+}
+
+
+
+
+You will also need a separate /service-worker.js
file that actually intercepts and routes the HTTP requests. Here's what a minimal implementation looks like:
/service-worker.js:
+ + + +import { initializeServiceWorker } from '@php-wasm/web';
+
+// Intercepts all HTTP traffic on the current domain and
+// passes it to the Worker Thread.
+initializeServiceWorker();
+
+]]>To avoid page reloads, all the PHPRequestHandler
responses must be rendered in an iframe. Remember, the entire setup only lives as long as the main index.html
. We want to avoid reloading the main app at all costs.
In our app example above, index.php
renders the following HTML:
Go to page.php
+
+
+
+
+Imagine our index.html
rendered it in a <div>
instead of an <iframe>
. As soon as you click on that link, the browser will try to navigate from index.html
to page.php
. However, index.html
runs the entire PHP app, including the Worker Thread, the PHPRequestHandler, and the traffic control connecting them to the Service Worker. Navigating away from it would destroy the app.
Now, consider an iframe with the same link in it:
+ + + +
+
+
+
+This time, click the link in the browser to load page.php
inside the iframe. The top-level index.html
, where the PHP application runs, remains unaffected. That's why iframes are crucial for the @php-wasm/web
setup.
:::info Crash reports
Playground doesn't collect crash reports automatically. Instead, it prompts users to submit a crash report when an instance fails to run in the browser.
The report includes a log, description, and a URL, and users can modify it before submitting it.
+ + + +The Logger API handles it from there. This simple REST API validates the data and sends it to the Making WordPress #playground-logs Slack channel.
:::
target="_top"
isn't handled yet, so clicking links with target="_top"
will reload the page you’re working on.iframe
may not always display.You can host the Playground on your own domain instead of playground.wordpress.net
.
This is useful for having full control over its content and behavior, as well as removing dependency on a third-party server. It can provide a more customized user experience, for example: a playground with preinstalled plugins and themes, default site settings, or demo content.
+ + + +A self-hosted Playground can be embedded as an iframe.
+ + + +
+
+
+
+Or dynamically loaded by passing the remote URL to the Playground Client.
+ + + +import { startPlaygroundWeb } from '@wp-playground/client';
+
+const client = await startPlaygroundWeb({
+ iframe: document.getElementById('wp'),
+ remoteUrl: `https://my-playground.com/remote.html`,
+});
+
+
+
+
+There are several ways to get the static assets necessary to host the Playground.
+ + + +In order of convenience and ease:
+ + + +To host the Playground as is, without making changes, you can download the built artifact from the latest successful GitHub Action.
+ + + +playground-website
.To customize the Playground, you can fork the Git repository.
+ + + +Build it from the fork's GitHub page by going to: Actions -> Deploy to playground.wordpress.net -> Run workflow.
+ + + +The most flexible and customizable method is to build the site locally.
+ + + +Create a shallow clone of the Playground repository, or your own fork.
+ + + +git clone -b trunk --single-branch --depth 1 git@github.com:WordPress/wordpress-playground.git
+
+
+
+
+Enter the wordpress-playground
directory.
cd wordpress-playground
+
+
+
+
+Install dependencies, and build the website.
+ + + +npm install
+npm run build:website
+
+
+
+
+This command internally runs the nx
task build:wasm-wordpress-net
. It copies the built assets from packages remote
and website
into a new folder at the following path:
dist/packages/playground/wasm-wordpress-net
+
+
+
+
+The entire service of the Playground consists of the content of this folder.
+ + + +The static assets include:
+ + + +remote.html
- the core of Playgroundindex.html
- the shell, or browser chromeYou can deploy the content of the folder to your server using SSH, such as scp
or rsync
.
It is a static site, except for these dynamic aspects.
+ + + +.htaccess
file from the package remote
For these to work, you need a server environment with Apache and PHP installed.
+ + + +As an alternative to Apache, here is an example of using NGINX to serve the Playground.
+ + + +:::info Refer to the source file
+ + + +The example may be outdated. Please check the source file for the latest version.
+ + + +:::
+ + + +The combined Apache .htaccess
file looks like this.
AddType application/wasm .wasm
+AddType application/octet-stream .data
+
+
+
+
+An equivalent in NGINX.
+ + + +location ~* .wasm$ {
+ types {
+ application/wasm wasm;
+ }
+}
+
+location ~* .data$ {
+ types {
+ application/octet-stream data;
+ }
+}
+
+location /scope:.* {
+ rewrite ^scope:.*?/(.*)$ $1 last;
+}
+
+
+
+
+You may need to adjust the above according to server specifics, particularly how to invoke PHP for the path /plugin-proxy
.
The file wp.zip
is a bundle of all the files for the virtual file system in Playground. There's a data file for each available WordPress version.
The package at packages/playground/wordpress
is responsible for building these data files.
Edit the build script in Dockerfile
to create a custom bundle that includes preinstalled plugins or content.
Here's an example of installing plugins for the data bundle.
+ + + +Before the section titled Strip whitespaces from PHP files
.
# === Preinstall plugins ===
+
+RUN cd wordpress/wp-content/mu-plugins && \
+ # Install plugins
+ for plugin_name in example-plugin-1 example-plugin-2; do \
+ curl -L https://downloads.wordpress.org/plugin/{$plugin_name}.latest-stable.zip -o {$plugin_name}.zip && \
+ unzip $plugin_file && \
+ rm $plugin_file && \
+ # Create entry file in mu-plugins root
+ echo " $plugin_name.php; \
+ done;
+
+
+
+
+You can download plugins from URLs other than the WordPress plugin directory, or use Git to pull them from elsewhere.
+ + + +It's also possible to copy from a local folder. For example, before RUN
:
COPY ./build-assets/*.zip /root/
+
+
+
+
+Then put the plugin zip files in build-assets
. In this case, you may want to add their paths to .gitignore
.
Here's an example of importing content.
+ + + +# === Demo content ===
+
+COPY ./build-assets/content.xml /root/
+RUN cd wordpress ; \
+ echo "Importing content.."; \
+ ../wp-cli.phar --allow-root import /root/content.xml --authors=create
+
+
+
+
+This assumes that you have put a WXR export file named content.xml
in the folder build-assets
. You can add its path to .gitignore
.
The php.js
file generated by the WebAssembly PHP build pipeline is not a vanilla Emscripten module. Instead, it's an ESM module that wraps the regular Emscripten output and adds some extra functionality.
Here's the API it exposes:
+ + + +// php.wasm size in bytes:
+export const dependenciesTotalSize = 5644199;
+
+// php.wasm filename:
+export const dependencyFilename = 'php.wasm';
+
+// Run Emscripten's generated module:
+export default function (jsEnv, emscriptenModuleArgs) {}
+
+
+
+
+The generated JavaScript module is not meant for direct use. Instead, it can be consumed through the PHP
class:
// In Node.js:
+const php = new PHP(await loadNodeRuntime('8.0'));
+
+// On the web:
+const php = new PHP(await loadWebRuntime('8.0'));
+
+
+
+
+Both of these classes extend the BasePHP
class exposed by the @php-wasm/universal
package and implement the UniversalPHP
interface that standardizes the API across all PHP environments.
The load() method handles the entire PHP initialization pipeline. In particular, it:
+ + + +Importing file to PHP by manually calling writeFile()
would be quite inconvenient. Fortunately, Emscripten provides a "data dependencies" feature.
Data dependencies consist of a dependency.data
file and a dependency.js
loader and can be packaged with the file_packager.py tool.
WordPress Playground also requires wrapping the Emscripten-generated dependency.js
file in an ES module as follows:
export default function(emscriptenPHPModule) {';
export const dependencyFilename = '<DATA FILE NAME>';
export const dependenciesTotalSize = <DATA FILE SIZE>;
}
Be sure to use the --export-name="emscriptenPHPModule"
file_packager.py option.
You want the final output to look as follows:
+ + + +export const dependenciesTotalSize = 5644199;
+export const dependencyFilename = 'dependency.data';
+export default function (emscriptenPHPModule) {
+ // Emscripten-generated code:
+ var Module = typeof emscriptenPHPModule !== 'undefined' ? emscriptenPHPModule : {};
+ // ... the rest of it ...
+}
+
+
+
+
+Such a constructions enables loading the dependency.js
as an ES Module usingimport("/dependency.js")
.
Once it's ready, you can load PHP and your data dependencies as follows:
+ + + +const php = await PHP.load('7.4', {
+ dataModules: [import('/wp.js')],
+});
+
+]]>PHP is always ran in a web worker to ensure the PHP runtime doesn't slow down the user interface of the main website.
+ + + +Imagine the following code:
+ + + +
+
+
+
+
+
+As soon as you click that button the browser will freeze and you won't be able to type in the input. That's just how browsers work. Whether it's a for loop or a PHP server, running intensive tasks slows down the user interface.
+ + + +Web workers are separate programs that can process heavy tasks outside of the main application. They must be initiated by the main JavaScript program living in the browser tab. Here's how:
+ + + +const phpClient = consumeAPI(
+ spawnPHPWorkerThread(
+ '/worker-thread.js' // Valid Worker script URL
+ )
+);
+await phpClient.isReady();
+await phpClient.run({ code: `
+
+
+
+Exchanging messages is the only way to control web workers. The main application has no access to functions or variables inside of a web workeer. It can only send and receive messages using worker.postMessage
and worker.onmessage = function(msg) { }
.
This can be tedious, which is why Playground provides a convenient consumeAPI function that abstracts the message exchange and exposes specific functions from the web worker. This is why we can call phpClient.run
in the example above.
https://github.com/WordPress/wordpress-playground/pull/215
+ + + +Blueprints are defined in JSON format, but the underlying implementation uses JavaScript functions to execute the steps. While JSON is the most convenient way of interacting with Blueprints, you can also use the underlying functions directly.
+ + + +JSON is merely a wrapper around the functions. Whether you use the JSON steps or the exported functions, you'll have to provide the same parameters (except for the step name):
+ + + +import BlueprintStep from '@site/src/components/BlueprintsAPI/BlueprintStep';
import { BlueprintSteps } from '@site/src/components/BlueprintsAPI/model';
<span>{BlueprintSteps.map((name) => (
<>
<BlueprintStep name={name} key={name} />
You can use Blueprints both with the web and the node.js versions of WordPress Playground.
+ + + +There are two main differences between the JSON and Function APIs:
+ + + +remote.html
vs index.html
playground.wordpress.net exposes two distinct APIs through two separate HTML files: remote.html
and index.html
. Here's an overview of their functions and differences:
index.html
uses WordPress Playground API client to control the "endpoint" that is remote.html
.index.html
, independent of the WordPress Playground JavaScript API.remote.html
. Only that file can be used as an "endpoint" for the PlaygroundClient
class.Here's a bit more about each of these files:
+ + + +remote.html
runs and renders WordPress and also exposes an API for developers to control it. Importantly, remote.html
does not render any UI elements, such as browser UI or version switchers. It's just WordPress. The primary functions of remote.html
are:
message
event from the parent window and executing the appropriate code command.That last part is how the public API works. The parent window (index.html
) sends a message to the iframe (remote.html
) with a command and arguments, and the iframe then executes that command and sends the result back with another message.
Sending messages is cumbersome so the PlaygroundClient class provides an object-oriented API that handles the messages internally.
+ + + +For quick testing and debugging, remote.html
also exposes the JavaScript API client as window.playground
. You can use it from your devtools as follows:
> await playground.listFiles("/")
+(6) ['tmp', 'home', 'dev', 'proc', 'internal', 'wordpress']
+
+
+
+
+playground
is a class instance in this context and you will benefit from browser's autocompletion.
index.html
is an independent app built around remote.html
using the WordPress Playground API client.
It renders the browser UI, version selectors, and renders WordPress by embedding remote.html
via an iframe. UI features like an address bar or a version selector are implemented by communicating with remote.html
using PlaygroundClient
.
index.html
monitors the query parameters it receives and triggers the appropriate PlaygroundClient
methods. For instance, ?plugin=coblocks
triggers installPluginsFromDirectory( client, ['coblocks'] )
. This mechanism forms the basis of the Query API.
For quick testing and debugging, index.html
also exposes the JavaScript API client as window.playground
. You can use it from your devtools as follows:
> await playground.listFiles("/")
+(6) ['tmp', 'home', 'dev', 'proc', 'internal', 'wordpress']
+
+
+
+
+Note that playground
is a Proxy object in this context and you won't get any autocompletion from the browser.
:::danger Careful with the demo site
+ + + +The site at https://playground.wordpress.net is there to support the community, but there are no guarantees it will continue to work if the traffic grows significantly.
+ + + +If you need certain availability, you should host your own WordPress Playground.
+]]>The Playground API client can be initialized with a JSON Blueprint. This is a convenient way of preconfiguring it in any way you like without worrying about progress bars and fetching remote files:
+ + + +import { startPlaygroundWeb } from 'https://playground.wordpress.net/client/index.js';
+
+const client = await startPlaygroundWeb({
+ iframe: document.getElementById('wp'),
+ remoteUrl: `https://playground.wordpress.net/remote.html`,
+ blueprint: {
+ preferredVersions: {
+ wp: '6.3',
+ php: '8.0',
+ },
+ // Optional: downloads additional PHP extensions like DOMDocument, mbstring, etc.
+ extensionBundles: ['kitchen-sink'],
+ steps: [
+ { step: 'login' },
+ {
+ step: 'installPlugin',
+ pluginZipFile: {
+ resource: 'wordpress.org/plugins',
+ slug: 'gutenberg',
+ },
+ },
+ ],
+ },
+});
+await client.isReady();
+
+
+
+
+Running a JSON Blueprint is only possible during the initialization of the API client.
+ + + +If this is sufficient for your needs, read more about JSON Blueprints.
+ + + +If you need to work with an already initialized client, you should look into Blueprint functions.
+]]>The main index.html
ties the entire application together. It starts all the concurrent processes and displays the PHP responses. The app only lives as long as the main index.html
.
Keep this point in mind as you read through the rest of the docs. At this point it may seem obvious, by the lines may get blurry later on. This package runs code outside of the browser tab using Web Workers, Service Workers, and, in the future, Shared Workers. Some of these workers may keep running even after the browser tab with index.html
is closed.
Here's what a boot sequence for a minimal app looks like:
+ + + + + + + +The main app initiates the Iframe, the Service Worker, and the Worker Thread. Note how the main app doesn't use the PHP stack directly – it's all handled in the Worker Thread.
+ + + +Here's what that boot sequence looks like in code:
+ + + +/index.html:
+ + + +
+
+
+
+
+/app.ts:
+ + + +import { consumeAPI, PHPClient, registerServiceWorker, spawnPHPWorkerThread } from '@php-wasm/web';
+
+const workerUrl = '/worker-thread.js';
+
+export async function startApp() {
+ const phpClient = consumeAPI(
+ await spawnPHPWorkerThread(
+ workerUrl, // Valid Worker script URL
+ {
+ wpVersion: 'latest',
+ phpVersion: '7.4', // Startup options
+ }
+ )
+ );
+
+ // Await the two-way communication channel
+ await phpClient.isReady();
+
+ // Must point to a valid Service Worker script:
+ await registerServiceWorker(
+ phpClient,
+ 'default', // PHP instance scope, keep reading to learn more.
+ '/sw.js', // Valid Service Worker script URL.
+ '1' // Service worker version, used for reloading the script.
+ );
+
+ // Create a few PHP files to browse:
+ await workerThread.writeFile('/index.php', 'Go to page.php');
+ await workerThread.writeFile('/page.php', '');
+
+ // Navigate to index.php:
+ document.getElementById('my-app').src = playground.pathToInternalUrl('/index.php');
+}
+startApp();
+
+
+
+
+Keep reading to learn how all these pieces fit together.
+ + + +Here's what happens whenever the iframe issues a same-domain request:
+ + + + + + + +A step-by-step breakdown:
+ + + +PHP.request
to convert that request to a responseAt this point, if the request was triggered by user clicking on a link, the browser will render PHPRequestHandler's response inside the iframe.
+]]>When you build Blueprints, you might run into issues. Here are tips and tools to help you debug them:
+ + + +wp-load
: to run a WordPress PHP function using the runPHP
step, you’d need to require wp-load.php. So, the value of the code
key should start with "<?php require_once('wordpress/wp-load.php'); REST_OF_YOUR_CODE"
.networking
: to access wp.org assets (themes, plugins, blocks, or patterns), or load a stylesheet using add_editor_style() (say, when creating a custom block style), you’d need to enable the networking
option: "features": {"networking": true}
.You can use an in-browser Blueprints editor to build, validate, and preview your Blueprints in the browser.
+ + + +:::danger Caution
+ + + +The editor is under development and the embedded Playground sometimes fails to load. To get around it, refresh the page. We're aware of that, and are working to improve the experience.
+ + + +:::
+ + + +If your Blueprint isn’t running as expected, open the browser developer tools to see if there are any errors.
+ + + +To open the developer tools in Chrome, Firefox, Safari*, and Edge: press Ctrl + Shift + I
on Windows/Linux or Cmd + Option + I
on macOS.
:::caution Warning
+ + + +If you haven't yet, enable the Develop menu: go to Safari > Settings... > Advanced and check Show features for web developers.
+ + + +:::
+ + + +The developer tools window allows you to inspect network requests, view console logs, debug JavaScript, and examine the DOM and CSS styles applied to your webpage. This is crucial for diagnosing and fixing issues with Blueprints.
+ + + +The community is here to help! If you have questions or comments, open a new issue in this repository. Remember to include the following details:
+ + + +Every Blueprint step you can declare in the JSON object also provides a handler function that can be used directly.
+ + + +For example:
+ + + +import { startPlaygroundWeb, login, installPlugin } from 'https://playground.wordpress.net/client/index.js';
+
+const client = await startPlaygroundWeb({
+ iframe: document.getElementById('wp'),
+ remoteUrl: `https://playground.wordpress.net/remote.html`,
+});
+await client.isReady();
+
+await login(client, {
+ username: 'admin',
+ password: 'password',
+});
+
+await installPlugin(client, {
+ // Resources can only be used with JSON Blueprints.
+ // If you use functions, you must provide the resolved
+ // file.
+ pluginZipFile: await fetch(pluginUrl),
+});
+
+
+
+
+For more information and live examples visit the Blueprints Steps page.
+]]>@php-wasm/web
uses the Comlink library to turns the one-way postMessage
available in JavaScript into a two-way communication channel.
If postMessage
sounds unfamiliar, it's what JavaScript threads use to communicate. Please review the MDN Docs before continuing.
By default, postMessage
does not offer any request/response mechanics. You may send messages to another thread and you may independently receive messages from it, but you can't send a message and await a response to that specific message.
To quote the Comlink library documentation:
+ + + +main.js
+ + + +import * as Comlink from 'https://unpkg.com/comlink/dist/esm/comlink.mjs';
+async function init() {
+ const worker = new Worker('worker.js');
+ // WebWorkers use `postMessage` and therefore work with Comlink.
+ const obj = Comlink.wrap(worker);
+ alert(`Counter: ${await obj.counter}`);
+ await obj.inc();
+ alert(`Counter: ${await obj.counter}`);
+}
+init();
+
+
+
+
+worker.js
+ + + +importScripts('https://unpkg.com/comlink/dist/umd/comlink.js');
+
+const obj = {
+ counter: 0,
+ inc() {
+ this.counter++;
+ },
+};
+
+Comlink.expose(obj);
+
+]]>