← Back to blog

A real WordPress preview for every pull request

The SandyWP team 7 min read

There are two practical ways to get a working WordPress preview attached to a pull request.

WordPress Playground's GitHub Action publishes a link that boots your plugin or theme in a WebAssembly WordPress inside the reviewer's browser. It is free and official. SandyWP creates an actual WordPress install on a server, running the exact head commit, reachable at a normal URL.

Which one you want depends on a single question. Does the thing you are reviewing need a real MySQL database, a real filesystem, or real HTTP requests?

If not, use Playground. It costs nothing and it is genuinely good. If it does, WASM will not get you there, and you need a real install.

This post covers both, honestly, including where ours is the wrong choice.

Why "just check out the branch" stops scaling

Reviewing a WordPress plugin change usually goes like this. Fetch the branch, switch your local site to it, remember which other plugins were active, click around, switch back.

That is five to fifteen minutes of setup for a review that takes two.

The cost is invisible until you count what it changes. It means designers and support people cannot review at all, because they do not have the local stack.

It means the second reviewer usually skips the manual pass and just reads the diff.

And it means anything that only reproduces on a clean install, such as an activation hook, a migration, or a first-run wizard, gets tested exactly once, by the person who wrote it.

A preview URL on the PR removes the setup entirely. Anyone with the link can look at the change running.

Option 1: WordPress Playground's PR preview action

The official action adds a "Preview in WordPress Playground" button to pull requests.

Playground boots a clean WordPress in the browser via WebAssembly, installs the plugin or theme built from the branch, and activates it.

Setup is a workflow file. If your plugin runs straight from the repository with no build step, one file does it: .github/workflows/pr-preview.yml, pointed at your plugin-path or theme-path.

If you need Composer, npm, or Vite, version 3 of the action splits it into two reusable workflows, pr-preview-build.yml and pr-preview-publish.yml.

The v3 announcement explains why. The build job runs untrusted fork code with read-only permissions, and a separate job from the default branch does the commenting.

That is a sound security design, and the split is unavoidable given GitHub's fork permission model.

Things the project documents that will bite you:

  • Private repositories are not supported. Playground needs an unauthenticated URL to download the artifact from.
  • Fork PR artifacts become public, because they are published to a release URL.
  • The ZIP must extract to a slug-named folder, so my-plugin/my-plugin.php, not the files at the root.
  • Reusable workflow YAML is always read from the default branch. Editing the workflow on a PR branch does nothing until it merges, which is a confusing hour the first time.
  • Diffing against the base ref needs fetch-depth: 0 on the checkout.

The bigger constraint is architectural. Playground runs WordPress on SQLite in the browser, not MySQL.

That is a deliberate design choice, and it is what makes the whole thing possible. It is also the boundary. We wrote up where that boundary sits in more detail in WordPress Playground vs SandyWP.

Option 2: a real WordPress install per pull request

SandyWP for GitHub takes a different approach. It provisions an ordinary WordPress sandbox on a server, checks out the pull request's head commit, and installs the code as a plugin, theme, or whole wp-content folder.

You connect the GitHub App from Account → GitHub and enable each repository explicitly. After that, previews are driven from PR comments:

@sandywp-bot create a new site
@sandywp-bot deploy
@sandywp-bot deploy using blueprint: gogh
@sandywp-bot login
@sandywp-bot delete
@sandywp-bot help

The bot maintains one comment on the PR with the site URL, the commit it verified, and the stack.

Push a new commit, comment deploy again, and the existing preview updates in place with the same URL and the same database. That last part matters more than it sounds. If you have entered test content, a redeploy does not throw it away.

A few rules are worth knowing before you roll this out to a team:

  • Only owners, members, and collaborators can run commands. A drive-by comment from a stranger does nothing.
  • The pull request head is authoritative. There is no "deploy this other branch" command, because that would let a comment decide what code runs.
  • Private repositories work. SandyWP mints a short-lived GitHub App installation token for the job. No deploy key or personal access token is stored.
  • Fork PRs are fetched from the base repository's pull-request ref, so public forks work without any access to the contributor's fork. Private-fork behaviour depends on your organization's policy, so check it before you promise it to anyone.
  • No build step runs. SandyWP deploys exactly what is committed. If your plugin needs npm run build, either commit the build output or point the preview at an installable subdirectory.
  • Previews use your normal quota. Each one is an ordinary account-owned sandbox that shows up in your dashboard and occupies an active-site slot. Previews are on paid plans.
  • The preview lives as long as the PR does. Merging or closing starts normal cleanup, and @sandywp-bot delete does it sooner.

Can a reviewer get into wp-admin?

Yes, but it is off by default, and you should understand why before turning it on.

Each repository picks one of three settings for wp-admin login links: Never, Only when requested, or With every successful preview. The default is Never.

The reason is that a magic login URL is a reusable bearer link. Anything posted in a GitHub comment travels, into email notifications, into CI logs, into the API.

On a public repository, a login link in a comment is a login link for the entire internet.

For a preview with throwaway content that is fine. For anything else it is not, so the setting makes you opt in per repository rather than deciding for you.

Can the preview start from something other than blank WordPress?

A plain deploy gives you blank WordPress with your code installed. Often that is exactly what you want, because a clean install is where activation bugs live.

Sometimes it is not. Reviewing a WooCommerce integration against an empty store tells you very little.

For that, the account owner allow-lists specific Blueprints per repository, and a collaborator can request one by slug with @sandywp-bot deploy using blueprint: gogh. SandyWP builds the base environment first, then installs the PR code into it.

The allow-list is per repository on purpose. GitHub commenters cannot browse your other Blueprints or invent a recipe you did not approve.

And requesting a different Blueprint replaces the preview rather than layering onto it. So you never end up debugging a site whose state is the accumulation of three recipes.

Which one should you use?

Use Playground's action when your plugin or theme is mostly PHP and JavaScript that does not care what the database is, when the repository is public, and when you want something free that works for external contributors with no account anywhere.

For a block plugin or a theme, this is often the correct answer, and we would rather you use it than pay us for something you do not need.

Use a real install when the review genuinely depends on a real environment. Custom tables and migrations, dbDelta behaviour, $wpdb->prepare against MySQL-specific SQL, cron, outbound HTTP to an API, file uploads and image sizes, a specific PHP version or extension, a private repository, or a stakeholder who needs a URL they can open on their phone.

Use neither when the change is a one-line CSS fix.

A preview environment is worth setting up for the class of change where "it looked fine in the diff" has burned you before. Not everything is that class.

If the code you are reviewing is a plugin you ship to customers, the environment questions tend to pile up quickly. That is why we wrote a separate page for plugin developers on testing across versions and stacks.

Getting started

Connect a repository from Account → GitHub, enable previews on it, set the source path to your installable plugin or theme directory, and comment @sandywp-bot deploy on an open pull request.

If it fails, the message is usually one of four things. The repository is not enabled, the source path does not point at an installable directory, the requested Blueprint is not allow-listed, or you are out of active-site slots.

Full details are in the GitHub previews feature page.