Reference

How it compares

Sanity, Payload, Sveltia and BooshCMS: where the content ends up, and how many times you describe it.

Written 2026-08-27, out of a long argument about whether a CMS can edit “any page”, and extended the same day once Sveltia CMS turned up as the closest thing to a direct competitor.

This is not a hatchet job on the other three. All of them are good, two of them are free, and one of them does something BooshCMS cannot do at all. The comparison is about what each asks of the project, where the client’s words end up, and how many times you have to describe your content — not which is better.


The thing nobody does

The intuition is reasonable: point a CMS at a website, have it find the editable bits, and let the client change them.

No CMS works that way. Not one of the four.

Payload looks closest, because you can add it to an existing project. But it does not open your Astro pages. It is a Node application with its own database and its own React admin UI; you define collections, migrate content into the database, and rewrite your templates to fetch from its REST or GraphQL API. Its Live Preview then renders your front end in an iframe and posts field changes to it. Documents, not source files.

Sanity is explicit about it. Content is a document in the Content Lake, the page is code you write that queries it, and the Studio edits the document.

Sveltia manages entries defined in a configuration file. Its own docs say it “doesn’t freely edit arbitrary source code” — a static page has to be declared as a file collection before anyone can touch it.

BooshCMS does the same thing, and refuses page source it cannot model.

The reason is the same in all four cases, and it is not a limitation of effort. A page’s source is open-ended — wrapper elements, loops, conditionals, imports, styles, a comment saying do not remove. To edit it safely you would have to model everything a developer might write, and that set has no end. So every CMS solves the same problem the same way: the page becomes a document, and code renders it.

What differs is where the document lives, and how many times you have to describe its shape.


Where the content ends up

Sanity Payload Sveltia BooshCMS
Architecture hosted SaaS Node app + your database browser SPA, no server desktop app, no server
Where content lives Sanity’s Content Lake Postgres or MongoDB you run files in your git repo files in your git repo
What the site depends on @sanity/client, project ID, dataset Payload and its database, running nothing nothing
Infrastructure to keep alive Sanity’s service a database and a server an OAuth app none
Cost free tier, then seats and usage free self-hosted; paid cloud free, open source one-off, no per-seat
How the site reads content GROQ / API REST / GraphQL / SDK getCollection() getCollection()
Schema written once, in code once, in code twice — config.yml and your Astro schema once — your Astro schema
Schema derived from your code no no no yes
Frameworks any any any Astro only
Editable pages documents you designed documents you designed entries you declared documents you designed
Arbitrary hand-written pages no no no no
Editing on the rendered page yes yes no not yet
Real-time collaboration yes yes no — not officially supported yet no
Runs on a phone yes yes yes not yet
If the tool goes away content is in Sanity content is in your database the content is already the site the content is already the site

Three rows carry most of the difference.

Where content lives. Sanity and Payload put the client’s words in a database and leave code in the repository that fetches them. Sveltia and BooshCMS leave the words in the repository. There is no export, because there is nothing to export from.

What the site depends on. A Sveltia or Boosh site has no runtime dependency on the CMS. Delete it and the site still builds with astro build.

How many times you describe your content. This is the one that separates the two git-based tools, and it is the whole of the difference between them.


Sveltia — the closest thing to a competitor

Sveltia CMS is the only one of the three that shares BooshCMS’s central idea: the content is files in the client’s own repository, and the site reads it with getCollection() like any other Astro project. Everything below is a difference within that shared position, not an argument against it.

What it does

A browser single-page app, served from a CDN. No build step and no server: you add two files to your site’s static folder —

public/admin/index.html     loads the CMS
public/admin/config.yml     describes every collection and every field

— register an OAuth application with GitHub or GitLab, and the client visits /admin. Saving makes a commit through the provider’s API. The host rebuilds. GitHub, GitLab, Gitea and Forgejo are supported.

It is free, open source, framework-agnostic, and runs on anything with a browser, including a phone. Those are real advantages and BooshCMS has none of them today.

What it costs: the schema is written twice

From Sveltia’s own documentation: collections are hand-written in the configuration file, and “there’s no automatic discovery of collections or fields.”

So an Astro project using content collections ends up describing its content twice:

// src/content.config.ts — what Astro validates and what your pages read
const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: ({ image }) => z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    cover: image().optional(),
  }),
})
# public/admin/config.yml — what the client's form is built from
collections:
  - name: blog
    folder: src/content/blog
    fields:
      - { name: title, widget: string }
      - { name: pubDate, widget: datetime }
      - { name: cover, widget: image, required: false }

Two descriptions of one thing, kept in step by hand. They can disagree, and only one of them fails loudly. Add summary to the Zod schema and Astro knows; the client’s form does not, and nobody finds out until someone asks why the field is missing. Rename a field in config.yml and the CMS writes frontmatter Astro then rejects at build time.

BooshCMS reads content.config.ts directly. The Zod schema is the form — there is nothing to keep in step, because there is only one description.

There is a third-party integration, astro-loader-sveltia-cms, that inverts the direction: it generates the Zod schema from the widget definitions, so config.yml becomes the single source. That solves the drift and is worth knowing about. It also makes the CMS’s configuration file the authority over your Astro project’s own schema, which is the opposite of what this app is for.

Two smaller things

  • An OAuth app to register and keep working. Not a server, but not nothing.
  • Multi-user is not officially supported yet, per their docs. BooshCMS is also one-editor-at-a-time, so this is a draw rather than a point.

Widgets, and why BooshCMS has fewer

