
Total Views
11
Read Time
28 min read
Updated On
12.10.2026
Introduction
HTML vs JSON vs Markdown: How to Store Rich Text Editor Content in 2026
How to store rich text editor content in 2026: HTML vs JSON vs Markdown trade-offs, real DB schema patterns, migration paths, and which format fits.
TL;DR
Store rich text editor content in 2026: HTML for display apps and SEO content. JSON for collaboration and structured editing. Markdown for docs and portability. Each has specific DB trade-offs.

Content
HTML vs JSON vs Markdown: How to Store Rich Text Editor Content in 2026
How you store rich text editor content in 2026 shapes everything downstream — database schema, API design, search indexing, migration paths, collaboration support, and what happens the day you switch editors. Pick the wrong format and you’ll fight it for years. Pick the right one and your editor becomes invisible infrastructure.
This guide compares the three real options for storing rich text editor content: HTML, JSON (editor-specific state like ProseMirror or Lexical), and Markdown. Real trade-offs. Database schema patterns. Migration costs. Which format fits which app in 2026.
The short answer. HTML works best for apps where content is primarily displayed to end users (blogs, articles, CMS, marketing sites, product descriptions). JSON works best for apps with collaboration, structured editing, or complex custom blocks (Notion-style tools, document platforms, collaborative editors). Markdown works best for developer-facing apps where content portability matters (documentation, technical blogs, GitHub-style tools). Each has specific database costs and query trade-offs.
🎥 See a modern editor in action: What is Eddyter? Why Developers Are Switching in 2026
Why the Content Storage Format Decision Matters
The storage format decision looks like a small technical choice. It’s actually one of the highest-leverage architectural decisions in your app. Three specific downstream impacts make it critical.
1. Format Locks You Into an Editor
Each format aligns naturally with specific editors. HTML works with any editor. ProseMirror JSON locks you to TipTap-family editors. Lexical state locks you to Lexical-family editors (including Eddyter). Markdown works with any editor but loses rich formatting.
If you store ProseMirror JSON for 2 years and then want to switch to Lexical, you need content migration. If you store HTML, the migration is lighter but you lose structural fidelity. Pick based on your long-term editor commitment.
2. Format Shapes Collaboration Support
Real-time collaboration (Y.js, Automerge, custom CRDTs) requires structured JSON state — not HTML. If your app will need multi-user editing within 2 years, storing HTML and converting at collab time is painful. Store JSON from day one if collaboration is on the roadmap.
3. Format Determines Search and Query Costs
HTML is searchable with full-text indexing (PostgreSQL, Elasticsearch) out of the box. JSON requires path-based queries (jsonb in Postgres, flattening in SQL). Markdown works like HTML for search. The format you pick shapes database CPU costs across your entire app lifecycle.
The 3 Rich Text Editor Content Storage Formats Explained
Let’s break down each format with real examples.
Format 1: HTML (The Universal Default)
HTML is the “web-native” format. Browsers render it natively. Humans can read it. Every tool supports it.
What it looks like:
html
What editors output HTML by default:
- Eddyter (clean semantic HTML)
- CKEditor 5 (HTML with CKEditor-specific classes)
- TinyMCE (HTML with inline styles)
- Froala (HTML with Froala classes)
- Quill (HTML via
quill.root.innerHTML) - Novel.sh (clean HTML output)
- BlockNote (clean HTML output)
Database storage:
sql
HTML stores cleanly in a TEXT column. Full-text search works via tsvector indexing. Storage overhead is minimal.
Strengths:
- ✅ Universal — every editor and browser supports HTML
- ✅ Searchable with standard full-text search
- ✅ Human-readable for debugging and manual inspection
- ✅ Rendering is literally free — browser does it natively
- ✅ CDN-cacheable as rendered output
- ✅ SEO-friendly — search engines index HTML directly
- ✅ Easy migration between editors (most editors accept HTML)
Weaknesses:
- ❌ Structural ambiguity — same visual output can have multiple HTML representations
- ❌ Hard to query specific document structures (all images in nested lists, for example)
- ❌ Collaboration requires conversion to CRDT format at edit time
- ❌ Editor-specific classes bloat storage (CKEditor’s
ck-content, TinyMCE’s inline styles) - ❌ Not ideal for complex custom blocks beyond standard HTML
Format 2: JSON (The Structured Format)
JSON stores the editor’s internal document model. Each editor framework has its own JSON schema — ProseMirror JSON, Lexical state, Slate value, Draft.js ContentState, Editor.js blocks.
What ProseMirror JSON looks like (TipTap, ProseKit):
json
What Lexical state looks like (Lexical, Eddyter):
json
What Editor.js blocks look like:
json
Database storage:
sql
JSONB storage with GIN indexing lets you query specific document structures. Full-text search requires extracting text via jsonb_path_query.
Strengths:
- ✅ Structured — every element has explicit type and properties
- ✅ Perfect fidelity for complex custom blocks (embeds, mentions, callouts)
- ✅ Collaboration-ready — Y.js and other CRDTs work directly on JSON state
- ✅ Queryable with JSONB operators (find all documents with code blocks, for example)
- ✅ Version-safe — editor can validate schema on load
- ✅ Supports structured edits (insert at specific node path)
Weaknesses:
- ❌ Editor-specific — ProseMirror JSON doesn’t work in Lexical and vice versa
- ❌ Rendering requires converting to HTML at display time (CPU cost)
- ❌ Full-text search needs flattening to text first
- ❌ Human-unreadable for debugging without a parser
- ❌ Storage is 1.5-2x larger than equivalent HTML
- ❌ Harder migration between editor families
Format 3: Markdown (The Portable Format)
Markdown is a lightweight markup format where plain text becomes formatted content via conventions (**bold**, # heading, [link](url)).
What it looks like:
markdown
What editors output Markdown:
- Dedicated markdown editors (@uiw/react-md-editor, MDXEditor, Milkdown)
- TipTap with StarterKit + markdown serializer extension
- Most editors can convert HTML to Markdown via libraries (
turndown,html-to-md)
Database storage:
sql
Markdown stores like HTML — simple TEXT column with full-text search indexing.
Strengths:
- ✅ Portable — reads anywhere, works with any tool
- ✅ Human-readable even without rendering
- ✅ Git-friendly for version control
- ✅ Compact storage (smallest of the three formats)
- ✅ Developer-familiar syntax
- ✅ Easy to migrate between editors
- ✅ Full-text search works like HTML
Weaknesses:
- ❌ Loses rich formatting (no custom blocks, no mentions, limited embeds)
- ❌ Rendering requires markdown parser (CPU cost per view)
- ❌ Collaboration is awkward — no stable CRDT format for Markdown
- ❌ Not ideal for non-developer users (writing experience is weirder than WYSIWYG)
- ❌ Dialect fragmentation — CommonMark vs GFM vs custom extensions
Head-to-Head Comparison Table
Dimension | HTML | JSON (editor state) | Markdown |
|---|---|---|---|
Universal editor support | ✅ Any editor | ❌ Editor-specific | ⚠️ Most editors |
Database storage | TEXT column | JSONB column | TEXT column |
Storage size | Baseline | 1.5-2x larger | 0.5-0.7x smaller |
Full-text search | ✅ Easy | ⚠️ Requires extraction | ✅ Easy |
Structured queries | ❌ Hard | ✅ Easy (JSONB) | ❌ Hard |
Collaboration (Y.js, CRDT) | ❌ Convert at edit | ✅ Native | ❌ Convert at edit |
Rendering cost | ✅ Free (browser) | 💰 Convert to HTML | 💰 Parse markdown |
Custom blocks fidelity | ⚠️ Via data-attributes | ✅ Perfect | ❌ Limited |
Human readability | ⚠️ Readable but noisy | ❌ Needs parser | ✅ Clear |
SEO indexing | ✅ Native | ❌ Via rendered HTML | ✅ Via rendered HTML |
Migration between editors | ✅ Easy | ❌ Hard | ✅ Easy |
Best for | Blogs, CMS, display apps | Collab tools, Notion-style | Docs, developer tools |
Which Format to Pick: Decision Framework
Match the format to your app’s primary use case.
Pick HTML If...
- ✅ Your app’s primary job is displaying content (blog, CMS, news, product descriptions)
- ✅ SEO matters (HTML indexes natively, JSON doesn’t)
- ✅ You might switch editors in the future (HTML has the easiest migration)
- ✅ You want simple full-text search without JSON extraction
- ✅ Collaboration is NOT on the near-term roadmap
- ✅ Your users don’t need complex custom blocks beyond standard HTML
Example apps: Blog platforms, news CMS, marketing sites, product description editors, email newsletter builders, help documentation (reader-facing).
Pick JSON If...
- ✅ You’re building a collaboration-first app (Notion clone, Google Docs clone)
- ✅ You need complex custom blocks (mentions, embeds, custom callouts, interactive widgets)
- ✅ You need structured queries on content (find all documents with code blocks, for example)
- ✅ You’re committed to a specific editor framework (TipTap, Lexical, or Eddyter)
- ✅ Version history with granular diffs matters (JSON diffs are cleaner than HTML diffs)
Example apps: Notion-style tools, Google Docs alternatives, collaborative document platforms, structured note-taking apps, EMR clinical note editors, legal document editors.
Pick Markdown If...
- ✅ Your users are developers (they expect markdown)
- ✅ Content portability matters (users want to export clean markdown)
- ✅ Content is version-controlled in Git
- ✅ You want the smallest storage footprint
- ✅ Rich formatting (complex blocks, mentions) is NOT required
- ✅ Content is primarily text with simple formatting
Example apps: Developer documentation (GitHub-style READMEs), technical blogs, developer help docs, internal wikis for engineering teams, code review platforms.
Hybrid: Store Multiple Formats
Many 2026 apps store multiple formats for different purposes:
sql
Trade-off: Storage is 2-3x higher but query flexibility is maximum. Common pattern for apps where editing and display workflows are decoupled.
Database Schema Patterns for Each Format
Beyond the format, your database schema choices determine query performance and storage efficiency.
Pattern 1: HTML with Full-Text Search
Best for: Blog platforms, CMS, article editors.
sql
Query example:
sql
Pattern 2: JSONB with Structural Queries
Best for: Collaboration apps, Notion-style tools, structured editing.
sql
Query examples:
sql
Pattern 3: Hybrid Storage for Max Flexibility
Best for: Apps with both editing and display workflows that have different performance needs.
sql
Keep JSON as the source of truth. Pre-render HTML on save. Export Markdown on demand.
Storage overhead: 2-3x compared to single format. For most apps this is acceptable given modern storage costs ($0.023/GB/month on AWS S3).
Migration Path: Converting Between Formats
If you’re already storing one format and need to switch, here are the real conversion paths.
HTML → JSON (ProseMirror or Lexical)
Use the editor’s native parser:
typescript
Fidelity: 85-95% for standard content. Custom blocks may need custom parser rules.
Typical migration cost: 2-4 days for 100K documents.
JSON → HTML
Use the editor’s native serializer:
typescript
Fidelity: 100% for structured content, lossy for interactive blocks.
Typical migration cost: 1-2 days for 100K documents.
HTML → Markdown
Use the turndown library:
typescript
Fidelity: 70-85% for standard content. Custom blocks (mentions, embeds) typically lost.
Typical migration cost: 1-2 days for 100K documents.
Markdown → HTML
Use the marked or remark libraries:
typescript
Fidelity: 100% for standard markdown. Custom extensions need custom parsers.
Typical migration cost: 1 day for 100K documents.
JSON (ProseMirror) → JSON (Lexical) or Vice Versa
Hardest migration path. Different document models mean no direct conversion.
Recommended approach: Convert through HTML as intermediate format.
typescript
Fidelity: 70-85% due to double conversion.
Typical migration cost: 1-2 weeks for 100K documents with custom block mapping.
Real-World Storage Format Choices by App Type
Here’s what successful 2026 apps actually use.
Content Platforms (Medium, Substack, Ghost)
Format: HTML + draft-level JSON
Why: Display performance (HTML renders free), SEO (HTML indexes natively), draft recovery (JSON preserves exact editor state)
Notion-Style Apps (Notion, Craft, Coda)
Format: Custom JSON (block-based)
Why: Collaboration (CRDT compatibility), structured queries (find all pages with database blocks), custom blocks (databases, embeds, synced blocks)
Developer Documentation (GitBook, Mintlify, Nextra)
Format: Markdown
Why: Developer-familiar, Git-friendly, portable, version-controlled, works with any renderer
Email Builders (Beehiiv, Buttondown, Kit)
Format: HTML (with MJML intermediate for complex emails)
Why: Direct SMTP compatibility, email client rendering, client-specific CSS handling
Legal Document Editors (Ironclad, LinkSquares)
Format: JSON with versioned snapshots
Why: Track changes fidelity, version diff accuracy, structured clause queries
Clinical EMR Editors (Epic, Cerner integrations)
Format: JSON (often custom structured + HTML display)
Why: PHI isolation, structured field extraction, audit trail requirements
Marketing CMS (Webflow CMS, Builder.io)
Format: JSON (component-based)
Why: Visual builder compatibility, A/B testing variants, structured content types
Internal Wikis (Confluence, Slab, Notion for internal use)
Format: JSON or HTML depending on vintage
Why: Collaboration (JSON) or display performance (HTML)
How Eddyter Handles Content Storage
Eddyter is built on Meta’s Lexical framework. By default, Eddyter outputs clean semantic HTML via the onChange callback, which is what most apps need.
jsx
For apps needing JSON state (collaboration, custom blocks, structured queries), Eddyter exposes the underlying Lexical state. For apps needing Markdown, convert the HTML output via turndown after save.
Get your API key at eddyter.com/user/license-key. Full setup in the Eddyter docs.
🎥 See it in action: Integrate Eddyter in 30 Minutes with Cursor, Claude, Lovable
Common Content Storage Mistakes to Avoid
Teams making their first content storage decisions consistently hit these specific issues.
Mistake 1: Storing HTML Then Adding Collaboration Later
If collaboration is on your 12-month roadmap, start with JSON state. Retrofitting Y.js on HTML content costs 3-5x the engineering effort of starting with JSON. The migration is painful because every historical document needs conversion.
Fix: Audit your roadmap. If collaboration is likely, start with JSON from day one.
Mistake 2: Not Sanitizing HTML Before Storage
Storing unsanitized HTML from an editor opens XSS risk. Even trusted editors can produce HTML that triggers XSS when combined with user-generated attributes.
Fix: Always run HTML through DOMPurify before storage, with explicit allow-lists for tags and attributes.
typescript
Mistake 3: JSON Without Schema Versioning
Editor frameworks update JSON schemas across versions. ProseMirror v2 vs v3 have breaking changes. Lexical v0.x vs v1.x have different node types.
Fix: Store the editor framework version alongside the content. Validate schema on load and run migrations when versions change.
sql
Mistake 4: No Pre-Rendered HTML Cache for Display
If you store JSON and render HTML at display time, you pay the conversion cost on every request. For high-traffic apps this becomes expensive.
Fix: Pre-render HTML on save and cache it in a separate column. Invalidate on edit.
Mistake 5: Full HTML Content in Hot Database Rows
Rich text content can be large (10-500 KB per document). Storing it in your primary user profile table or feed table bloats row size and hurts query performance.
Fix: Store content in a separate table with foreign key to the parent entity. Fetch content only when needed.
sql
Mistake 6: No Version History
Content changes get lost without version history. Users accidentally delete content. Compliance audits require historical access.
Fix: Store version snapshots on major edits.
sql
Performance Benchmarks: HTML vs JSON vs Markdown
Real performance differences across the three formats for a typical SaaS workload.
Storage Size (10,000 Documents, Average 5 KB Each)
Format | Total Storage | Per Document Avg |
|---|---|---|
Markdown | 32 MB | ~3.2 KB |
HTML | 50 MB | ~5.0 KB |
JSON (ProseMirror) | 85 MB | ~8.5 KB |
JSON (Lexical) | 75 MB | ~7.5 KB |
Full-Text Search Query Time (PostgreSQL, 100K documents)
Format | Avg Query Time | p99 Query Time |
|---|---|---|
HTML (tsvector) | 18ms | 45ms |
Markdown (tsvector) | 15ms | 38ms |
JSON (text extraction + tsvector) | 42ms | 110ms |
Render Time (Server-Side, 1000 Documents)
Format | Avg Render Time | Notes |
|---|---|---|
HTML | 0ms | No conversion needed |
Markdown | 2.1ms | marked library |
JSON → HTML (ProseMirror) | 4.3ms | generateHTML |
JSON → HTML (Lexical) | 3.8ms | $generateHtmlFromNodes |
Collaboration Compatibility
Format | Y.js Integration | Automerge | Custom CRDT |
|---|---|---|---|
HTML | Convert at edit | Convert at edit | Convert at edit |
JSON (ProseMirror) | Native (y-prosemirror) | Via adapter | Via adapter |
JSON (Lexical) | Native (y-lexical) | Via adapter | Via adapter |
Markdown | Convert at edit | Convert at edit | Convert at edit |
The pattern: JSON wins for collaboration, HTML wins for display performance, Markdown wins for storage size and developer tooling.
FAQs
1. What’s the best format to store rich text editor content in 2026?
The best format depends on your app’s primary use case. HTML works best for content-display apps (blogs, CMS, news sites, marketing content) because it renders free in browsers, indexes natively for SEO, and migrates easily between editors. JSON (editor-specific state like ProseMirror or Lexical) works best for collaboration apps (Notion-style tools, document platforms) because it supports Y.js and CRDTs natively. Markdown works best for developer-facing apps (documentation, technical blogs) because it’s portable, Git-friendly, and developer-familiar. For most SaaS apps where the editor is a feature rather than the entire product, HTML is the pragmatic default — it’s universal, searchable, and keeps your options open for editor migration.
2. Should I store HTML or JSON for a Notion-style collaborative editor?
Store JSON (specifically the editor’s state format — ProseMirror JSON for TipTap-based editors, Lexical state for Lexical-based editors). Collaboration tools require structured document state for CRDT integration (Y.js, Automerge). HTML can’t represent the editor’s internal state fidelity, so collaborative edits produced on HTML get lost or conflict. Store JSON as the source of truth, pre-render HTML for display performance, and keep the JSON schema version alongside the content for safe migrations. Expect 1.5-2x storage overhead vs HTML, which is acceptable given modern storage costs.
3. Can I switch from HTML storage to JSON storage later?
Yes, but it’s a non-trivial migration. HTML to JSON conversion using editor-native parsers (generateJSON for ProseMirror, $generateNodesFromDOM for Lexical) achieves 85-95% fidelity for standard content. Custom blocks typically need custom parser rules. For a typical SaaS with 100,000 documents, HTML to JSON migration costs 2-4 days of engineering plus QA. The real cost comes later: if you added Y.js collaboration between the HTML storage and JSON migration, you’ll have mixed collaboration state that needs reconciliation. Plan the migration before adding collaboration, not after.
4. Is Markdown enough for a developer-facing SaaS?
Markdown works well for developer-facing SaaS when content is primarily text with simple formatting. Documentation platforms (GitBook, Mintlify, Nextra), developer blogs, technical help docs, and engineering wikis all run cleanly on Markdown. Where Markdown falls short is when content needs complex custom blocks (interactive widgets, mentions, embeds), when non-developer users need to edit (WYSIWYG expectations), or when real-time collaboration matters. For these cases, store JSON and convert to Markdown on export. Most developer-facing SaaS can start with Markdown and migrate to JSON only if feature complexity grows.
5. How much storage overhead does JSON have vs HTML?
JSON storage is typically 1.5-2x larger than equivalent HTML. For 10,000 documents averaging 5 KB each, HTML takes ~50 MB total while ProseMirror JSON takes ~85 MB and Lexical state takes ~75 MB. For most SaaS apps this overhead is acceptable — modern storage costs ($0.023/GB/month on AWS S3) make the difference trivial. The real cost of JSON is query complexity: full-text search requires extracting text from JSON via jsonb_path_query operations, which run 2-3x slower than direct tsvector on HTML/Markdown. If you need both JSON state and fast search, pre-render HTML on save and index that separately.
6. Should I sanitize HTML before storing it?
Always sanitize HTML before storing it, even from trusted editors. Editor libraries sometimes produce HTML that triggers XSS when combined with user-generated attributes or when rendered in unexpected contexts. Use DOMPurify with explicit allow-lists for tags and attributes. Don’t rely on the editor to sanitize — the editor’s output format is designed for editing fidelity, not storage security. For SaaS apps handling user-generated content, sanitize at both the editor output boundary (before storage) and the display boundary (before rendering). This defense-in-depth approach catches both malicious input and sanitizer bypasses.
7. What’s the right storage pattern for apps with both editing and display workflows?
Use a hybrid storage pattern: JSON as the source of truth for editing, pre-rendered HTML cached for display, optional Markdown for export. Store all three in the same documents table with triggers that regenerate HTML on JSON updates. This pattern uses 2-3x storage vs single-format but gives you editing fidelity (JSON), display performance (HTML with zero conversion cost), and portability (Markdown for exports and API responses). The storage overhead is almost always acceptable given modern storage costs. The engineering overhead is handling schema versioning and ensuring HTML cache stays in sync with JSON updates.
8. Can I store rich text content as plain text and format at display time?
No. Plain text loses all formatting information, including bold, italics, headings, lists, links, and images. If users expect any formatting beyond basic paragraphs, plain text storage breaks the user experience. If your app genuinely only needs plain text, use a plain <textarea> instead of a rich text editor — simpler, smaller bundle, no editor framework to maintain. If users need formatting but you want simple storage, Markdown is the right choice — minimal syntax, portable, easy to parse at display time.
The Bottom Line on Rich Text Content Storage
Content storage format is a decision you make once and live with for years. Pick based on your app’s primary workflow, not based on what feels most modern.
HTML wins for display-first apps where content is read more than it’s edited. JSON wins for collaboration apps where structured state and CRDT integration matter. Markdown wins for developer-facing apps where portability and Git integration matter.
For most SaaS teams building apps where the editor is a feature (not the product), HTML is the pragmatic default. It’s universal, searchable, cacheable, and keeps your migration options open. Add JSON storage later only when specific needs (collaboration, structured queries, custom blocks) justify the complexity.
Whatever you pick, sanitize before storage, version your schema, and separate hot query rows from heavy content rows. These three patterns matter more than the format choice itself.
Ready to Build Your Rich Text Editor?
Stop debating storage formats. Deploy Eddyter into your React or Next.js app today — clean semantic HTML output by default, structured Lexical state available when you need it, 10-minute setup, multi-model AI (GPT-5, Claude Sonnet 5, Haiku 4.5, Gemini 3) built in, and 7-framework support at $12-$59/mo flat.
👉 Try Eddyter free at eddyter.com
📚 Read the docs
💰 See pricing
🎥 Watch the intro video | Watch the 30-min setup guide

Written by
Shreya Taneja
Project Manager

