Architecture

HTML, JSON or Markdown: how should you store rich text?

The storage format you pick on day one decides what you can do in year three — search, diffing, migrating editors, rendering on a phone. Here is what each format actually costs, and the one mistake that is genuinely hard to undo.

11 min read

You are about to add a rich text field to a table. The choice you make in that migration decides, three years from now, whether you can search inside documents, show a meaningful diff, render on a phone without a browser engine, or move to a different editor at all.

There are three real options — HTML, a structured JSON document, and Markdown — and the right answer depends on questions most teams have not asked yet.

HTML: the default, and usually correct

The editor produces HTML, you store the string, you render it. Nothing to translate, and everything already understands it.

What it buys you.

What it costs you.

Choose HTML when the content is written by humans and read by humans, and you are not building features that reason about document structure. That is most applications, and picking it is not settling.

Structured JSON: when you need to reason about content

Instead of markup, store a document tree — the model ProseMirror, Lexical and Tiptap use natively, and which many HTML-first editors can also emit:

{
  "type": "doc",
  "content": [
    { "type": "heading", "attrs": { "level": 2 },
      "content": [{ "type": "text", "text": "Pricing" }] },
    { "type": "paragraph",
      "content": [
        { "type": "text", "text": "Plans start at " },
        { "type": "text", "marks": [{ "type": "bold" }], "text": "$129" }
      ] }
  ]
}

What it buys you.

What it costs you.

Choose JSON when you need comments, suggestions, collaborative editing, structural search, or rendering outside a browser. If you are building a document product rather than a text field, this is usually right.

Markdown: right for a narrower case than people think

Markdown is compact, diffs beautifully in git, is readable as plain text, and is nearly immune to markup injection because there is barely any markup.

The problem is that it cannot represent most of what a rich text editor produces. There is no standard Markdown for a merged table cell, a footnote with a back-reference, a tracked change with an author, coloured text, a page break, or an image with a caption and alignment. Every implementation invents extensions, and they do not agree.

In practice teams escape by embedding raw HTML in the Markdown — at which point you have both formats, both problems, and a sanitisation story that is now harder than either alone.

Choose Markdown when the content genuinely is prose — documentation, README files, notes, blog posts by technical authors — and you want git-friendly diffs. Do not choose it because it feels cleaner, if your users will eventually paste a table from Excel.

The tempting mistake: store both

“Store JSON as the source of truth and cache rendered HTML alongside it” is reasonable and common. It is also the most frequent source of subtle corruption, for one reason: the two get out of sync.

A background job updates one and not the other. A migration rewrites HTML directly because it was quicker. Two years later nobody can say which column is authoritative, and neither can be trusted.

If you cache a rendering, make the relationship enforceable:

The same logic applies to a plain-text column for search. Derive it, version it, and never let a human edit it.

The one decision that is genuinely hard to reverse

Format changes are migrations, and migrations are survivable. What is not easily survivable is losing information you never stored.

If you store Markdown and users spend two years pasting tables with merged cells, that structure was destroyed on the way in. No later migration recovers it, because it is not in your database. Same for comments anchored to character offsets when the text has since changed, or an image whose alt text your pipeline dropped.

So the practical rule is: store the richest representation you can, and derive the poorer ones. Going from HTML or JSON down to Markdown or plain text is always possible. Going the other way is not.

A short decision procedure

  1. Will you need comments, suggestions or collaborative editing? If yes, use structured JSON. Anchoring on character offsets in an HTML string is a bug factory.
  2. Do you need to query inside documents? If yes, JSON — or HTML plus a derived, versioned search column.
  3. Must you render outside a browser? If yes, JSON.
  4. Is it prose written by technical users, wanting git-style diffs? Markdown.
  5. Otherwise, HTML. It is the pragmatic default and the easiest to leave.

And whichever you choose, confirm your editor can produce all three. Being able to emit HTML, a structured document and Markdown from the same content is what keeps the decision reversible — which matters far more than getting it perfect the first time.


RichTextEditor is a perpetual-licence JavaScript editor — one purchase, self-hosted, no metered editor loads. Download the evaluation or see how it compares.