A widget is the editing control a field gets — the difference between a one-line box, a rich text editor, a date picker, a colour swatch, a dropdown of entries from another collection. Every CMS here has the concept; what differs is who chooses.

In Sveltia you choose it, per field, in config.yml:

- { name: colour, widget: color }
- { name: author, widget: relation, collection: team, value_field: name }

Its list is long — string, text, number, boolean, markdown, rich text, code, image, file, datetime, colour, UUID, map, relation, list, object, key-value, compute, hidden.

In BooshCMS you do not choose. The control is derived from the Zod type, and from the field’s name where the type alone is not enough:

In your schema What the client gets
z.string() named title one line
z.string() named description a textarea
z.string() named body the rich text editor
z.string() named bookingUrl a web address, validated
z.enum([…]) radios up to three options, a dropdown beyond
reference('team') a dropdown of that collection’s entries
image() the picture picker
z.array(z.object({…})) a repeater of grouped fields
z.array(z.discriminatedUnion('type', […])) the page builder

The trade is real in both directions. Choosing gives you controls inference cannot reach — a colour picker, a map, a code editor, a computed field — and it is never wrong about your intent. Inferring gives you a form with no configuration at all, and one that cannot drift from the schema, at the cost of a shorter list and the occasional wrong guess. BooshCMS’s guesses are deliberately conservative: linkText stays a plain string rather than becoming a URL field, because guessing wrong costs the client an awkward control.

Where a construct cannot be turned into a control at all, the field is shown read-only with a plain-language warning rather than the collection being dropped.


What the others do better

Worth saying plainly, because the trade is real.

  • Editing on the rendered page. Payload and Sanity both let an editor click the heading where it appears and change it there. BooshCMS shows a live preview beside a side panel; you edit the field and the preview follows about a second later. That is a genuine difference in feel, and the thing most worth stealing.
  • Instant preview. Payload’s postMessage updates need no save and no rebuild.
  • Several people at once. Sanity and Payload have real-time collaboration. BooshCMS has one client and git underneath, with file-level conflicts and field-level merging for pages. Adequate for a client and their developer; not a newsroom. Sveltia is in the same position.
  • Any framework. All three others are framework-agnostic. BooshCMS reads Astro.
  • Anywhere, on anything. Sveltia and the two hosted tools run in a browser. BooshCMS is a desktop app until the web build ships.
  • More controls. Sveltia’s widget list is longer than what BooshCMS infers.
  • Free. Sveltia costs nothing and is open source.

Honouring the Astro build

This matters more than it sounds, and it is the reason the trade is worth making.

Every part of the convention is Astro’s own documented primitive:

  • defineCollection with a Zod schema — core Astro, the documented way to model structured content
  • the glob() and file() loaders — core
  • getEntry and getCollection — core
  • mapping a block’s type to a component and spreading its props — plain Astro, no plugin, no runtime

A developer opening that project sees ordinary Astro. They do not need to know this app exists to read it, extend it, or hand it to somebody else. There is no bespoke directory, no generated file to avoid touching, no build step that belongs to us, and no configuration file describing content that the schema already describes.

The only Boosh-specific things in a project are @client in a comment and a studio.config.json at the root — and both are inert to Astro. A comment is a comment; a JSON file nothing imports is a JSON file. Remove them and the build output is byte-identical.

Compare what the alternatives ask. Sanity wants a client library, a project ID and a Studio. Payload wants a database and a server. Sveltia wants an admin page, an OAuth app, and a second description of your content.

The honest caveat

Astro documents content collections for content. Using them for page composition — a page as an entry with an ordered array of blocks — is a pattern rather than a documented recommendation.

It is built entirely from documented pieces, and it is what people already do on Astro sites backed by any headless CMS, so it is idiomatic in the way that matters: nothing surprising, nothing bespoke, nothing that breaks when Astro moves. But it is a convention this app asks for, and it should be described that way rather than as something Astro tells you to do.

It is also a build convention, and the same size of commitment as choosing Sanity in the first place. A Sanity site already accepts that a page is a document with a page-builder array, that components are registered, and that a renderer maps between them. This asks exactly the same — and hands the content back as files in the client’s own repository.

And a second one

BooshCMS is new, and nobody but its author has run it. Sveltia is mature, widely used and free; Sanity and Payload are businesses with support behind them. That is the real difference in risk, it is not small, and it is not a risk the other three carry.

What it is not is a difference in the editing mechanism, and it is worth separating the two because they get conflated.

Collections — the surface all four of these tools actually share — are edited by patching what is on disk: changed values written over the file, fields not in the schema left untouched, YAML entries nobody opened returned byte for byte. One defect has been found there and fixed.

The .astro page-source path is different. It discards the file and rebuilds it from a model, and five defects were found in it on 2026-08-27 — four by deliberately attacking it, one on a real site. That path has since gained a verification layer that proves per file that a save would keep everything, and editablePages: false turns it off for a whole project.

None of that is a comparison with the other three, because none of them attempts it. Sveltia’s file collections point at data files with declared fields, not at component source; its own docs say it “doesn’t freely edit arbitrary source code”. A project composing its pages as collection entries — the recommended convention — never uses the page-source path at all, and can switch it off in one line.


The position

Conventional Astro that a client can edit, with the content staying in their repo — and the schema written once.

Sveltia gets you the first two, free and in a browser, at the cost of describing your content twice. Sanity and Payload give you a better editing experience and real infrastructure, at the cost of a database and a dependency in the build.

Safe, contained, owned. No database, no service, no dependency, no second description, and nothing trapped anywhere else.

See Building a site for BooshCMS for the contract a project has to meet, and decisions.md for what was settled along the way.