How I Built The Blog…again… and again!
- Chapter 1: The Site Was Fine, Then It Wasn't (0:10)
- Chapter 2: Leaving WordPress (7:55)
- Chapter 3: Payload, and Why It Was Almost Right (15:03)
- Chapter 4: The Gutenberg Problem (26:13)
- Chapter 5: EmDash, and the Plugin Wall (32:11)
- Chapter 6: ☕ The Editor Is Part of the Work (37:15)
- Chapter 7: Back to WordPress, and the Reason (42:20)
- Chapter 8: VoicePress (49:22)
- Chapter 9: Where It Stands (54:47)
Chapter 1: The Site Was Fine, Then It Wasn’t
Let me be honest about something before I start: this post is about the actual site you are reading. Not a fever dream, not a fictional festival, not a thought experiment dressed up as a blueprint. It is a retrospective about a website I have run for a very long time, the mess I got it into over the last few months, and what I ended up doing about it.
I want to be clear about that from the off, because everything else I write on here tends to be speculative in some way, and this one is the opposite. Every platform I am about to name, I actually installed. Every plugin I am about to describe, I actually wrote. The failures in particular are real, which is presumably why they are worth writing down.
The trigger was not a dramatic event. That is the disappointing truth of it, and I have thought about whether to make it sound more dramatic than it was and decided not to.
For a long time, the site was fine. Genuinely fine, not fine-in-the-way-people-say-fine-when-they-are-gritting-their-teeth. It had been running on WordPress for years. It had a theme I had customised into something recognisably mine. It had plugins I had installed and mostly ignored, which is the traditional WordPress way. Posts went up when I felt like putting posts up, and the ones that got read got read, and the ones that did not get read sat there being unread, which is the deal with every blog ever written.
Then, at some point that I genuinely cannot pin to a date, the management stuff started.
I do not want to relitigate any particular decision made by Automattic. That is not what this post is for, and there are people who were not part of it and who have opinions about all of it. What I am going to say is narrower and more practical: the direction of travel was somewhere I did not want to be going, and I noticed it slowly, over months, and I did not like where the road went.
Here is the thing about that kind of change. It does not arrive as a single bad afternoon. It arrives as a series of small disagreements that you can each talk yourself out of, because each one on its own is completely reasonable. It is only about halfway through that you can look back at the route you took and realise you never actually agreed to it.
And then I made a decision that a lot of people make and I have never entirely regretted.
I looked at how much of my relationship with WordPress depended on WordPress itself.
Because here is what I had not properly reckoned with. My site was self-hosted. It had been self-hosted for years. It lived on my own infrastructure, in my own Docker setup, with my own backup routine and my own domain. But the software — the actual code that turns markdown into a website — was somebody else’s, and it was becoming somebody else’s in a direction I had not chosen.
So the question I started asking myself was not “is WordPress good?” It had been good to me. It was still good to me.
The question was: what would it take to stop depending on it entirely?
And I want to be honest about how that question sounded the first time I said it out loud, because it sounds dramatic. It sounds like a manifesto. It sounds like a man who has read too much about digital sovereignty and decided to go live with it.
That was not what was happening. What was happening was much smaller and much more practical.
I have built things on this site for years. I have built features that did not exist in any plugin. I have written code against this site’s internals. And at some point I realised that the thing I was most heavily invested in was a piece of software whose direction was being set by somebody else, and that if I ever disagreed strongly enough with that direction, all of my work would go with it.
Which is a very different motivation from ideological opposition. I was not looking to drop WordPress because I think it is badly run… mostly. I was looking at it the way you look at any dependency: what does my continued reliance cost me, and what happens to the things I have built when it goes wrong?
And that second question is the one that will decide the shape of this entire post.
I want to give you a concrete version of it, because “dependency risk” is the sort of phrase that sounds like something from a risk register rather than something that kept somebody awake.
So the honest version of the dependency is not “can Automattic delete my posts”. Obviously not, and I have never seriously worried about that.
The dependency is subtler and I had to think my way into it. It is not that the platform disappears. It is that the platform changes, legitimately and reasonably, and everything I have built on top of it has to change with it, and I do not get a vote.
And that is true of the software underneath the software. The posts are written by me. The site is my own hardware. But the editor they are written in, the admin they are managed through, the shape of the data they are stored in — all of that belongs to the platform, and all of it can move under me without anybody setting out to break anything.
Which is a risk that has always existed and is not the fault of anybody in particular. It is just what using a platform means. It was a good deal for a decade.
What I was actually doing, I think, was refusing the deal for the first time. Not because it had become a bad deal, but because I had noticed that the platform had become load-bearing in a way it had not been when I started, and I did not want a load-bearing platform to be a thing I had not chosen.
There is a name for wanting that, and the name is sovereignty, and I have written about it before. But I also want to be careful here, because sovereignty has a way of becoming an excuse for refusing to change, and I would rather be honest about that risk than pretend the argument was pure.
Sometimes “I want to own my own infrastructure” is a legitimate architectural preference. Sometimes it is a way of saying “I do not want to learn a new tool”. I had to hold both of those in view for six months and be honest about which one was driving me on any given day, which turns out to be a much harder thing to do than I expected.
The answer, as it turned out, was that both of them were driving me the whole time, and they were pointing at the same platform.
That is what made this hard. Not the decision. The decision was easy. The inventory was hard.
Because when you actually go and look at what your site depends on — properly look, not the comfortable mental version — you find that the list is longer than you think and the reasons are more embarrassing than you’d like. There are plugins there because of things you did years ago and cannot now remember. There are decisions in the theme that you are not sure you would make again. There are three or four things in there that are load-bearing in a way you will only discover the week you try to remove them.
I went into this thinking I was shopping for a new CMS.
I came out of it understanding my own site considerably better than I had going in, which I suspect is the most common actual outcome of a migration attempt, successful or otherwise.
So here is the shape of what happened. I left. I looked at a lot of things. I spent a long time somewhere I liked a great deal and eventually left that as well. I came back, and the reason I came back is not really about WordPress at all — it is about the editor, about staying self-hosted, and about not paying Matt any money to carry on with it.
I have tried to keep that honest, because the version of this story where you come back because you lost is much easier to tell than the version where you come back because you chose.
Let’s start where it started, which is unfortunately not with an interesting problem.
Chapter 2: Leaving WordPress
The first thing I should say about leaving WordPress is that I did not leave it in a rage. I want to be really clear about that, because I have seen a lot of posts written by people who did, and I am aware of how useful that genre is for engagement, and I am not doing it.
I left it carefully. I made a list. I took a copy of everything. And I spent a long time — genuinely a long time, not an afternoon — looking at alternatives before I moved anything.
That search is the least interesting part of this post to write about and almost the most useful, so I am going to give it the space it deserves.
Here is the shape of a blog. There is content — posts, and things that are not posts. There is a way of writing and formatting that content. There is a place to put images. There is somewhere for comments, if you have comments. There are a few features the site does that are not stock features, which for me are the important ones, and which in this context turn out to matter more than anything else.
Almost every platform decision is really a decision about one of those.
The trick is that the platforms make you evaluate them on the features they have rather than the features you have, and if you are not careful you end up choosing something on the strength of a capability you admire and then discovering it cannot do the thing you actually required.
So before I looked at a single platform, I wrote down the list of what my site does that is not standard. It is shorter than you might expect and it turned out to be the whole basis of the decision.
It turned out to be a short list, which was mildly disappointing. My site is not complicated. It is posts, a menu, an audio player that almost nobody uses, and a fairly deep archive that I care about more than is strictly reasonable.
Having written that list down, the search became much easier, because now I could rule things out.
And what ruled most things out was not a missing feature. It was the editor.
I want to dwell on that for a moment, because I do not think people outside of this industry take it seriously enough, and it is the thing that determined the outcome of my entire search.
Every single platform I looked at had the capability I needed and the editor I did not want. Not one of them — not the markdown blog engines, not the CMS platforms, not any of them — had both. And this was not a close call. I am not talking about preferring one editor to another. I am talking about a difference in whether writing feels like work or like something I want to do.
I have written on this site for years. I have written posts that took me weeks and posts I wrote in twenty minutes at half past midnight. In every platform I tried, both of those options were reduced to the same slightly worse version of the same slightly wrong feeling.
That sounds like a small complaint. It is not. It is the whole thing.
The platforms fell into three groups, and the order I take them in is the point — the reason I ended up where I did is entirely down to which group I spent my time in.
The first were platforms that were never going to work, and which I could eliminate in about ten minutes each. The full enterprise content systems, built for organisations with editorial workflows and governance and a marketing department — which would have given me a hundred features I do not want in exchange for a great deal of ceremony I definitely do not want. Nothing wrong with them. They are excellent. I am not a publisher with a staff.
The second was the group that ate the whole summer, and it is the one that actually matters here.
Platforms that could genuinely have worked. Capability all there, every feature I needed present and correct, technically better than what I already had in most measurable respects. And the only real objection was the editor.
That is an awful category to spend months in, because there is no way to resolve it analytically. I could build a feature comparison table covering every one of them and it would tell me nothing, because the variable doing the work is not a feature. It is whether I want to write.
Which is a much harder thing to write a comparison table about, and a much more common thing to get wrong, because capability is countable and wanting to write is not.
The third is the one I keep coming back to and want to defend, because I think lightweight markdown-first platforms get dismissed unfairly.
They are the most philosophically aligned with everything I had been arguing for. Your content is files. Your site is built from them. No database, no admin, nothing to administer, and the whole thing is version controlled by definition. If you take the sovereignty argument seriously, that is where it points, and I spent a lot of time being convinced on paper.
And I could not write in any of them.
Because a static site has no editor. It has a text editor. Which is a perfect match for how I think about my files and a complete disaster for how I actually write, because I am not somebody who enjoys writing in a plain text editor and I am not going to pretend otherwise in order to make a point about file formats.
And here is the thing I did not appreciate at the time, which is that the two rejections have the same cause.
The editor ruled out the platforms that could do everything. It also ruled out the platforms that were philosophically perfect. Those are the two groups I liked most on paper, and I eliminated both of them for the same reason — not a missing feature, not a philosophical disagreement, but a surface I did not want to type into.
The one thing I will say in favour of everything I rejected is that nearly all of it was better than what I have, on measurable things. Better performance, better security defaults, less for me to maintain. I am aware that self-hosting is a choice with a running cost — upgrades, backups, being responsible for a server at two in the morning — and I have chosen it repeatedly and would choose it again.
But there is a finding in there, and it took me most of the search to see it.
I kept finding platforms that were philosophically more correct than what I had, technically capable of everything I needed, and that I did not want to write in.
Which is a strange way to fail. There is a version of this story where the answer is “I was too attached to my tools”, which is broadly true and not very interesting. The more useful version is that there is a real gap between a platform being philosophically correct and a platform being usable, and I had been conflating them — because the philosophical argument is the one you can have out loud, in public, at length, and the usability problem is the one you only discover at eleven at night when you are trying to finish something and the words will not come.
But I got to the end of that realisation eventually, and it reframed the search in a way I was not expecting. I stopped looking for a CMS that would do what my site does, and started looking for an editor that would feel right, and then asking whether anything could be built on top of it.
Which is how I ended up spending a lot of time with something that was never going to be the answer.
Chapter 3: Payload, and Why It Was Almost Right
Payload is where I spent the longest and ended up with the most to show for it, which makes it the most annoying part of the whole story to write.
Because it was good. Genuinely, properly, frustratingly good, and in several respects better than what I have now. I do not want to pretend otherwise and I am certainly not going to pretend otherwise in a way that makes coming back look inevitable, because it does not.
If you have not come across Payload, the short version is that it is a modern content system built on Next.js, where you define your content types in code and it gives you an interface for editing them. It is TypeScript, it talks to Postgres, and the content model is a file in your repository rather than something configured in an admin panel.
That last part is the one that hooked me, and it is worth explaining why, because it goes directly against everything I had assumed for a decade.
In WordPress, the shape of your content is a thing you configure. You go to a screen, you tick some boxes, and there is now a custom post type with three fields. It works, it is flexible, and it is stored in a database that nobody can read without a database.
In Payload, the shape of your content is code. It is a TypeScript file describing what a post is, what fields it has, what those fields look like, and what happens when you save one. You commit it. It gets reviewed. It gets deployed. There is no separate place where the content model lives and no way for the model and the code to drift apart, because they are the same thing.
That is a genuinely better idea. I am not going to pretend I discovered a reason to dislike it, because I did not.
And then there was the part that made me build two whole features on it before I had any confidence.
Because the thing about having your content model in code is that it is reliable in a way the WordPress equivalent is not. Once a Payload schema says that a post has a title and a body and a date and a series and an audio file, that is a fact. It is in the repository. It is versioned. It cannot be broken by somebody clicking something in an admin panel, and it cannot drift when you export and import your database somewhere else.
I built the series feature first. It was, by a distance, the more ambitious of the two, because series is structural — you are not attaching a tag to a post, you are building a relationship between posts and then rendering a whole interface that expresses it. Reading order, position in the series, a matrix view of how the argument branches, a progress indicator on the single post view, a themed presentation per series so a series could have its own visual identity.
None of that is difficult in a system where the content model is code. All of that is quite difficult in a system where you are negotiating with a database.
And I want to be accurate about what was happening here, because it is easy to make this sound more inevitable than it was.
This was not a build. Not at the start. When I arrived at Payload I had a category and a menu link, and I was perfectly happy with them, because I had not yet written a second series that needed more.
What happened is that I wrote one. The Bridlington Mesh, seven posts, and it needed a structure that a category could not hold — including the fact that the posts branch, and that hiding that behind a linear list buries the most interesting material in the middle. And by then I was somewhere that made building that kind of thing pleasant, so I built it. Slowly, over months, as features accreted rather than as a spec being implemented.
The matrix came last and it came because I could finally see the shape of the thing I wanted, and only then could I tell what was missing. You cannot design that view until you have enough of the series to see what its shape actually is.
That is not how software projects usually get described, but it is how mine get built. I do not know what a feature set should be before I have lived with the problem long enough to know what is annoying about it.
It is worth being specific about what the series feature does, because “grouping posts” undersells it and undersells why it was worth building.
The basic idea is that a series is a first-class thing rather than a category. It has its own page. It has its own appearance — its own colours, its own monogram, its own identity — so that a series reads as a body of work rather than as a list of related articles. When you are on a post, it tells you where you are in the whole, and it tells you what to read next.
And then there is the part I am genuinely proud of, which is the matrix.
Most series are linear. Post one, then two, then three. But some of them are not, and pretending otherwise is how you end up with a series where the interesting bit is hidden at the end. The Bridlington Mesh is the obvious example — the posts branch. There is a technical track and a conceptual track and a venue track, and a reader who wants the argument about whether the network can distinguish a phone from a cinema camera does not need to read four camera-build posts to get there.
So the matrix renders the shape of the whole thing as a grid. Every post is a cell. Relationships between posts are visible. You can see at a glance which posts are siblings, which are a spine, and which are optional excursions. And you can navigate by structure rather than by scrolling.
That is the feature I have not yet rebuilt. It is the one thing on the “not done” list that I actually miss, and I have caught myself more than once reaching for it on this site, typing something, and then remembering there is not a matrix here. That is what a missing feature feels like, and it is a useful reminder that “98% the same” is a description of the feature list and not of the experience.
There is also a small progress indicator on individual posts — a little visual marker showing how far through you are — which is a trivial thing to build and makes reading a long series feel like a journey rather than an archive. And a per-series theme cascade, which is how each series gets its own colours without any of it being hardcoded.
All of that, built twice. Which brings up the question of why I built any of it in the first place, which is worth a sentence.
Because I wanted to read a long series the way I wanted to write one. That is all. Every feature in that list exists because the way this blog presented a body of related writing was not how I wanted to read one, and by the time I knew that, I was deep enough into a new platform that making it seemed like the obvious next thing to do.
Which is worth noticing as a pattern, because it repeats: nothing on this list was designed up front. Each one arrived after I had lived with the inadequacy long enough to be annoyed by it, and by then I had the means to fix it. The platform did not create the need. It made answering the need cheap.
And that turns out to be the actual reason neither of these was a reason to leave WordPress. You cannot list a capability as a requirement for a migration when you did not have it before the migration started.
The audio feature came second and was, in its own way, more revealing, because it is the one that should have been portable and turned out not to be.
And it started even further from necessity than the series one did. There was no problem to solve. Nobody had asked for audio. The whole thing began as a thought — what if I ever wanted to turn this into a podcast, or what if I wanted to show that I can do accessible audio properly as a demonstration of skill — and that thought became a collection that linked posts to files, and the files got chapters, and the chapters got parsed.
The requirement, such as it was, is simple enough. Attach an audio file to a post, parse its chapters, and present a player with a chapter-aware scrubber so somebody can jump to the bit they want rather than scrubbing blind through forty minutes with a progress bar.
Parsing the chapters was the interesting problem. The audio files are chaptered, and the chapter information lives inside them, and getting it out means running a media tool over the actual file on disk and reading what it finds. That works fine. You get the file, you probe it, you get a list of chapter markers with timings, you store them against the post.
And then the awkward bit: when the audio needs converting to MP3 for delivery, the chapter titles have to survive the conversion, which they do not by default, and I spent a while working out why and then writing a fix that puts the right version of the ID tag in so the titles come through. It is an absurdly small problem to have had and I am oddly proud of the fix.
So by the end of it I had a site with a genuinely good content model, a series feature that did everything I wanted, and a proper audio player with chapter navigation.
And the plugins on this site exist because of that. Not because I designed them for WordPress, and not because I planned to need them. Because I came back from Payload, found that WordPress had nothing remotely like either of these things, and discovered that the way I was living had outgrown the tools I had started with.
Which is worth saying plainly, because it inverts the story I had been telling myself. I did not carry these features to a new platform. I came back with nothing and discovered what was missing.
And I was still not happy, and it was not the features.
I have thought hard about what the problem actually was, because “I didn’t like the interface” is not a satisfying answer to “why did you spend months building things and then throw it away”.
It was the writing.
The editor — the thing you actually spend your time in, the box you type words into — was not as good as the thing I already had. Everything around it was better in ways I could measure. The typing itself was worse in a way I could feel, and I could not make it better, because that is not what the platform was optimising for.
And I want to be careful here, because there is a version of this argument that is just “my workflow is special and nobody else could understand it”, and that version is both untrue and useless. Plenty of people write on Payload all day and love it.
But the thing I had in WordPress was specific. It was the Gutenberg editor, which fits the way I write in a way that nothing else I tried came close to. And the honest position is not “Payload’s editor is bad”. It is that I had spent years becoming fluent in one particular writing surface, and the cost of leaving it was higher than the benefits on offer justified.
I built two good features and I could not make myself want to type in the thing I had built them for. That is the whole of it. It is not a technical objection. It is not a strategic objection. It is closer to writing with the wrong pen.
So I did something stupid, which is that I went looking for a way to keep Payload and fix the editor.
Which does not exist, but I did not know that yet.—
Chapter 4: The Gutenberg Problem
The thing I could not let go of was not Payload. It was something much more specific.
It was the editor I already had.
I have been writing in the Gutenberg editor for a very long time now. Long enough that I do not really use it so much as play it. There are things I do in it that I could not tell you how to do — where I put my hands, which buttons I reach for without looking, the particular sequence that gets a paragraph of body copy formatted correctly.
I did not acquire that. I did not go and learn it. It accreted, over years of writing, into something my hands do faster than my thoughts can describe.
And then I went and used every other editor on the market.
Here is what I found, and it took me most of a year of evenings to find properly. Almost every modern editor is better. Better in ways that are objectively demonstrable. It has better keyboard shortcuts, or a better block system, or better handling of something. If you handed the same editor to a different person and asked them to judge it in isolation, they would probably prefer it to mine.
It just does not fit me. And I do not mean that as a preference, because preferences can be argued with. I mean that I could feel the difference in how much I got written.
On this site’s WordPress editor I will write two thousand words in an evening without thinking about the writing. On a platform that is, by any reasonable measure, technically superior, I will write six hundred words and feel vaguely guilty about it. Same brain, same evening, same amount of time. That difference is not about the software being good or bad. It is about a thing I have spent years making my own.
So I started looking for the thing I actually wanted.
I wanted the Gutenberg editor. On something modern.
Now — before anyone points it out in the comments, and somebody will — this is in principle an entirely reasonable thing to want. The editor is open source. It is a JavaScript application. There is nothing about Gutenberg that requires it to run on PHP talking to MySQL. It is not in principle impossible to have that editor somewhere else.
The difficulty is everything around it.
Gutenberg does not arrive alone. It arrives as a package, and the package expects an environment. It expects to be talking to a REST API that has a particular shape, receiving blocks with particular attributes, saving through a particular endpoint, retrieving through another, handling revisions through a third. It expects to be running inside the admin of a WordPress site, loading its own styles to match a theme, registering itself with a block registry that the theme has to know about.
Every one of those expectations is reasonable on WordPress and an enormous amount of work anywhere else.
So I went looking. Genuinely looked — for a headless WordPress with a modern frontend, for a fork that decoupled the editor, for a static site generator with a Gutenberg-compatible block parser, for anything at all that would let me type in the thing I am used to typing in.
And what I found, consistently, was a project that would let me use Gutenberg data format on a different platform. Blocks in, blocks out. Parse the markup, render the blocks, serve a static site.
That is not what I asked for.
There is a difference between a site that stores content as Gutenberg blocks and a site where the Gutenberg editor works as an editor, and the difference is the entire point. One of those is a content format. The other is a writing environment. Everybody who has ever migrated away from a word processor recognises this immediately: you can open your document in the new program, it can read your file perfectly, and it is still not the thing you were working in.
And here is the bit that took me longest to accept, and which I think is the actual lesson of this entire post.
There is no version of this that exists.
I do not mean it has not been built yet. I mean it is not a question somebody has failed to solve. It is a question that resolves against the ecosystem’s direction.
Everything modern is moving away from the WordPress stack. To Next.js, to Postgres, to SQLite, to edge rendering, to anything that is not PHP and MySQL. That movement is mostly good and I support it. And Gutenberg is, structurally, part of the thing being moved away from. It is a PHP-and-WordPress thing that people love, being carefully reimplemented elsewhere.
But “being reimplemented elsewhere” is the problem. It is not a port. It is a replacement, and a replacement that has to be funded, and funded things have priorities set by whoever is funding them. Nobody is funding “the exact Gutenberg editor on a non-WordPress stack” because the people who would fund it are, overwhelmingly, happy with WordPress.
I want to be careful how I say that, because there is a version of this that sounds like a complaint against developers, and it is not aimed at developers at all. The people building these things are doing excellent work and most of them would tell you exactly this if you asked them.
It is a structural observation. The thing I wanted would only exist if somebody wanted it badly enough to maintain it forever for my benefit alone, and that is not a reasonable thing to expect and I would not ask for it.
So I accepted it.
I accepted it the way you accept a lot of things when you are running a small operation: with a bit of resentment, and a lot of practicality, and a clear-eyed view of what my actual options were.
And then, because I had built two good features on a platform I was about to leave, I did the only sensible thing.
I took them with me.
Chapter 5: EmDash, and the Plugin Wall
Before I say anything else about EmDash, I want to be clear that this is the part of the story where I got most excited, and I did not want to get it wrong by implying it did not work out.
EmDash is going in the right direction. I think that sincerely. And I think the specific thing it is doing — the individual plugin and theme store, where third parties can build and publish their own extensions — is the correct model for a platform in 2026, and I would like to say that again because I do not think it gets said enough.
So let me be precise about the three separate problems, because they are separate and they get conflated constantly.
The first is the editor, and I have already made this point about every other platform, so I will make it again briefly: it feels very basic, and it does not let you adjust the layout on a post-by-post or page-by-page basis. That is the same objection that eliminated every other platform in the previous chapter, and I found it genuinely odd to arrive at a platform I had been hearing good things about and to discover that the thing that had ruled out everything else was still the thing ruling things out.
But the editor is a preference argument, and preferences can be wrong. The other two are not.
The second problem is that my plugins did not work under it. Both of them. The series plugin and the audio plugin. Not because of anything I did wrong — they are not tightly coupled to my site in the way people assume, they are proper plugins — but because they do not run.
And the third problem is the interesting one, and it is the reason I am writing this chapter rather than just a paragraph.
They do not run because of permissions. Not security permissions — access. My plugins need to reach parts of the system that EmDash’s plugin model does not currently let them reach.
Let me make that concrete, because “it did not work” is the least useful sentence in software and I have a decent idea why mine did not work.
The audio plugin is the more instructive of the two.
Part of what it does is an admin screen where I can upload a file, which sounds trivial and is not. In WordPress that means hooking into the admin, adding a meta box to the post editing screen, registering an endpoint that the browser can call, handling the upload, and creating an attachment in the media library — a database record pointing at a file that now exists on disk.
Then, separately, it needs to read that file back off disk to find the chapter markers inside it. So the plugin does not just need the media library. It needs the actual file path, on the filesystem, and it needs permission to go and run something against it.
And the series plugin has a different flavour of the same problem. It registers a custom content type, which is ordinary. It adds fields to the editing screen, which is ordinary. But it also hooks into the customiser — the thing that controls theme options — because each series gets its own theming, and the series plugin needs to feed values into that.
Two different plugins, two completely different mechanisms, and the same underlying issue: both of them want to reach into the running system in ways that go beyond the plugin boundary as EmDash currently draws it.
And I want to be fair to EmDash here, because I can see how that boundary got drawn and I do not think it is stupidity.
The natural instinct for a modern platform is to draw the line tightly. Make plugins declarative, make them safe, make them unable to do arbitrary things to the filesystem or the database. That instinct is correct. It is what stops somebody installing a plugin and taking the site down. It is the opposite of WordPress, where a plugin can do anything at all to anything, and where that has been a genuine security problem for twenty years.
But the tighter boundary that makes a platform safe is the same boundary that makes it incompatible.
And there is no easy way round that, because the plugins that do what mine do — that read files, that transcode media, that register custom types, that hook the customiser — are precisely the plugins that need the looser boundary. They are plugins that do real work, not plugins that add a settings screen.
So the honest summary of the EmDash situation is: it is a young platform, it is going the right way, and the plugin store is the right idea, but the plugin API does not yet give a plugin enough access to do anything genuinely substantial. My plugins are not exotic. They are not doing anything weird. They are doing ordinary things — custom content types, file handling, a transcode step — and they cannot do them.
Which is frustrating, because of all the platforms I looked at, it is the one whose direction I agree with most.
But agreeing with the direction and being able to work in it today turn out to be two completely different questions, and I had been quietly assuming they were the same one.
That assumption has a name and I have written about it before.
Chapter 6: ☕ The Editor Is Part of the Work
I made this argument properly in a post called The Sovereign Stack — Why Britain Is a Digital Tenant, and How to Buy the Freehold, and I stand by every word of it. If you have not read it, the short version is that when you rent your infrastructure, someone else gets to decide what it becomes.
But I have realised that argument has a gap in it, and the last six months have made the gap obvious.
I argued about the infrastructure. Own your own machine, own your own storage, own the only copy of your own work. That is correct and I am not walking it back. But it turns out there is a second layer of dependency that I had not thought about clearly, and it is not about servers at all.
It is about the thing you type into.
Here is the position I was in at the start of this whole thing, before I decided to leave anything.
I am self-hosted. My content is my own. My files live on infrastructure I control, with my own backups, on my own domain. Nothing about the survival of my work depends on WordPress continuing to exist as a company.
And yet every single piece of work I have ever published was written in an editor that WordPress provides, in a shape that WordPress defines, and if WordPress tomorrow removed the block editor, all of that would need converting.
I do not think that matters very much. A markdown file is a markdown file. But notice what the actual dependency was. Not the storage. Not the database. Not even the publishing.
The dependency was the surface I type into.
And this is the part I got wrong for years.
I thought of the CMS as a container for my work. Something my work lived inside, which I could swap out. And in every other context that model works fine — you swap the container, the contents are unaffected. You can move your photos between services forever. You can move your documents between word processors. The work is the work; the container is incidental.
It does not work like that for the editor.
Because the editor is not a container for the writing. After a few years it becomes the apparatus of the writing. It is the set of habits, the muscle memory, the unconscious adjustments. You stop thinking about the writing because the thinking has moved into your hands and the hands have learned a specific set of moves.
And when you take that away, what you discover is not that the new tool is worse. It is that you are worse at it, for a while, and the writing gets measurably harder.
This is the thing that separates a migration from an inconvenience. A migration is hard because of data, compatibility, unknown unknowns. An inconvenience is not hard at all. An inconvenience is just annoying, and you do it anyway.
But this was neither. This was hard, in a way that had nothing to do with engineering, and I could not explain it to anyone who had not felt it, because the correct description of the problem is “typing felt slightly worse” and that does not sound like a reason to give up on something you have already built two features into.
And here is where I am going to say something that I suspect will annoy people, and I do not care, because I think it is true and I think a lot of people are quietly living with it.
A developer who cannot write in their editor does not produce less. They produce it more slowly, and then they stop enjoying it, and eventually they stop.
Nobody says this, because we are all supposed to believe that a good tool enables everyone equally. And it does — for everyone except the person who has spent a decade inside one particular tool and made it theirs.
The measure of a content platform is not what it can be made to do. Every platform can be made to do anything, eventually, with enough effort. The measure is how it feels at the point where you are typing, on a Tuesday, when the idea is good and you are ready and you want to get it down before it goes.
That is the whole test. And it is not a soft test. It is the only test that determines whether you publish at all.
Which brings me to the uncomfortable conclusion, which is that I could not be persuaded to leave my editor for any platform that did not have one I liked better. Not a better CMS. Not a better content model. Not a better business.
A better editor.
And the entire industry — correctly, in my view — is moving away from the thing I wanted. Which means my preference is, structurally, a minority one.
I do not think that makes me wrong. I think it makes me a person who has made a bet on familiarity, and familiarity is a real asset, and it is a real cost too, and most people do not get to find out which one they have until they try to move.
Anyway. The rant is over. I went back to WordPress. You may guess how that felt, given everything I have just said, and you would be right.
Chapter 7: Back to WordPress, and the Reason
I built both plugins in a few days.
And I want to be careful with that sentence, because “built” is doing a lot of work in it and I do not want to be misleading anybody — including myself, six months from now when I remember this differently.
I did not port anything. There was nothing on this site to port, because this site had never had either feature. Both plugins were written from nothing, on this platform, because I came back from Payload and found that WordPress could not do either of these things and that I was not prepared to go without them.
So “I rebuilt both plugins” is wrong, and the accurate version is: I built them twice, in two different places, and the second time I had the first time as a reference. The reference was memory. That detail matters and I will come back to it.
I want to be precise about that, because “a few days” sounds like it means something easy, and it was easy for a reason worth naming: I had already built these features once, on a different platform, and a working implementation is an enormous advantage over designing one from scratch.
The series plugin is about eleven hundred lines. The audio plugin is about sixteen hundred. Together they are under three thousand lines of PHP, and because I had written almost all of it before, I was not designing so much as translating — asking what in the new system does the job this piece of code used to do.
And WordPress, whatever else you want to say about it, is very good at that particular thing. It has a documented extension surface, it has hooks for everything, and it has twenty years of people having solved your exact problem in slightly different ways. Half of the translation was finding the hook I wanted. The other half was rediscovering things I already knew about my own code, which is the part nobody warns you about when they tell you a rewrite is faster than a fresh build.
I am going to say the number I came out with, because it is more interesting than a vague “basically the same”.
Ninety-eight percent.
Which means two percent is missing, and the useful question is what the two percent is. Because “98% the same” is not a measurement, it is a mood. And the mood can hide a lot.
In my case the missing two percent is: polish I have not bothered rebuilding, and one feature I had on the other platform that I have simply not made.
The polish is small and specific. The other platform had an admin experience that was, in ways that only show up after a year of using it, considerably nicer. Some of the interface work I did not carry across because it was decoration rather than function, and some of it I genuinely have not got round to. The audio plugin has an interface that works and that I do not entirely love, and it is on the list.
The feature I have not rebuilt is the matrix view — the thing that renders how a series branches, where the argument forks and where the pieces relate to each other rather than just running in order. It worked on the other platform. It is the best idea in the series plugin and it is not on this one yet.
So: ninety-eight percent, honestly accounted for, is one genuinely good idea I have not finished porting and a set of small things I have deprioritised. That is a real gap and it is a much better gap than “the plugins broke”.
And I want to be careful about that number, because it is the sort of figure that gets quoted back at you later as though it were measured. It was not measured. It is a judgement, made by me, about work I did myself, and the honest thing to say is that I have not sat down and enumerated every difference.
What I have done is look at it properly, once, and see whether anything was missing that I actually relied on. And almost nothing was. That is the real finding, and it is a better one than the number.
Which is worth pausing on, because it is counter-intuitive. I rebuilt two plugins for a different platform and expected the experience to be similar to porting something between two versions of the same thing. It was not. It was closer to moving house — everything arrives, and then you spend a month finding out which cupboard you put the kettle in.
Some of that is inherent. Some of it was avoidable, and the avoidable part is worth naming, because it is the thing I would do differently.
The avoidable part is that I rebuilt rather than ported. I had working code that expressed exactly what I wanted and I did not carry it across; I re-derived it from memory. That was faster for the first few hundred lines and then slower for the rest, because I kept finding places where I had improved on my original and did not remember the improvement.
If I was doing it again — and I am not sure I would do it differently, which is its own kind of answer — I would open the old version alongside the new one and work through it line by line, treating it as a reference rather than a memory. It is very tempting when you know what you want to build to just build it again, and the temptation is a trap, because the version in your head is the version from three months ago and you have since worked out three better ways to do something.
What I want to be clear about is that none of this was hard. Not the rebuild, not the translation, not the debugging. Three days of actual work over what was probably two weeks of evenings. Compare that to the six months of searching, and it says something quite uncomfortable about where the difficulty in this whole project actually lived.
I am not going to labour the point. It is not a moral. It is just strange to notice that the thing everybody warns you about — the code, the compatibility, the migration — was the easy part.
I want to say one more thing about this because it is the part nobody discusses in migration stories.
Nothing about the work migrated, either. Every line of the series plugin and every line of the audio plugin was written against WordPress from a blank file, to reproduce behaviour I had previously implemented somewhere else. There was no automated path, no converter, and no shared codebase — only me, and what I remembered.
Which means the “data” in a migration — the posts, the images, the comments, the settings — was the easy part. We have tools for moving content between platforms. That has been solved for a decade and it works.
What is not solved, and cannot be, is the things you built yourself. Because those exist as code written against one platform’s assumptions, and they have no automatic path to another. There is no converter. There is only somebody who knows how both systems work, sitting there re-deriving what they built and why.
If you are migrating and you did not write custom code, this is not your problem. If you did, you should know that this is the part of the migration that will actually take your time, and nobody will hand you a tool for it.
Chapter 8: VoicePress
The other thing that came out of the last six months is not a plugin. It is an app.
I built a macOS application called VoicePress. It is about nine and a half thousand lines of Swift. It does one thing, which is turn the posts on this site into audio, in my voice, and put the results back into the blog.
Before I describe it, I should be clear about something: this is a tool for me. It is designed around my workflow, my posts, my site, and my way of working. It is not a product. Nobody else uses it. When I say the workflow is specific to me, I mean that literally — there are decisions baked in that only make sense if you are me, and I would have to change them before it made sense for anybody else.
I will get to what would have to change. First, how it works.
The thing that made this possible is that the text-to-speech models have got good enough to clone a specific voice from a short sample, and that running them has become something you can do on your own machine rather than sending text to somebody else’s API.
That combination — a usable model, and local execution — is recent. It was not true when I started looking at this and it is one of the reasons I stopped and thought about it rather than just continuing with the API-based approach I had been using.
So the shape of the app is: it knows about the posts, it knows how to turn the markdown into something worth speaking, it queues up work, it runs the generation, and then it puts the result where the blog can find it.
That last part is the part I am most pleased with, and it is the part that ties back to the plugins from earlier in this post.
The audio plugin expects audio to be attached to a post, with chapters. VoicePress is what produces that audio. Between them there is a small piece of tooling that registers the finished file against the right post on the right site — because this site runs on multisite, meaning there is more than one blog in the same WordPress install, so “the blog” is not a single address, it is a choice you have to make deliberately.
So the flow is: a post is written in markdown in an obsidian vault. The app picks it up, renders it, speaks it, converts it, registers it against the correct site on the multisite, and the plugin then presents it on the post itself with a chapter-aware player.
App writes the audio. Plugin presents the audio. Neither one knows much about the other.
And the reason that is worth building that way, rather than as one big system, is that the two halves have genuinely different failure modes and different lifecycles. The app is something I will change constantly — I am always tweaking how it reads. The plugin is something that should just work and keep working, because if it breaks, every post on the site that has audio becomes a broken page.
There is a batch queue in the middle, which exists because generating an hour of audio takes a while and I am not going to sit and watch it. I give it a list of posts, tell it when to start, and it gets on with it. There is a scheduling system because sometimes I want to run it overnight while the machine is doing nothing else. And there is a diagnostics view — a doctor — that tells me what is misconfigured when something is wrong, because I built that view after spending an evening not knowing why.
And the voice is mine.
I want to be honest about how strange that is. It is my voice saying words I wrote, and it does not feel uncanny, which I expected it to. It feels like a recording of me reading my own post, which is exactly what it is. The difference is that I did not have to be there for it.
The obvious question, which I get asked: no, it is not me reading. It is a model trained on a sample of my voice, running locally, and yes I know how that sounds. The people who do voices for a living have opinions about this and I have thought about them more than I expected to, and I will say only this: I use it for my own words, on my own site, and if it is a bit strange that a person has created a synthetic version of themselves to read their own writing, then that is a fairly accurate description of what this whole project is.
What it does not do is replace the writing. That is worth being clear about, because I think there is a version of this technology that people find genuinely upsetting and I understand why, and I do not think this is it.
Every word in every audio file on this site was written by me, typed by me, edited by me. The model does not write. It does not summarise. It does not improve. It reads aloud, in a voice I gave it, and if a sentence is unclear on the audio it was unclear on the page, because the model is reading the words I actually wrote rather than making them better.
The audio has a bug that is worth mentioning, actually. It does not read markdown. It reads prose. So anything I write that is a diagram or a code block or a list of options, the audio either skips or has to have something done to it first, so it doesn’t sound like an asthmatic having a coughing fit. There is a rendering step that turns the markdown into something speakable, and that step involves me making judgement calls about what a listener needs and what they do not.
Which means the audio version of this post is not this post read out. It is this post, processed by a set of rules I wrote, decided by me, sometimes getting it wrong.
Which is, if you think about it for more than a second, exactly what every editor has ever been.
Chapter 9: Where It Stands
The site is where it was, essentially, and better than it was, and I am aware that those two things being simultaneously true is slightly suspicious.
The two custom features I could not find anywhere else are here and working. The content model I wanted is here in spirit if not in substance. The audio pipeline runs, and the app that drives it is mine, and it does a job I would otherwise be paying somebody else to do.
And I am still on WordPress, for three reasons, in the order they actually mattered.
The first is features. When I sat down and listed what my site does that is not standard, the plugins I had written for it were on that list, and they were the whole list. Everything else a platform does, this one does. The features were never the problem with the alternatives — they were the thing I had to carry across myself, and I carried them across, and that is the entire argument.
The second is that it is still self-hosted. That never changed, and it was never really in question, but it is worth saying plainly in a post about leaving and coming back: whatever else happened, the site is on my own infrastructure, in my own Docker setup, on my own domain, with my own backups. That was the condition of the whole exercise and it survived it.
The third one I will say plainly rather than dress up, because it is not a sophisticated argument and pretending otherwise would be silly.
I am not paying Automattic money. I do not want to be. And I would rather use a platform where I have a genuine, ongoing, very robust objection than one where I have decided to stop objecting and start paying.
Which I appreciate is not a technical argument, and is not even really a philosophical one, because the moment I have a credit card on file it becomes a financial one. I am aware. I have thought about it.
I would also like to say, without any irony at all, that WordPress is good. It genuinely is. It gave me everything I needed for a decade and it is still doing so. The objection is not to the software. It is to a direction of travel, and I would rather not fund a direction of travel.
And yes, I am aware that this makes me exactly the kind of person who gets de-platformed eventually by certain CEOs who can’t take no for answer. If that happens it will be a genuinely funny outcome for a post about building plugins to avoid depending on anything.
So where does that leave the plugins?
They are not going public, at least not now, and I want to be careful about why, because there are two very different reasons and only one of them is work.
The honest reason is work. A plugin that works on my multisite is a tool. A plugin that works on anyone’s site is a release, and that is a bigger job than it sounds. To release them properly they would need the author metadata cleaned up, the one place where I have a domain hardcoded turned into a setting, some tidying, and — most substantively — documentation of which WordPress APIs they depend on and why. Somebody would have to read that and decide whether it works on their setup, and right now they would have to read the code instead, which is not the same thing.
The unhelpful reason, which is the one that actually stops me, is that I built them for this site. That is all they are for. Making them public would mean supporting them, and I do not have room for that on top of everything else, and I would rather they just quietly keep working here.
But since I have been thinking about what release would actually involve, it is worth being concrete rather than hand-wavy, because “it needs tidying” is the phrase people use when they have not looked.
The short list is: take my name out of the plugin headers and replace it with something that makes sense for whoever ends up maintaining it. Turn the one hardcoded domain into a setting — which is genuinely one line of code, and the existence of exactly one hardcoded domain is the slightly embarrassing proof that these were never written to be portable. Add a readme. And then the real work, which is documentation.
Because the thing that actually stops another developer is not the licensing or the tidiness. It is: how do I know whether this will work on my site?
These plugins are not just calling documented public functions. They are reaching into the admin, hooking the Customizer, handling AJAX, creating attachments, and — the one that would catch everybody out — reading files off the server’s filesystem to probe them. On a standard WordPress install, all of that is fine. On somebody else’s multisite, with their own plugins installed and their own security restrictions, any of it might not be. And there is currently no way for them to find that out except by installing it and seeing what breaks.
So the documentation would not be a readme. It would be an honest statement of what the plugins touch and why, which is the thing I would want to read if I were installing someone else’s site features, and the thing I have not written.
Which is probably the honest summary of a lot of personal software: it works, in one place, for one person, and the difference between that and a release is mostly documentation and the willingness to be responsible for somebody else’s evening when it does not work.
And VoicePress is even less of a candidate. It is written around my workflow, my posts and my site. Making it user-friendly is not a matter of tidying — it would mean redesigning the workflow itself, which is a real project rather than a polish job. If there were interest I would be delighted to do it. There is not currently interest, so it stays as the thing it is.
Which leaves me where I have been for the last six months, which is: working on the site, not changing the site.
That is a slightly unusual sentence to end a blog post on, and I notice it is a bit of an anticlimax after all of that. But it is the truth, and the truth is that I set out to find somewhere better and concluded that where I was was fine, having learned a great deal about why.
Which is not the most dramatic possible ending. It is the most accurate one.
There will be more Bridlington Mesh or Carnaby Media Hub type posts. I have a few idea that I’m working to nail down before I get started though.
But this post was about the thing that lets me build all of that, and it was about the fact that for a few months I genuinely thought I might have to move it somewhere else, and that the answer turned out to be about nine and a half thousand lines of Swift and a decision to stay where I was.
Which is not a very dramatic answer either. I have been thinking about it for a while now. It is, however, mine.
