Reviewing a pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party. or theme Pull Request has always carried a bit of friction. To actually test the change, someone had to pull the branch, spin up a local WordPress, and activate the code by hand. That is exactly why the WordPress Playground PR Preview GitHubGitHub GitHub is a website that offers online implementation of git repositories that can easily be shared, copied and modified by other developers. Public repositories are free to host, private repositories require a paid subscription. GitHub introduced the concept of the ‘pull request’ where code changes done in branches by contributors can be reviewed and discussed before being merged by the repository owner. https://github.com/ Action exists: it adds a “Preview in WordPress Playground” button straight to the PR. As a result, any reviewer opens the environment with the code already applied in the browser, without installing anything.
The action already existed in v2. However, version 3 underwent an important simplification: the manual “glue” that each project had to write for builds was replaced by ready-made, reusable workflows. In this post, therefore, I’ll cover what the action does, why it’s worth it, how to set it up in the two possible scenarios, practical examples, and the step-by-step path to migrate from v2.
What the WordPress Playground PR Preview action does
In short, it publishes a preview link on the Pull Request. That link points to a WordPress Playground instance, in other words, a full WordPress running through WebAssembly (WASM) in the browser of whoever clicks it. Playground boots clean, installs the plugin or theme from the PR branch, and automatically activates everything.
The result, then, is a disposable, isolated environment created on demand that mirrors the state of that branch exactly.
Why use it
- One-click testing: the reviewer needs no local setup and no WordPress installed.
- Works with forks: external contributions get a preview without exposing repository secrets.
- Plugin, theme, or both: you can create scenarios that combine several artifacts.
- Handles complex builds: projects that rely on Composer, npm, or Vite are supported too, through dedicated workflows.
- Real sandbox: the code runs in the user’s browser, isolated, without touching any server.
Scenario 1: no build step
This is the simplest path. Use it when the files committed to the repository run as-is, without compilation, for example, a plain PHPPHP PHP (recursive acronym for PHP: Hypertext Preprocessor) is a widely-used open source general-purpose scripting language that is especially suited for web development and can be embedded into HTML. https://www.php.net/manual/en/index.php plugin.
First, create .github/workflows/pr-preview.yml:
name: PR Preview
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
preview:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: WordPress/action-wp-playground-pr-preview@v3
with:
plugin-path: .
github-token: ${{ secrets.GITHUB_TOKEN }}
For a theme, swap plugin-path: . for theme-path: .. Besides that, the permissions blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. is mandatory: the action needs pull-requests: write to write the link on the PR and contents: read to read the code.
Main inputs of the direct action
| Input | Required | Default | Purpose |
|---|---|---|---|
plugin-path | one of four | — | Relative path to the plugin directory |
theme-path | one of four | — | Relative path to the theme directory |
blueprint | one of four | — | Custom Blueprint JSONJSON JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML. (string) |
blueprint-url | one of four | — | URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org to a hosted Blueprint |
mode | no | append-to-description | append-to-description or comment |
github-token | yes | — | Token with PR write access |
playground-host | no | https://playground.wordpress.net | Base Playground URL |
You provide one of plugin-path, theme-path, blueprint, or blueprint-url. The mode, in turn, decides whether the link goes into the PR description body or as a comment.
Scenario 2: with a build step
When the preview depends on compilation (npm run build, Composer, etc.), v3 uses two workflows that talk to each other. First, one builds the untrusted PR code with read-only permissions. Then, the other publishes the result from the default branch, with write permissions, without ever executing the PR code. The only point of contact between them is the generated ZIP file — and that is precisely what keeps the flow secure.
The build workflow lives in .github/workflows/pr-preview-build.yml:
name: PR Preview - Build
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
build:
uses: WordPress/action-wp-playground-pr-preview/.github/workflows/preview-build.yml@v3
with:
artifacts: my-plugin=build/my-plugin.zip
node-version: '20'
build-command: |
npm ci
npm run build:plugin-zip
The publish workflow, in turn, lives in .github/workflows/pr-preview-publish.yml:
name: PR Preview - Publish
on:
workflow_run:
workflows: ["PR Preview - Build"]
types: [completed]
permissions:
contents: write
pull-requests: write
jobs:
publish:
permissions:
contents: write
pull-requests: write
uses: WordPress/action-wp-playground-pr-preview/.github/workflows/preview-publish.yml@v3
with:
kind: plugin
The artifacts field uses the name=path/to/file.zip format — one per line, if you have several. The kind in publish, meanwhile, tells whether the artifact is a plugin or a theme. Besides that, the reusable workflows take care of automatically creating a ci-artifacts prerelease to host the ZIPs publicly. After all, Playground needs to download the asset, so it can’t be in a draft release.
Build workflow inputs
| Input | Required | Purpose |
|---|---|---|
artifacts | yes | name=path.zip entries, one per line |
build-command | yes | Shell commands that produce the ZIPs |
node-version | no | Runs actions/setup-node@v4 if set |
php-version | no | Runs shivammathur/setup-php@v2 if set |
fetch-depth | no | Passed to actions/checkout@v4 (default: 1) |
Practical example: custom Blueprint
Sometimes you need more than “install and activate”. Say, a plugin from the PR running alongside WooCommerce and already logged in as admin. In that case, use a Blueprint. It’s a JSON recipe that describes the site’s initial state.
- uses: WordPress/action-wp-playground-pr-preview@v3
with:
blueprint: |
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"preferredVersions": { "php": "8.3", "wp": "6.6" },
"steps": [
{ "step": "installPlugin",
"pluginData": {
"resource": "git:directory",
"url": "https://github.com/${{ github.repository }}.git",
"ref": "${{ github.event.pull_request.head.ref }}",
"path": "/"
},
"options": { "activate": true } },
{ "step": "installPlugin",
"pluginData": { "resource": "wordpress.org/plugins", "slug": "woocommerce" },
"options": { "activate": true } },
{ "step": "login", "username": "admin" }
]
}
github-token: ${{ secrets.GITHUB_TOKEN }}
This Blueprint pins PHP 8.3 and WordPress 6.6, installs the code from the PR branch, adds WooCommerce from the official repository, and hands over an already-logged-in environment. For build scenarios, moreover, you can reference the generated ZIPs inside the Blueprint using the {{ARTIFACT_URL:<name>}} placeholder. That way, v3 resolves the public artifact URL for you.
Migrating from v2 to v3
In practice, the migrationMigration Moving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. varies with the complexity of your setup.
Simple plugins and themes (no build): here, almost nothing changes. Switch the action reference from @v2 to @v3 and keep the same inputs.
# works the same in v2 and v3
- uses: WordPress/action-wp-playground-pr-preview@v3
with:
plugin-path: .
github-token: ${{ secrets.GITHUB_TOKEN }}
Projects with a build: this is where the real gain is. On v2 you needed a build workflow, plus manual parsing of the artifact name, plus an expose-artifact-on-public-url helper, plus manual Blueprint generation, and still the manual release publishing. v3, by contrast, replaces all of that with the two reusable workflows (preview-build.yml and preview-publish.yml) shown above. The step-by-step, therefore, looks like this:
- Create
pr-preview-build.ymlwith the build workflow template. - Create
pr-preview-publish.ymlwith the publish workflow template. - Define the artifacts in the
artifactsfield (name=pathformat). - Set
kind: pluginorkind: themein publish — or, alternatively, useblueprintwith{{ARTIFACT_URL:<name>}}placeholders for setups with multiple ZIPs.
Inputs such as artifact-name, artifact-filename, artifact-source-run-id, pr-number, and commit-sha, which existed only for the manual glue, are no longer needed in most cases.
Finally, watch out for the mandatory release cleanup: the change that breaks the most in practice is that draft releases no longer work. After all, Playground can’t download assets from a draft release without authentication. Therefore, if you have ci-artifacts releases in draft, convert them to prerelease or delete them.
Common gotchas
- 404 error when opening the preview: the release is probably set as a draft instead of a prerelease. Fix it on the repository’s Releases tab.
- Plugin doesn’t show up as installed: the ZIP must be extracted to a folder named after the slug. Make sure your build does that. For example:
bash mkdir -p stage/my-plugin && rsync -a ./ stage/my-plugin/ && (cd stage && zip -r ../my-plugin.zip my-plugin) - Build failing when comparing against the base: if you use
git diffagainst the base branch, setfetch-depth: 0in the build workflow. After all, the default checkout brings only the latest commit. - Permission error: the
permissionsblock is missing at the workflow or job level.
Conclusion
If you already used v2 for builds, migrating is worth it: you delete dozens of lines of manual glue and get tested, community-maintained workflows in their place. On the other hand, if you don’t use PR preview in your plugin or theme repository yet, the no-build scenario solves it with a few lines of YAML — and noticeably improves the experience for whoever reviews code in your project.
The full documentation lives at wordpress.github.io/action-wp-playground-pr-preview. For the more specific cases, also check the detailed migration guide.