Creating WordPress Playground helped me grow as an engineer, leader, and product person. This post, focusing on the engineering side, highlights the technical contributions I’m particularly proud of.
- WordPress Playground – I wanted a live WordPress example in my tutorial and ended up revolutionizing WordPress. Playground is the platform that lets you run WordPress instantly on any device without a host via a mixture of WebAssembly, TypeScript, C, PHP, and low-level parsers. It’s my most successful, most technically advanced project to date.
- Blueprints – A Dockerfile-like build system that’s the single largest reason to use WordPress Playground. You write a JSON snippet, say
{"plugins":["woocommerce"]}, and Playground creates a WordPress site to your exact requirements. There’s an entire community gallery of Blueprints where a single click can spin something as wild as a WordPress-based RSS reader. Blueprint are portable between browser, servers, mobile apps, and so on. - Data Liberation – It’s the dream of owning all my content in a local-first way while also synchronizing it across my devices and collaborating on it with others. A lot of the specific code contributions below were motivated by this project.
- TLS 1.2 implementation and TLS ↔ fetch() gateway. I’ve done it to support HTTPS network calls in WordPress Playground. Native PHP talks to the internet by streaming bytes over raw network sockets – Playground’s WebAssembly version of PHP cannot do that in a web browser. My best shot was to emulate a TLS server and run the actual network requests via
fetch(). It worked really well! - create-wp-site (and the entire php-toolkit) – A CLI tool to turn your static content into a WordPress site. It speaks Markdown, HTML, EPUB, crawls websites, accesses git repos, and more. Internally, it uses Playground and a lot of the Data Liberation tools listed below.
- Playground WordPress boot protocol. Back in the days, every Playground-based tool had to figure out how to boot WordPress on its own. It was a big adoption barrier. Bero, Brandon, and I did a lot of scoping and came up with a single
bootWordPress()function suitable for Playgrounds in all runtimes – browsers, servers, VS code extensions, you name it. - Synchronizing two Playgrounds – I’ve been exploring ways of keeping two WordPress sites in sync and came up with an approach of recording all filesystem operations and SQL queries on site A and replaying them on site B. The FS operation journal turned out to be exact technique Playground uses to synchronize WordPress files with a local directory.
- Private APIs for Gutenberg JavaScript packages – I’ve conducted a year-long series of alignment discussion to resolve the tension between WordPress core, which offers an indefinite backwards compatibility, and Gutenberg plugin, which offers on BC guarantees but is merged into core. We ended up inventing a system of cross-package private APIs.
- Streaming HTML tag parser – I’ve built it with Dennis Snell for WordPress core. It became the backbone of the full HTML parser in WordPress, enabling fast and secure HTML processing in PHP. It consumes much less memory than PHP 8.4’s HTMLDocument. If you’re curious, I wrote a separate article about it.
- Streaming XML parser – WordPress export format (called WXR) is based on XML but not all WordPress hosts provide the XML parsing PHP extension. That’s why the content importer is a plugin and not a built-in feature. I wanted a streaming importer in WordPress core so I’ve built a parser intended for WordPress core that you can use on any WordPress host. Internally, it uses Dennis Snell’s streaming UTF-8 decoder.
- MySQL ↔ SQLite gateway – WordPress supports only one database: MySQL. And yet, Playground runs WordPress on SQLite. How? Via a custom MySQL ↔ SQLite translation layer! In 2023 I’ve worked with Ari Stathopoulos to translate queries via the PHPMyAdmin MySQL query tokenizer. It went a long way, but failed to process syntax nuances. I’ve tried 32 different parser libraries and generators and couldn’t find anything capable of parsing all possible MySQL queries. AI also couldn’t solve it entirely, so I ended up building a new parser. It can parse nearly all queries. Jan Jakeš, who took over that work, brought over 70k unit tests from mysql-server and got them all to pass. It’s limited to running as a WordPress plugin, so In 2025, I’ve also prototyped a network-level gateway.
- HTTP Client (original PR) – I’ve built it to give the Data Liberation project a portable, highly compatible client that can run on all WordPress hosts and eventually become a part of WordPress core itself. It’s non-blocking, supports streaming, progress monitoring, HTTPS, concurrent downloads, and runs on bare PHP 7.2+. It exchanges bytes via PHP sockets, parses chunked encoding et al., and even implements a mini event loop via stream_select. And it works with the TLS gateway in Playground!
- Git client – I wanted to give Playground a fast access to git repos and GitHub REST API was too slow. I ended up building a generic streaming Git client that’s also useful outside of Playground and makes a great Data Liberation data source. It’s supports basic git operations like committing, amending, pushing, pulling, rebasing, and merging, but also more advanced features such as sparse checkout and three-way merge. For maximum interop, it stores data in a
.gitdirectory in the same format as the regular CLIgittool. - ZIP encoder and decoder – As with most PHP extensions, ZIPArchive is not universally available. I’ve built a brand new dependency-free ZIP encoder and decoder to have a universally portable library for Data Liberation. It’s streaming and capable of fetching specific fragments of remote ZIP files – e.g. it could download just the 100KB we need to read a readme.txt file from an otherwise 250MB archive. That’s especially useful in Playground where minimizing the transfer size reduces loading times and greatly improves the user experience. I’ve also explored a similar decoder in TypeScript.
- Filesystem and ByteStream APIs – A standardized data access interface for Data Liberation that makes accessing content. With LocalFilesystem, GitFilesystem, ZIPFilesystem, SQLiteFilesystem, RequestReadStream et al., accessing content is easy even if it’s stored in a zipped XML in a remote git repository.
wp_rewrite_urls– One pain of migrating WordPress sites is updating the URLs. The existing migration tools use regexps and database UPDATE queries which are inaccurate and slow. I wanted a reliable importer, so I’ve built this function that unwraps the layers of structured using streaming parsers. It parses HTML, then WordPress block comments, then JSON inside them, then URLs using a WHATWG–compliant parser, replaces the base URL if needed, and re-encodes the block markup.
Leave a Reply