slash-editor
Guides

Markdown

Exporting a document to .md and importing it back.

Markdown import/export lives in its own entry, @slash-editor/core/markdown, so marked never lands in a bundle that doesn't use it. Add the extension through extend:

import { markdown } from "@slash-editor/core/markdown";

const editor = useSlashEditor({
  blockKit: { extend: [markdown()] },
});

// Export
const md = editor.getMarkdown();
new Blob([md], { type: "text/markdown" }); // save as .md

// Import
editor.commands.setContent(md, { contentType: "markdown" });

useSlashEditor({ content: md, contentType: "markdown", … }) loads markdown as the initial content the same way.

Without an editor

serializeMarkdown and parseMarkdown need neither an editor nor a DOM, so they run on a server:

import { createBlockKit } from "@slash-editor/core";
import { parseMarkdown, serializeMarkdown } from "@slash-editor/core/markdown";

const extensions = createBlockKit({ mention: { items } });
const md = serializeMarkdown(doc, extensions);
const json = parseMarkdown(md, extensions);

Pass the extensions the document was written with — a node the schema doesn't know renders as nothing. Omitted, they default to createBlockKit(). Parsed documents carry no block ids; the editor assigns them on load.

What it writes

GitHub-flavoured markdown. Blocks GFM has no syntax for use what GitHub renders, and carry the rest in <!-- slash:… --> comments, which GitHub and Obsidian hide. Every node below survives the round trip.

BlockMarkdown
Callout> [!TIP] alert: 💡 TIP, ℹ️ NOTE, ❗ IMPORTANT, ⚠️ WARNING, ⛔ CAUTION. Other icons add a marker
Toggle<details> / <summary>; a toggle heading wraps its title in <h1><h3>
ColumnsColumn contents one after another, between slash:columns / slash:column markers
Image![alt](src), plus a marker when it has a width
File, video, embedA marker with their metadata, then [label](url)
Mention@label, wrapped in markers that keep the id

Left out: comment anchors (their text stays; threads live in your CommentThreadStore), AI drafts not yet kept, and uploads that haven't finished.

On import, GitHub alerts and <details> written by hand come in as callouts and toggles. A marker that can't be used — hand-edited into invalid JSON, say — is dropped and the block after it imports as plain markdown.

setContent on a collaborative document replaces it for every peer.

On this page