Comparison

MDX vs Rich Text: Which Should a CMS Store?

MDX vs rich text in a CMS: MDX stores one portable string, rich text stores a JSON tree. Which format to store, when each wins, and the security catch.

DT
Draftbase Team · September 6, 2026 · 8 min read
Split diagram: one MDX document on the left, the same content broken into a nested rich text node tree on the right

Store MDX when your content ships React components, and store rich text when non-technical editors own the page. That's the whole decision. MDX keeps prose in one portable string that can render a chart or a callout inline. Rich text keeps prose in a structured JSON tree that no editor can break and no renderer has to compile. Draftbase stores MDX in its richText fields. Everything structured goes in typed fields beside the prose. That's a third answer, and it's worth knowing. Pick wrong and you pay for it during your next migration, not today.

What's the difference between MDX and rich text in a CMS?

The split isn't about formatting features. Both handle headings, bold, links, lists, and images fine. The difference is what sits in the database column.

What an MDX field actually stores

One string. It looks like Markdown, plus JSX tags for anything Markdown can't express.

## Pricing

Our Startup plan is $49/mo.

<PricingTable plan="startup" seats={5} />

That string is code. MDX compiles to a JavaScript component, so <PricingTable> isn't decoration. It's a real component call with typed props. The CMS stores text. Your app decides what the tags mean.

What a rich text field actually stores

A tree. Contentful and Sanity both went this way, for the same reason.

{
  "_type": "block",
  "style": "h2",
  "children": [{ "_type": "span", "text": "Pricing" }]
}

Portable Text is the open spec version of this idea. Every block names its own type. Nothing gets guessed from markup. An editor can't write bad output, because the editor writes the tree itself.

Why the storage format decides your migration cost

Here's the part most comparisons skip. Some formats go back. Some don't.

MDX out of a CMS is a plain string. Move to another platform and it's still a plain string. Your <PricingTable> tags break until you re-register that component, but the prose survives intact.

A rich text tree is portable in theory and annoying in practice. Contentful's tree and Sanity's tree are different shapes. Converting between them means writing a serializer per node type, plus one per embedded reference. It's a day of work, not a git mv.

MDX stringRich text JSON
Storageone text fieldnested node tree
Renderer neededMDX compilerper-node serializer
Editor safetyeditor can write broken syntaxstructurally impossible
Embedded componentsJSX tags, typed propstyped node + custom renderer
Cross-CMS movecopy the stringwrite a converter

When should you use an MDX CMS?

When developers write the content, or when the content needs live components inside the prose.

Docs sites are the obvious case. Changelogs, API references, engineering blogs, anything where the author already lives in a code editor. If a post needs an interactive demo halfway down, MDX is the only format where that's a one-line change.

The second case is component reuse. A <Callout type="warning"> written once renders identically across 400 pages. Change the component, every page updates. No find-and-replace across a content tree.

The MDX CMS guide walks the full storage and render path.

When should you use a rich text CMS?

When the people writing don't write code. This is where rich text wins outright, and pretending otherwise is how a CMS gets bought and then abandoned.

A marketing team wants a toolbar. They want bold on Cmd+B, a link dialog, and a preview that matches production. They do not want to learn why <Callout> broke because someone typed <Callout >. Rich text editors hand them a WYSIWYG surface. Bad states just aren't reachable.

Multi-channel output

Rich text also wins when the same content renders to more than a website. A JSON tree turns into HTML, a PDF, a native mobile view, or plain text for email. MDX expects a JSX runtime on the other end. Sanity makes this case directly. It's a fair one.

Real-time collaboration

Two people editing one doc at once needs a structured tree underneath. You can't merge live edits to a Markdown blob without conflicts. Need Google-Docs-style co-editing? Rich text is the answer.

MDX vs traditional CMS storage: where the boundary sits

A traditional CMS like WordPress stores rendered HTML, or something close to it. That's a third option. It's also the least portable of the three. HTML bakes in the look, so a restyle means rewriting content.

MDX and rich text both avoid that. They split on who holds the render logic. With rich text, the CMS names the node types. Your app maps them. With MDX, your app names the components and the CMS just holds text. That flip is the real tradeoff.

The underused angle: MDX from a CMS is remote code

Every "MDX vs rich text" post treats this as a formatting choice. It's a security boundary.

Rich text JSON is inert data. Walk the tree, render nodes, done. MDX is compiled. If your CMS lets any editor write MDX, and your server compiles it, you're executing content-authored code on your server.

It has already happened. HashiCorp disclosed CVE-2026-0969 on February 11, 2026. The serialize function in next-mdx-remote versions 4.3.0 through 5.0.0 allowed arbitrary code execution during React server-side rendering of untrusted MDX. Version 6.0.0 fixed it with a breaking change: blockJS now defaults to true, disabling JavaScript expressions. Set blockJS: false and a second option, blockDangerousJS, still blocks eval, Function, process, and require by default.

So the rule is short. Trusted authors, MDX is fine. Untrusted or public submissions, block expressions or store rich text instead.

The component allowlist is the real control

