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.
- Zero conversion on read or write. The fastest possible render path is
innerHTMLof stored content. - Every downstream tool speaks it — email templating, PDF renderers, static site generators, scrapers.
- It is the lowest-friction path out. Whatever you migrate to will accept HTML.
What it costs you.
- You must sanitise on render, forever.Storing markup means storing whatever an attacker could put in it. This is not optional and it is not the editor’s job alone — a client-side filter can be skipped by a client that simply does not run it.
- Querying is painful.“Find documents whose second heading mentions pricing” is a parse, not a query.
LIKE '%pricing%'also matches an attribute value. - Diffs are noisy. Two visually identical documents differ if one has
<b>and the other<strong>, or attributes in another order. A naive text diff shows a change where the reader sees none.
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.
- Real queries. In Postgres,
jsonbwith a GIN index makes “documents containing a table” or “all H2 text” an actual query rather than a parse. - Meaningful diffs and comments. Nodes can carry stable ids, so a comment anchors to a node rather than a character offset that shifts when someone edits a paragraph above it. If you want track changes or threaded comments, this matters enormously.
- A validating schema. The document model can make invalid states unrepresentable, so
<iframe>in your content is not something to filter — it is something that cannot exist. - Render anywhere. Native mobile, PDF, email can each walk the tree. No headless browser.
What it costs you.
- Conversion on every read and write, and a renderer you now own on every platform.
- The schema is a migration surface. Adding a node type means old documents lack it and new renderers must tolerate that. This is normal database evolution, but it is work that HTML does not have.
- It is the most editor-specific option. A ProseMirror document is not a Lexical document. Mitigate by keeping the mapping to HTML lossless in both directions, so you always have an exit.
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 cached column is derived — never written by anything except the renderer.
- Store the renderer version alongside it, so you can detect and rebuild stale rows.
- Rebuilding from source must be a routine operation you actually run, not a theoretical one.
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
- 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.
- Do you need to query inside documents? If yes, JSON — or HTML plus a derived, versioned search column.
- Must you render outside a browser? If yes, JSON.
- Is it prose written by technical users, wanting git-style diffs? Markdown.
- 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.