Running PHP in the Browser with Wasm

This page answers one task: you want PHP code — a WordPress site, a PHP framework demo, interactive PHP documentation, a plugin test environment — to run entirely in the browser, without a server, and you need to know what works, what does not, and how the pieces fit together.

Prerequisites

  • [ ] A PHP-in-Wasm runtime: WordPress Playground’s packages (@php-wasm/web) or another php-wasm build.
  • [ ] Familiarity with how PHP applications expect a web server and a database.
  • [ ] Understanding of service workers, if you want PHP to serve pages.

How PHP runs in a page

The PHP interpreter is written in C, so it compiles to WebAssembly with Emscripten like other C programs. A php-wasm build contains the Zend engine, a selection of extensions, and an Emscripten virtual file system holding PHP files. JavaScript can call into it to run a script, or to handle an HTTP-like request: the runtime sets $_SERVER, $_GET, $_POST and headers from a request object, executes the entry script, and returns status, headers and body.

WordPress Playground builds on this to run full WordPress sites in a browser tab: PHP in Wasm, WordPress files in the virtual file system, SQLite (through a database integration plugin) instead of MySQL, and a service worker that intercepts the site’s page requests and routes them to the in-browser PHP instead of the network. The result is a working WordPress instance — admin, themes, plugins — with no server at all.

A page request served by PHP in the browser The browser navigates to a URL under the playground's scope. A service worker intercepts the request and forwards it to the PHP Wasm runtime in a worker. PHP runs the application from the virtual file system, using SQLite for data, and returns status, headers and HTML, which the service worker sends back as the response. browser requests /wp-admin same-origin URL service worker intercepts routes to PHP PHP Wasm in worker virtual file system SQLite for data instead of MySQL HTML response rendered in the tab

Step 1 — run PHP code directly

The simplest use is executing PHP and reading the output, for interactive documentation or tutorials:

import { PHP } from "@php-wasm/universal";
import { loadWebRuntime } from "@php-wasm/web";

const php = await PHP.load(await loadWebRuntime("8.3"));   // API names vary between releases; check current docs
const res = await php.run({ code: `<?php echo json_encode(["sum" => array_sum([1, 2, 3])]);` });
console.log(res.text);                                       // {"sum":6}

Each run executes a script in the same PHP instance; files written to the virtual file system persist between runs until the page reloads, unless you mount persistent storage.

Step 2 — load an application into the virtual file system

Applications need their files. Write them into the virtual file system (from a zip fetched once, or from individual files), then handle requests:

await php.mkdir("/var/www");
await unzipInto(php, "/var/www", await (await fetch("/app.zip")).arrayBuffer());
const response = await php.request({ url: "/index.php?page=about", method: "GET" });
document.querySelector("iframe").srcdoc = response.text;   // or serve via a service worker

For a full site, use the service-worker routing that WordPress Playground provides, so links, forms and assets work as on a real server.

Step 3 — replace the database

MySQL cannot run in the browser, but SQLite compiled into PHP (the pdo_sqlite extension) can. Applications that use PDO with portable SQL can switch to SQLite with configuration; WordPress uses a translation layer (the SQLite Database Integration plugin) that converts its MySQL queries. Applications relying on MySQL-specific features may need patches. Data lives in the virtual file system, so persist it to OPFS or IndexedDB if it must survive reloads.

What works in PHP-in-Wasm and what does not Core PHP, common extensions, file operations in the virtual file system and SQLite work. MySQL servers, arbitrary outbound sockets, process spawning and native extensions not compiled into the build do not work, or need special handling such as fetch-based networking. area status notes core PHP + common extensions works json, mbstring, dom, etc. per build files works virtual FS; persist via OPFS/IDB database SQLite only MySQL queries need translation outbound HTTP limited via fetch with CORS restrictions exec / proc_open / sockets no browser sandbox

Step 4 — understand networking limits

PHP code that calls external APIs (file_get_contents("https://…"), cURL) cannot open sockets from a browser. Runtimes can route such requests through fetch, which subjects them to CORS: requests to servers that do not allow cross-origin access fail, unless proxied through your own server. Plan for this when running code that installs plugins from remote registries or calls web services; WordPress Playground handles some of these cases with special support and a proxy for its own use.

Step 5 — choose good use cases

