We use WP Playground heavily at work for previewing plugin changes on pull requests. I’ve written before about how Playground replaced Docker in our e2e test setup — but we also use it for something simpler: giving every PR a one-click preview link so anyone on the team can test changes without setting up a local environment.

It works great — until the repo is private.

The problem

Playground’s blueprint system can install plugins directly from GitHub. For public repos, you just point it at the repo and it pulls the latest code. But for private repos, Playground has no way to authenticate with GitHub. It simply can’t download the plugin zip.

This is a dealbreaker when most of your work lives in private repos.

The workaround

The idea is straightforward: instead of letting Playground fetch the plugin from GitHub, we build the plugin zip ourselves in CI and upload it somewhere Playground can reach. Then we swap the plugin reference in the blueprint with the public URL.

I went with Cloudflare R2 for storage — I already had a personal Cloudflare account, the free tier is generous, and a small Worker in front of it handles the upload auth. Any S3-compatible storage would work just as well.

The workflow

The GitHub Actions workflow triggers on every pull request. Here’s what it does:

  1. Checks out the code and installs Composer dependencies (with a PAT for private package access)
  2. Prepares the plugin folder — copies everything into a clean directory named after the repo, excluding dev files like node_modules and .github
  3. Bumps the version — appends the PR number to the version string so you can tell which PR you’re testing (e.g. 1.3.1b - PR 42)
  4. Zips it up and uploads to R2 via a Cloudflare Worker endpoint
  5. Generates the blueprint — reads the project’s base blueprint JSON, replaces the plugin slug with the R2 URL, and base64-encodes it
  6. Posts a PR comment with a direct Playground link and a zip download link

The end result is a comment on every PR that looks like:

Test on Playground Test this pull request on the Playground or download the zip

One click, and you’re testing the PR in a fresh WordPress instance. No local setup, no access tokens, no manual steps.

The blueprint swap

The key piece is how the blueprint gets modified. Each project has a base blueprint at .wordpress-org/blueprints/playground.json that defines the WordPress environment — activated plugins, settings, login state. The workflow reads that file, finds the plugin slug that matches the repo name, and replaces it with the R2 artifact URL:

const blueprint = JSON.parse(
  fs.readFileSync('.wordpress-org/blueprints/playground.json', 'utf8')
);
blueprint.plugins = blueprint.plugins.map(plugin =>
  plugin === repoName ? artifactUrl : plugin
);

The modified blueprint gets base64-encoded and appended to the Playground URL as a fragment. Playground reads it and installs the plugin from R2 instead of trying to reach GitHub.

What you need

If you want to set up something similar, the pieces are:

  • A Cloudflare R2 bucket (or any publicly accessible storage)
  • A Cloudflare Worker (or equivalent) that accepts authenticated uploads and serves public downloads
  • A GitHub Actions workflow that builds, uploads, and generates the Playground link
  • A base blueprint in your repo that defines your Playground environment
  • Repository secrets for the upload token and worker URL

The workflow itself is reusable across repos — the only thing that changes per project is the base blueprint file.


It’s a small amount of plumbing, but once it’s in place every PR gets a live preview link automatically. For a team working across multiple private plugins, that’s a meaningful improvement to the review process.