Blog
Engineering
Content operations

Draft, publish, and the two-row trick

Achar stores a draft as a second document whose id begins `drafts.` rather than a flag on one row. It looks like duplication, and it is the reason a headline can be rewritten for a week without touching what the site serves.

Tomás Ferreira

Most content systems store a draft flag. Achar stores two rows: the document, and the same document whose id begins `drafts.`. It looks like duplication, and it is the reason an editor can rewrite a headline for a week while the site keeps serving the old one.

One flag is one write away from a mistake

With a flag, the draft and the published version are the same row. Every save is an implicit decision about whether the public is looking at this yet, and every integration that reads content has to know the flag exists and honour it. Publishing becomes a boolean flip that either happened or did not, with nothing left to compare against afterwards.

With two rows, the draft is a document nobody but its editor can see, and publishing is a copy. That makes it atomic, revertible the way any other write is, and invisible to everything that reads published content — including the CDN, which never sees a half-edited page.

A draft is not a state of a document. It is a second document that is about to become the first.

What falls out of the arrangement

  • A read sees the published row or the draft, never a mixture of the two.
  • Discarding a draft is deleting one row, and cannot reach what is live.
  • Publishing is one transaction with a revision on each side of it.
  • A preview is the draft read by the same query that serves the site, not a second renderer.

The cost is one extra row per edited document and one rule everybody has to learn: an id beginning `drafts.` is not content, it is a proposal. Written on the first screen of the studio, that rule is cheaper than any amount of documentation about a flag.

*[_id == $draftId][0]{ title, "editedAt": _updatedAt }

Reading the draft explicitly is what a preview does. Everything else reads the published id and is correct by construction rather than by remembering.

Tomás Ferreira

Principal engineer

Tomás wrote the query engine behind Achar and has strong opinions about what a query language should refuse to do. Previously he built a document store that outlived three rewrites of its frontend, which is where most of those opinions came from.

Related posts

Content operations
Engineering

Your content model is the product decision

Every content system fails the same way: a field that grew a second meaning, and a template that reads it both ways. The model is the one thing you will still be living with in three years.

Maya Chen
Engineering

GROQ in twenty minutes

There is no join, because there is no second table. A query says which documents and then what to return, and everything that looks like syntax is one of those two halves made more specific.

Tomás Ferreira
Content operations

A content lake is not a CMS

A CMS owns your pages. A lake owns your content and has no opinion about your pages, which sounds like a small distinction until the second frontend arrives.

Maya Chen