Browser/Node WebAssembly runtime12 min read

PHP WebAssembly (php-wasm) in the browser and Node

A guide to PHP compiled to WebAssembly through the WordPress Playground project: how it runs, where it fits, its limits, and a minimal browser example.

PHP compatibility and project lifecycle

Evidence is limited to the repository README. It mentions recompiling PHP 7.0 to 8.4 releases to WebAssembly and calls the project experimental. Per-build support, extensions and API stability are unknown. This is not a native server runtime.

Official sources checked 2026-10-22. Browser lessons on this site run PHP 8.5.

Nothing here has been executed or checked by a human, and the only evidence used is the WordPress Playground repository page.

What this project is

The repository is WordPress Playground. Its README describes it as an experimental, in-browser WordPress that runs without a PHP server, using WebAssembly. The README title pairs it with PHP WASM (WebAssembly), and the repository topics include emscripten, wasm and webassembly. The PHP interpreter in this stack is compiled to WebAssembly so it can run inside a JavaScript host.

The repository is licensed GPL-2.0 according to its page. It is described as a WordPress.org project started and led by Adam Zielinski. Its lifecycle status is unknown in this guide. The page says the software is experimental and may break or change without warning, and it gives no support policy. Version numbers, support windows and deprecations are not stated in the packet, so none are claimed.

Classification

PHP WebAssembly is a browser and Node WebAssembly runtime. It is not a web server, not an async library, not a framework adapter and not a historical alternative interpreter. The README says it runs without a PHP server, so there is no FastCGI or PHP-FPM layer. Never expose FastCGI publicly in any setup you build around PHP.

Two distinctions matter:

  • PHP core versus WordPress. The PHP interpreter is the language runtime. WordPress is a CMS product that runs on top of it. Playground bundles and boots WordPress, but the @php-wasm packages are the runtime layer. The README also credits a Drupal-in-the-browser demo as inspiration. That demo is prior art, not part of PHP core.
  • Unrelated projects. Swoole and Open Swoole are native PHP extensions for persistent servers. This project does not use them.

Architecture and packages

The README names these pieces:

  • @php-wasm/cli, which runs PHP.wasm from a local command line through npx.
  • @wp-playground/client, a JavaScript client. Its startPlaygroundWeb function connects a page to Playground through an iframe.
  • @wp-playground/cli, which starts a local WordPress server through npx @wp-playground/cli server --auto-mount.
  • Build targets that recompile PHP 7.0 to 8.4 releases to .wasm for the web and for Node.

The README says Playground forked an earlier PHP-to-WebAssembly build (pib), which was later modified in seanmorris/php-wasm.

Execution model

The focus of this lesson is Web Worker execution with @php-wasm packages. The packet does not document the exact worker API, so treat the following as a design guide and confirm details in the official documentation.

In the README example, the host page creates an iframe and calls startPlaygroundWeb with a remoteUrl and a Blueprint. The Blueprint sets a landing page and preferred PHP and WordPress versions. After that, client.run takes a PHP code string and returns a response whose text property holds the output.

To keep untrusted or experimental HTML isolated, keep it in an iframe separate from your application page. To stop a runaway run, the usual browser approach is to terminate the worker that hosts the interpreter. Whether and how the packages expose this is unknown here. Never assume termination is graceful, because it discards everything in the instance.

Request lifetimes and persistent workers

A classic PHP deployment gives each HTTP request a fresh lifecycle. Globals, static properties and open handles disappear when the request ends. A WebAssembly instance inside a worker is different. It is a long-lived process-like object that may serve many calls, so it behaves like a persistent worker.

The packet does not say whether client.run resets state between calls, so that is unknown. Plan for state leakage:

  • Static properties, globals, defined constants and loaded classes may survive between runs.
  • The virtual filesystem, including files written by earlier code, may persist.
  • Unclosed handles, output buffers and registered shutdown behavior may carry over.

Cleanup options, from safest to least safe, are to start a fresh instance for each untrusted job, terminate the worker and create a new one, or write code that explicitly resets state. Never rely on the last option alone for isolation between users.

