Skip to content

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

FormatMethodBest For
Markdowneditor.getMarkdown()Storage, Git, static sites, CMS
JSONeditor.getJSON()Database storage, API transfer, re-import
HTMLeditor.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.

typescript
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).

typescript
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.

typescript
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

RegistryToken TypeExample
EDITU_SPACING_TOKENSPadding"md""p-4"
EDITU_BACKGROUND_TOKENSBackground"muted""bg-neutral-100"
EDITU_RADIUS_TOKENSBorder radius"lg""rounded-lg"
EDITU_HEIGHT_TOKENSSpacer height"lg""h-16"
EDITU_DIVIDER_STYLE_TOKENSDivider style"dashed""border-dashed"
EDITU_DIVIDER_THICKNESS_TOKENSDivider thickness"thick""border-t-4"
EDITU_BUTTON_VARIANT_TOKENSButton 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:

typescript
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:

tsx
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

ScenarioRecommended Format
Save to database, re-import laterJSON (lossless round-trip)
Store in Git, render on static siteMarkdown (human-readable, diffable)
Send as email or render server-sideHTML (ready to display)
Transfer between Editu instancesJSON (exact document structure)
Feed to a CMS or documentation toolMarkdown (wide tool support)

Next Steps

Released under the MIT License.