Menu
Tools
fyro.net utilities

We Implemented WordPress Piplets Natively

Jordan Goldman WordPress

This blog no longer runs WordPress. It runs a piplet.

If you missed the announcement, Matt Mullenweg revived a 2007 experiment at WordCamp US 2026 called a piplet: a single, self-modifying PHP file that stores its own content. No MySQL, no SQLite, no vendor directory. The design targets are about 25 megabytes of content in one file, roughly four million words, loading in something like 50 milliseconds. The WP Clan has a good write-up of the specs. It is not on the core roadmap, it is an experiment in the same lineage as the SQLite work and WordPress Playground, and the point of all of it is to cut bloat.

We liked the idea enough to just build it.

What was here before

The old install was WordPress with a child theme, Akismet, a math captcha plugin, and eleven posts. That came to 139 megabytes across roughly 4,400 files, plus a twelve-table MySQL database, to serve eleven articles about DNS records and CIDR notation.

What is here now

Four PHP files, about 155 kilobytes in total:

  • piplet.php holds every post, comment and setting as a PHP array literal, and rewrites itself when something changes.
  • index.php is the front controller and renders the site.
  • admin.php is a small panel for writing posts and moderating comments.
  • spamguard.php cleans incoming comments.

Time to first byte on a post page sits around 12 milliseconds, measured on the box itself, so it comfortably clears the 50 millisecond target. That is not clever engineering on our part. It is what happens when a page load stops being twenty thousand lines of framework and a database round trip.

The interesting part is writing to yourself

Reading a piplet is trivial. You include it and you have your data. Writing to it, safely, is where the actual problems live, and there were three worth mentioning.

Splicing the file without destroying it. The data lives between two marker comments, and a save rewrites only the region between them, so a failed write can never damage the engine above it. The obvious failure mode is content that contains the marker text, which would make the file split at the wrong place. The fix is to put an apostrophe in the marker. PHP's var_export() always escapes an apostrophe inside a string value, so no post and no comment can ever forge a marker, no matter what someone types. The new file is then syntax-checked with token_get_all() and round-trip verified before it is allowed to replace the original, and the replacement itself is an atomic rename.

Two writers at once. Two people commenting in the same second would each read the file, add their comment, and write back, and one of them would lose. Every mutation now takes an exclusive flock, re-reads the file from disk inside the lock, applies the change and then saves. The re-read is the part that matters, and it is why the engine is wrapped in a class_exists() guard: that lets the file safely include itself again to pick up whatever landed since.

Backups that survive a brute-force attempt. Every save keeps a rolling backup. That sounds fine until you notice that failed logins and comment rate-limit counters also write to the file, so a few minutes of someone guessing passwords would quietly rotate every real backup out of the ring. Saves now fingerprint only the durable content, and skip the backup when nothing anyone cares about actually changed.

Comments

Both comments in the old database were link spam, in two different languages, which set expectations appropriately.

Links in comments are not moderated, they are destroyed. Markup is stripped even when it arrives entity-encoded, then URLs, protocol-relative hosts, bare domains, email addresses, BBCode, markdown links and the usual obfuscations go too, including hxxp, example (dot) com and example[.]net. What is left gets scored, and a high enough score is refused at the door rather than published full of holes.

One thing worth calling out for the audience of this particular blog: IP addresses and CIDR blocks are deliberately left alone. An early version of the filter helpfully redacted 10.0.0.0/8 out of a test comment, which is not a great outcome on a site with an article about CIDR notation. The same pass caught "DNS over TCP.It is also used for..." as a hostname, because .it is a real top-level domain and a missing space after a full stop looks exactly like a domain. Bare hostnames are now only matched against distinctive suffixes, and anything ambiguous has to carry a path before it counts.

Posting a comment takes patience

Rather than a captcha, posting is gated on a button you press and hold. Each page view is assigned a random delay between five and twelve seconds, followed by a narrow window in which the comment is accepted. Release early and nothing happens. Hold past the window and the form lapses, because an upper bound is the whole point: without one, anything that simply holds the button forever still gets in.

The delay and the window are signed into the form token, so the browser cannot shorten either one, and neither number is ever sent to the page. Not in the markup, not in a data attribute. The button asks the server which of three states it is in and gets back one word, so the only way to find the deadline is to sit and wait for it, which is the cost we were trying to impose in the first place.

To be straight about what this does and does not do: what the server can actually verify is that the form was submitted no sooner than the delay and no later than the window closing. Real hold duration is not verifiable from the server, since only the browser knows when a finger went down. Something determined enough to poll and time its submission will still get through. It costs a fixed number of seconds per attempt and some genuine work, and that is enough to make drive-by comment spam not worth the trouble.

WordPress on rc7.net

Separately, and on the strength of the same commitment to slimming the software down, WordPress is now an option over at rc7.net, alongside the LAMP containers, VPS instances and baremetal. A project willing to seriously ask whether it needs a database at all is a project worth hosting.

We are not going to pretend a piplet is right for everyone. Eleven posts and a handful of comments is close to the ideal case, and a site with fifty contributors and a media library wants a real database. But it turns out that a personal blog was never one of those, and it took a 2007 experiment coming back around for anyone to say so out loud.

0 comments

No comments yet.

Leave a comment

Plain text only. Links, addresses and HTML are stripped automatically, so there is no point posting them.

Press and hold - or focus it and hold Space - then let go when it says to. Releasing too early or holding too long both start over.

RSS feed · Served from a single self-contained PHP file. No database.