Output Modes
Editu can export editor content in three formats: Markdown, JSON, and HTML. Each format serves a different purpose — choose the one that fits your use case.
Quick Comparison
| Format | Method | Best For |
|---|---|---|
| Markdown | editor.getMarkdown() | Storage, Git, static sites, CMS |
| JSON | editor.getJSON() | Database storage, API transfer, re-import |
| HTML | editor.getHTML() | Email, rendering outside editor, SSR |
Markdown Output
The default and most portable format. Standard blocks (headings, lists, code, tables) serialize as standard Markdown. Layout blocks serialize as raw HTML with data-editu-* attributes since they have no Markdown equivalent.
const markdown = editor.getMarkdown();
// # Hello World
//
// <div data-editu-group data-padding="md" data-background="muted">
// Content inside the group...
// </div>TIP
Use the flavor option to control Markdown dialect: 'gfm' (default), 'commonmark', 'obsidian', or 'docusaurus'. See Getting Started - Markdown Flavor.
JSON Output
The lossless format — every node, attribute, and mark is preserved exactly. Ideal for storing in a database and re-importing later with editor.commands.setContent(json).
const json = editor.getJSON();
// {
// type: 'doc',
// content: [
// { type: 'heading', attrs: { level: 1 }, content: [...] },
// { type: 'group', attrs: { padding: 'md', background: 'muted', ... }, content: [...] },
// ]
// }INFO
JSON is the only format that fully round-trips layout block attributes. Markdown and HTML both preserve the data, but JSON maps 1:1 to the internal ProseMirror document structure.
HTML Output
Generates a complete HTML string of the document. Layout blocks render as styled <div> and <a> elements with Tailwind utility classes resolved from semantic tokens.
const html = editor.getHTML();
// <h1>Hello World</h1>
// <div class="p-4 bg-neutral-100 rounded-lg" data-editu-group>
// <p>Content inside the group...</p>
// </div>This is the format to use when you need to render editor content outside the editor — in emails, preview pages, or server-side rendered views.
Tailwind Token Mapping
Layout blocks store semantic tokens as attributes (e.g., padding: "md", background: "muted"). When the editor renders these blocks — both in the editor view and in getHTML() output — it resolves tokens to Tailwind CSS utility classes via the token registry.
Token Registries
| Registry | Token Type | Example |
|---|---|---|
EDITU_SPACING_TOKENS | Padding | "md" → "p-4" |
EDITU_BACKGROUND_TOKENS | Background | "muted" → "bg-neutral-100" |
EDITU_RADIUS_TOKENS | Border radius | "lg" → "rounded-lg" |
EDITU_HEIGHT_TOKENS | Spacer height | "lg" → "h-16" |
EDITU_DIVIDER_STYLE_TOKENS | Divider style | "dashed" → "border-dashed" |
EDITU_DIVIDER_THICKNESS_TOKENS | Divider thickness | "thick" → "border-t-4" |
EDITU_BUTTON_VARIANT_TOKENS | Button variant | "primary" → "bg-blue-600 text-white hover:bg-blue-700" |
Resolver API
Each registry exports a resolver function you can use when rendering layout blocks outside the editor:
import {
resolveEdituSpacing,
resolveEdituBackground,
resolveEdituRadius,
resolveEdituSpacerHeight,
resolveEdituButtonVariant,
} from '@editu/core/tokens';
resolveEdituSpacing('md'); // "p-4"
resolveEdituBackground('accent'); // "bg-blue-50"
resolveEdituRadius('lg'); // "rounded-lg"
resolveEdituSpacerHeight('xl'); // "h-24"
resolveEdituButtonVariant('ghost'); // "bg-transparent text-blue-600 hover:bg-blue-50"TIP
If your project uses a custom Tailwind theme, you can override the token-to-class mappings by providing your own registry. See Layout Blocks - Token Registry for details.
Working Example: HTML Export with Layout Blocks
This example creates an editor with layout blocks, inserts content programmatically, and exports the final HTML:
import { Editu } from '@editu/react';
import {
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituButtonReact,
} from '@editu/react';
import type { Editor } from '@tiptap/core';
function ExportDemo() {
const handleCreate = ({ editor }: { editor: Editor }) => {
// Insert layout blocks programmatically
editor.commands.setContent({
type: 'doc',
content: [
{
type: 'group',
attrs: { padding: 'lg', background: 'muted', borderRadius: 'lg' },
content: [
{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Welcome' }] },
{ type: 'paragraph', content: [{ type: 'text', text: 'This is a styled section.' }] },
],
},
{ type: 'spacer', attrs: { height: 'md' } },
{
type: 'button',
attrs: { label: 'Get Started', href: '/docs', variant: 'primary', target: '_self' },
},
],
});
};
const handleExport = (editor: Editor) => {
const html = editor.getHTML();
console.log(html);
// Output includes Tailwind classes:
// <div class="p-6 bg-neutral-100 rounded-lg" data-editu-group>
// <h1>Welcome</h1>
// <p>This is a styled section.</p>
// </div>
// <div class="h-8" data-editu-spacer></div>
// <a class="bg-blue-600 text-white ..." href="/docs">Get Started</a>
};
return (
<Editu
extensions={[
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituButtonReact,
]}
onCreate={handleCreate}
onUpdate={({ editor }) => handleExport(editor)}
/>
);
}WARNING
The HTML output includes Tailwind utility classes. Make sure your rendering environment has Tailwind CSS configured, or the classes won't produce any styles.
When to Use Each Format
| Scenario | Recommended Format |
|---|---|
| Save to database, re-import later | JSON (lossless round-trip) |
| Store in Git, render on static site | Markdown (human-readable, diffable) |
| Send as email or render server-side | HTML (ready to display) |
| Transfer between Editu instances | JSON (exact document structure) |
| Feed to a CMS or documentation tool | Markdown (wide tool support) |
Next Steps
- Layout Blocks — Full reference for layout primitives and token registry
- Custom Blocks — Build your own blocks with custom serialization
- Getting Started — Content import/export basics