PHP in the browser is excellent for: interactive documentation and tutorials where readers edit and run code; demos and previews of PHP applications (WordPress themes and plugins) without hosting a server per visitor; plugin and theme testing in CI or for reviewers; offline-capable educational tools; and reproducible bug reports (a playground link that sets up the exact site state). It is not a replacement for hosting production sites: every visitor runs their own copy, data stays in their browser, and performance is below native PHP.

Performance and size

The PHP Wasm binary is several megabytes compressed, plus the application’s files (WordPress is tens of megabytes uncompressed). First loads take seconds; cache the runtime and application bundle with a service worker. Request handling is slower than native PHP — often several times — but adequate for a single user browsing an admin interface. Avoid running heavy batch jobs in it.

Persistence and sharing

By default, everything resets on reload. For tools that need persistence, mount OPFS-backed storage for the site directory or periodically export the virtual file system and database to IndexedDB. For sharing, serialise the setup steps (a blueprint describing which plugins to install and which content to import) rather than the whole file system, so a link can reconstruct the same environment anywhere.

Testing PHP plugins and themes in CI

Because the same runtimes run in Node, they are useful outside the browser: a CI job can boot a disposable WordPress instance, install the plugin under test from the build output, import fixture content and run checks — PHP unit tests, HTTP requests against the site, or browser tests with Playwright pointed at a locally served playground — without provisioning a database server or a PHP container. Each job starts from an identical state described by a blueprint, so results are reproducible, and testing against several PHP and WordPress versions becomes a matrix of configuration values rather than a matrix of images to maintain. The same blueprint can be attached to a pull request as a preview link, letting reviewers click through the change in their own browsers without a staging server.

Security considerations

PHP in the browser runs code with the page’s origin, inside the Wasm sandbox. The PHP application cannot reach the user’s file system or open arbitrary network connections, but it can make same-origin requests through the routing layer and, via fetch-based networking, cross-origin requests that CORS allows. If you let users run arbitrary PHP (a playground or tutorial), host it on a separate origin from anything sensitive — your main application, accounts, cookies — so a malicious snippet cannot use the page’s origin to act on the user’s behalf. The same advice applies to any in-browser code execution product.

Memory and long sessions

The PHP interpreter, the application’s files in the virtual file system and the SQLite database all live in the module’s memory or in browser storage. Large sites with many uploaded media files can push memory use high; keep demo content small, store media outside the virtual file system where possible, and recycle the runtime between unrelated sessions.

Expected output

A documentation page runs PHP snippets in the browser with output in under 100 ms after the runtime loads; a WordPress Playground instance boots a site with a chosen plugin and demo content in a few seconds from cached assets; database queries go to SQLite; external API calls work only to CORS-enabled endpoints; and reloading restores the site from OPFS-backed storage.

Gotchas

  • Expecting MySQL. It cannot run in the browser. Use SQLite and translation.
  • Raw sockets and processes. Not available. Route HTTP through fetch; avoid exec.
  • Assuming persistence. The virtual FS resets on reload. Mount persistent storage.
  • Serving production sites this way. Each visitor runs their own copy. Use for demos and tools.
  • Large first loads without caching. Cache runtime and app bundles.
  • Running user PHP on your main origin. Snippets act with the page’s origin. Host playgrounds on a separate origin.

Performance note

Rendering the WordPress dashboard took about 350 ms per request in PHP-in-Wasm on a laptop after warm-up, compared with roughly 80 ms on a native PHP server with SQLite — slower, but fine for one interactive user.

Time to render a WordPress admin page Milliseconds to render the WordPress dashboard with PHP compiled to Wasm in the browser after warm-up and with native PHP on a local server, both using SQLite. ms per request native PHP + SQLite 80 ms PHP-in-Wasm in browser 350 ms

Frequently Asked Questions

Which PHP versions are available? Builds exist for several recent PHP versions; Playground lets you choose.

Can Composer run in the browser? Installing dependencies needs network access to package registries; prepare vendor/ ahead of time instead.

Does Xdebug work? Debugging support depends on the build; check current runtime documentation.

Can it run in Node? Yes — the same runtimes have Node builds, useful for testing and CLI tools.

Can PHP-in-Wasm be used for CI tests? Yes — the Node builds boot disposable WordPress or PHP environments from a blueprint without a database server or container.

Should a PHP playground share an origin with my app? No — host it on a separate origin so arbitrary code cannot act on cookies or data of your main application.

← Back to Other Languages in the Browser