Practical use cases

The README lists learning WordPress by exploring, learning WordPress development by writing code, and instant access to the WordPress ecosystem. Practical uses follow from that: zero-setup demos, teaching sandboxes, quickly testing a plugin or theme, and embedding an interactive WordPress in a JavaScript application. Playground tools such as the Gutenberg interactive code block and the Visual Studio Code extension live in the separate playground-tools repository.

Installation approach

There is no Docker Compose path in the source, so this guide uses the documented Node and static-page approach.

  1. Run the PHP.wasm CLI with npx @php-wasm/cli -v to see the PHP version it reports. The README also shows selecting a version with the PHP environment variable, using 7.4 as its example.
  2. Use the Playground CLI inside a plugin or theme directory. The README says --auto-mount detects a plugin or theme and mounts it into the virtual WordPress environment.
  3. To work on Playground itself, clone the trunk branch, run npm install, then npm run dev. The README says the site opens at http://127.0.0.1:5400/.

The example below uses the README's iframe pattern with a minimal Blueprint. Requesting PHP 8.4 follows the learning track and the README's mention of 8.4 builds. Whether the hosted service accepts that value is unverified.

Strengths

  • No PHP server is required to run PHP and WordPress in a browser.
  • Browser delivery is zero-setup for learners.
  • Version switching is available through CLI flags and Blueprint preferences, per the README.
  • The iframe boundary gives a natural place to isolate rendered output.

Limitations

  • The README states it is experimental and may break or change without warning, and a stable API is a future milestone.
  • Compatibility with PHP extensions, native libraries and networking is not described in the packet, so it is unknown.
  • Performance, memory limits and the licensing of bundled components beyond the repository's GPL-2.0 label are not covered here, so no claims are made.
  • The example depends on a third-party hosted client and remote page.
  • Runtime examples need Node or a network and are not browser exercises on this site.

Deployment

The README documents a build of the website for offline support testing, then serving it with php -S or an nginx container. It does not provide a production deployment guide in the packet. Any hosting plan, caching strategy or service worker policy is unknown and should come from the official documentation. If you serve files locally, bind to 127.0.0.1.

Links

The repository page is the only supplied source. It links to the project page, live demo and documentation, which you should read before relying on any behavior described here.

Practical examples

These examples have not been run in a live environment yet. Commands and versions follow the official documentation linked on this page; check them before relying on them, and report a problem if one does not work.

Run PHP in a local static page using the Playground client

A static page adapted from the README's iframe and startPlaygroundWeb pattern. It uses a minimal Blueprint, runs a one-line PHP snippet through client.run, and prints the response text. The page is served from 127.0.0.1 and imports the client from the hosted playground.wordpress.net site.

public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>PHP WASM check</title>
</head>
<body>
  <iframe id="wp-playground" style="width: 800px; height: 500px"></iframe>
  <pre id="output">Waiting for PHP...</pre>
  <script type="module">
    import { startPlaygroundWeb } from 'https://playground.wordpress.net/client/index.js';

    const client = await startPlaygroundWeb({
      iframe: document.getElementById('wp-playground'),
      remoteUrl: 'https://playground.wordpress.net/remote.html',
      blueprint: {
        landingPage: '/',
        preferredVersions: {
          php: '8.4',
          wp: 'latest',
        },
      },
    });

    const response = await client.run({
      code: '<?php echo "PHP " . PHP_VERSION;',
    });
    document.getElementById('output').textContent = response.text;
  </script>
</body>
</html>

Install and run

npx @php-wasm/cli -v
php -S 127.0.0.1:9999 -t public

Expected behavior

The first command should print PHP version information from the WebAssembly build. After the second command, opening http://127.0.0.1:9999/ should load an iframe with WordPress and show a line beginning with PHP followed by a version, if the hosted client accepts the 8.4 request.

Requires Node with npx, host PHP for the static server, and network access to playground.wordpress.net. The 8.4 Blueprint value is unverified. The example does not demonstrate worker termination, because the packet does not document that API. It is not a browser exercise.