From Manila to Conversant
Twenty-five years of scripted publishing, and what the CMS world forgot along the way.
Between 1997 and 2004, a handful of people working in a scripting environment built publishing systems that solved problems the content-management industry then spent two decades rediscovering. Manila, Radio UserLand and Conversant were not commercially dominant and never became a standard. They were, in several specific ways, better than what replaced them.
This is the lineage, honestly told: what each system was, what it got right, why it lost anyway, and which of its ideas you should steal for whatever you are building now.
Before: the website framework
Frontier's web framework arrived before any of the products did, and it set the pattern everything after it inherited. A site was a table in the object database. Pages were nodes. A template was a page with placeholders. Rendering walked the tree and wrote files — or answered a request directly from the built-in server.
Two consequences followed, and both are still worth arguing for. First, there was no separate build system, because the database was the source of truth and rendering was just a verb. Second, a page could be a script. If a page needed logic, you wrote the logic where the page lived, and nobody had to invent a plugin architecture to allow it.
The idea most worth stealing
Content, template and logic in one addressable structure, with rendering as an ordinary function of it. Static-site generators arrived at a similar place twenty years later, by a longer road, and mostly without the live-editing half.
Manila, 1999: editing in the browser, properly
Manila put the editorial interface in the browser at a time when that was genuinely novel. An editor logged in, saw the page they were reading, clicked edit, changed it and saved. No separate admin application, no export step, no staging copy to reconcile.
What it got right, in order of how long the rest of the industry took to catch up:
- Editing in context. You edited the page you were looking at. Inline editing was reinvented as a premium feature around 2015.
- Roles that matched a newsroom. Member, contributor, editor, managing editor. Not a permissions matrix — job titles.
- Discussion attached to content. The message board and the pages were the same system, so a thread about an article lived with the article.
- Structured templating with real fallbacks. A page could override a template fragment without copying the whole template.
- Shortcuts. A named glossary of links and snippets, so a URL that changed was changed once. Wikis got this; most CMSes never did.
What it got wrong: hosting was effectively single-tenant on a runtime that did not scale horizontally; the templating language was idiosyncratic enough to be a hiring problem; and the whole thing depended on a commercial runtime with one vendor. None of those are design failures exactly. All of them were fatal.
Radio UserLand, 2001: the desktop half
Radio moved the runtime onto the writer's own machine. You wrote locally, in an application that was also a web server, a feed aggregator and a full scripting environment; your posts were upstreamed to a hosting service that served them publicly.
It is easy to miss how many current ideas were already sitting in that description:
- Local-first authoring. Your content was yours, on your disk, working offline.
- Publish as synchronisation. Upstreaming was a diff-and-push, not an upload — the same shape as every static deploy pipeline today.
- Reading and writing in one tool. The aggregator sat beside the editor, because the loop of read, react, publish is one activity and not two.
- An API from day one. XML-RPC meant other tools could drive it, which is how the MetaWeblog API came to exist at all.
- Categories as feeds. Every category produced its own RSS, which made syndication a structural property rather than a feature.
Its failure was mostly timing and economics. It asked a writer to install and maintain a runtime at exactly the moment hosted services made that unnecessary, and the free hosting that made it attractive had an expiry date built into the business model.
Conversant: the community-shaped one
Conversant — the platform this site originally ran on — came at the problem from the discussion end rather than the publishing end. It was groupware first: messages, threads, membership, notification and search, with weblog and page rendering layered on top of the same store.
That ordering produced a system with properties the page-first CMSes never had:
- One identity across everything. The person commenting, the person on the mailing list and the person editing a page were one record with one set of preferences.
- Email and web as equal interfaces. You could participate entirely by email, or entirely through the browser, and the archive was identical.
- Search that understood structure. Attribute indexing (the reason AttSearchEngine existed) meant you could search by author, board and date and not merely by words.
- Templates that non-programmers survived. Enough power to build a real site, little enough rope to avoid logic in templates.
- Multi-tenancy that worked. Hundreds of independent communities on one runtime, which Manila never managed.
Why the lineage lost
It is tempting to tell this as a story about better technology losing to worse marketing. That is not quite honest. There were four real reasons.
- Single-vendor runtime. When your entire ecosystem depends on one company's commercial product, the ecosystem's ceiling is that company's roadmap. PHP had no such ceiling, and that mattered more than any individual feature.
- The hiring problem. A university could find twenty people who knew PHP and none who knew UserTalk. That calculation decided more procurements than any feature comparison.
- Horizontal scale. The object database's great strength — one coherent persistent tree — is precisely what makes running twelve identical front-ends hard. The industry moved to stateless application servers and the model did not follow.
- Open source expectations. By 2005 the default assumption for infrastructure was that you could read the source and fork it. Systems that could not meet that assumption stopped being shortlisted regardless of quality.
What this means for a system you own today
If you are running one of these, none of those four reasons is an argument for replacing it tomorrow. They are arguments for making sure you are not trapped: that content can be extracted, that the runbook exists, that more than one person can operate it. Those three conditions convert an urgent migration into a scheduled one, and the difference in cost is roughly an order of magnitude.
The extraction path, concretely
The practical question is usually not which system was better but how to get twenty years of content out of one of them without losing the URLs, the authorship or the threads. The order that has worked for us, every time:
- Inventory before deciding anything. Count the pages, the messages, the members and the binaries. Establish what has actually been read in the last three years.
- Use the API if one answers. An endpoint that responds to
metaWeblog.getRecentPostsis worth a week of database archaeology. - Extract to open formats. Markdown or HTML for bodies, JSON for metadata, files for binaries, with encodings normalised to UTF-8 on the way out.
- Preserve every URL. Produce a redirect map from the old address space to the new one and test it as a list, not by sampling.
- Keep the threads together. Discussion is the part most migrations drop, and it is frequently the most valuable content in the system.
- Run in parallel. Serve both for a period and diff the output page by page. A weekend cutover with no comparison is how content disappears quietly.
on exportSite (adrSite, folder) {
local (adrPage, count = 0, manifest = {});
for adrPage in site.pageList (adrSite) {
local (meta = {
"title": adrPage^.title,
"slug": site.urlFor (adrPage),
"created": adrPage^.created,
"modified": adrPage^.modified,
"author": adrPage^.author,
"categories": adrPage^.categories ?? {}});
«Body and metadata as separate files: diffable, and never coupled.
file.writeWholeFile (folder + meta.slug + ".html", adrPage^.body);
file.writeWholeFile (folder + meta.slug + ".json", json.stringify (meta));
manifest = manifest + {meta};
count++;
if count mod 50 == 0 {
msg ("Exported " + count + " pages…");
thread.sleepFor (0.01)}};
file.writeWholeFile (folder + "manifest.json", json.stringify (manifest));
return (count)}
What to steal
Five ideas from this lineage that would improve most systems being built right now:
- Edit in context. If an editor has to imagine how their change will look, your authoring experience is worse than Manila's was in 1999.
- One identity everywhere. Comments, newsletter and authorship as one record with one preference set.
- Shortcuts. A named glossary for links and snippets. Change a URL once.
- Local-first with sync-shaped publishing. Offline authoring and a diff-and-push deploy. Half the industry has now rebuilt this.
- An API from the first day. Not as an integration feature — as the thing that lets your content leave, which is what makes it safe to commit to your system at all.
The systems in this article all eventually lost their market. The ideas did not lose; they were relearned, slowly and expensively, by people who had never heard of them. Which is, more or less, the reason this publication still exists.
Take this away
- Content, template and logic in one addressable tree — rendering is just a verb
- Manila had in-context editing and newsroom roles in 1999
- Radio was local-first authoring with sync-shaped publishing
- The lineage lost on vendor risk, hiring and horizontal scale, not on design
- Extract via the API first; preserve URLs and keep threads together