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.
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.
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.
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.
Related
- Running Python in the browser with Pyodide — a comparable interpreter port.
- Running SQLite in the browser with Wasm — the database side.
- Caching Wasm with a service worker — offline loading.
- Comparing payload size across languages — download costs.
← Back to Other Languages in the Browser