Your component map is the gate. An MDX tag only renders if your app passes that component in. Everything else falls through to plain text or an error boundary. Draftbase's renderer works this way. @draftbase/renderer compiles the string with @mdx-js/mdx, then renders only the components you hand it. Registered MDX components carry typed props. A bad prop is a build error, not a 3am page.

What breaks in production with each format?

Different failure modes, and both are worth planning for.

MDX breaks at compile time. Someone types <Callout> and forgets the closing tag. The page throws instead of rendering. That's loud, which is good, but it happens after publish unless you compile on save. Wrap the render in an error boundary so one bad post doesn't take down a route.

The second MDX failure is quieter. A developer renames a component prop. Forty old posts still pass the old prop. Nothing errors, the prop is just ignored, and forty pages silently lose a feature. Typed props catch this at build time. Untyped ones don't.

Rich text fails differently. A node type gets added in the CMS, your renderer has no case for it, and that block renders as nothing. No error, no log line, just a gap in the page. The fix is a default case in the serializer that renders unknown nodes as plain text.

The other rich text failure is size. Trees get big. A long article with many embedded references can push a single API response past what your cache tier likes. Check your response sizes before you find out from a p95 chart.

Neither list should scare you off. Both are known problems with known fixes. Pick the format on the editor question, then handle the failure mode that comes with it.

When is MDX the wrong choice?

Four cases, and none of them are edge cases.

Your editors are non-technical and there's no developer on call. Your content ships to mobile and email as well as web. You need real-time co-editing. Or you accept content from users you don't control.

Any one of those and rich text is the better buy. Two of them and MDX will actively slow the team down.

Storing prose as MDX and structure as fields

There's a third answer both camps skip: keep structure out of the prose entirely.

If a post has an author, a publish date, tags, and a hero image, none of those belong inside the content blob. They're fields. Query them, sort by them, validate them. The prose field then only holds prose, and the format question gets much smaller.

Draftbase works this way. Templates are schemas of typed fields (text, number, boolean, date, media, reference, JSON), and richText holds a plain MDX string beside them. You get MDX components with typed props where prose needs them, and typed fields everywhere else. See schema-driven content modeling for how the field types map to a real model.

That split is also why the format choice stops being permanent. Fields move cleanly between platforms. One prose string moves cleanly too. A deeply nested tree with embedded references is the part that doesn't.

Which one should you actually ship?

If your team is React developers writing docs and technical posts, store MDX. You'll want the components within a month. Bolting them onto a JSON tree later is worse.

If a marketing team owns the pages, store rich text. Don't let a developer talk you out of it on portability grounds. A format your editors avoid has a portability cost of 100%.

Draftbase is built for the first case. MDX in richText, typed fields for structure, REST and GraphQL delivery. Revisions with rollback when a component change breaks a page. The Hobby plan is free. The Startup plan is $49/mo. Both sit on the headless CMS pricing page, not behind a call.

OptionVerdictProsCons
MDX stringStore MDX when developers write the content and it needs React components inline.
  • One plain string moves between platforms without a converter
  • JSX components with typed props render inside the prose
  • A component change updates every page that uses it
  • Diffs cleanly in revisions, because it stays text
  • Editors can write syntax that fails to compile
  • Compiling untrusted MDX is remote code execution (CVE-2026-0969)
  • Assumes a JSX runtime, so PDF and email output need extra work
  • No real-time co-editing without a structured tree underneath
Rich text JSONStore rich text when non-technical editors own the page and need a WYSIWYG toolbar.
  • A WYSIWYG toolbar makes broken syntax unreachable
  • Inert data, so nothing gets compiled or executed
  • One tree serializes to HTML, PDF, mobile, or plain text
  • Supports real-time collaborative editing
  • Every vendor's tree is a different shape, so moving means writing a converter
  • Needs a serializer per node type and per embedded reference
  • Unknown node types render as nothing, silently
  • Large documents inflate API response size

Ship content that's built to be found

Draftbase generates schema, structured data, and a fast MDX editor for every post.

Frequently asked questions

Should a CMS store MDX or rich text?

Store MDX when developers write the content and it needs React components inline. Store rich text when non-technical editors own the page and need a WYSIWYG toolbar.

How is MDX different from regular rich text in a CMS?

MDX is one plain string that compiles to a JavaScript component. Rich text is a nested JSON tree of typed nodes. MDX moves between platforms as text. A tree needs a converter per node type.

Is rich text better for non-technical editors?

Yes, and that is rich text's strongest case. A WYSIWYG toolbar makes broken syntax unreachable, so a marketing team never sees a compile error.

Is storing MDX in a CMS a security risk?

Only if you compile untrusted MDX. CVE-2026-0969 hit next-mdx-remote 4.3.0 through 5.0.0 in February 2026. Version 6.0.0 blocks JavaScript expressions by default.

Can one CMS store both MDX and structured content?

Yes. Draftbase templates hold typed fields for structure and a richText field that stores plain MDX beside them, so prose and structure stay separate.

Related reading

Go deeper on MDX Editor