Cheat Sheet
comark (core)
parseMarkdown(source, options?)
Parse Comark content into a MarkdownDocument.
import { parseMarkdown } from 'comark'
import shiki from 'comark/plugins/shiki'
const result = await parseMarkdown(source, {
autoUnwrap: true, // Remove <p> wrappers from single-paragraph containers
autoClose: true, // Auto-close incomplete syntax
plugins: [shiki()] // HTML parsing is on by default via the html plugin
})
result.nodes // Node[]: parsed AST nodes
result.frontmatter // Record<string, any>: frontmatter data
result.meta.toc // TOC object (from toc plugin)
result.meta.summary // Node[] (from summary plugin)createMarkdownParser(options?)
Creates a reusable parser initialized once, more efficient for batch processing.
import { createMarkdownParser } from 'comark'
const parse = createMarkdownParser({ plugins: [shiki()] })
const tree1 = await parse('# Document 1')
const tree2 = await parse('# Document 2')renderMarkdown(document, options?)
Convert a MarkdownDocument back to a markdown string.
import { parseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'
const document = await parseMarkdown(source)
const markdown = await renderMarkdown(document, {
maxInlineAttributes: 3, // Switch to YAML block syntax above this threshold
})autoCloseMarkdown(source)
Close unclosed markdown syntax and Comark components. Useful for streaming.
import { autoCloseMarkdown } from 'comark'
autoCloseMarkdown('**bold') // '**bold**'
autoCloseMarkdown('::alert\nText') // '::alert\nText\n::'String renderers
@comark/html
renderHtml(markdown, options?)
Parse and render markdown to an HTML string in one call.
import { renderHtml } from '@comark/html'
const html = await renderHtml('# Hello\n\nThis is **bold**.')createHtmlRenderer(options?)
Creates a reusable parse+render function initialized once.
import { createHtmlRenderer } from '@comark/html'
const renderHtml = createHtmlRenderer({
plugins: [shiki()],
components: {
alert: async ([, attrs, ...children], { render }) =>
`<div class="alert-${attrs.type}">${await render(children)}</div>`
}
})
const html = await renderHtml(source)renderHtmlFromDocument(document, options?)
Render a pre-parsed MarkdownDocument to an HTML string.
import { renderHtmlFromDocument } from '@comark/html'
const html = await renderHtmlFromDocument(document, {
components: {
alert: async ([, attrs, ...children], { render }) =>
`<div class="alert-${attrs.type}">${await render(children)}</div>`
}
})@comark/ansi
renderAnsi(markdown, options?)
Parse and render markdown to an ANSI-styled terminal string in one call.
import { renderAnsi } from '@comark/ansi'
const output = await renderAnsi('# Hello\n\nThis is **bold**.')
process.stdout.write(output)createAnsiRenderer(options?)
Create a reusable parse-and-render function with pre-configured parser and ANSI renderer options.
import { createAnsiRenderer } from '@comark/ansi'
const renderAnsi = createAnsiRenderer({ colors: false, width: 120 })
const output = await renderAnsi('# Hello')renderAnsiFromDocument(document, options?)
Render a pre-parsed MarkdownDocument to an ANSI-styled string.
import { parseMarkdown } from 'comark'
import { renderAnsiFromDocument } from '@comark/ansi'
const document = await parseMarkdown('# Hello')
const output = await renderAnsiFromDocument(document)
process.stdout.write(output)printAnsi(markdown, options?)
Parse and render markdown directly to stdout. Replaces the deprecated writeAnsi alias.
import { printAnsi } from '@comark/ansi'
await printAnsi('# Hello\n\nThis is **bold**.')createAnsiPrinter(options?)
Create a reusable Markdown printer. Pass writer to target something other than process.stdout. Replaces the deprecated createAnsiWriter alias.
import { createAnsiPrinter } from '@comark/ansi'
const printAnsi = createAnsiPrinter({
writer: (output) => process.stderr.write(output),
})
await printAnsi('# Hello')Framework renderers
@comark/vue · @comark/react · @comark/svelte · @comark/angular: same API, different import path.
<Markdown>
Parses and renders markdown in one async step.
<Markdown :plugins="[shiki()]" :components="{ alert: Alert }">
{{ content }}
</Markdown><Markdown plugins={[shiki()]} components={{ alert: Alert }}>
{content}
</Markdown><Markdown value={content} plugins={[shiki()]} components={{ alert: Alert }} /><comark-markdown [value]="content" [plugins]="[shiki()]" [components]="{ alert: Alert }" /><MarkdownDocument>
Renders a pre-parsed MarkdownDocument with no parser shipped to the client.
<MarkdownDocument :value="document" :components="{ alert: Alert }" /><MarkdownDocument value={document} components={{ alert: Alert }} /><MarkdownDocument value={document} components={{ alert: Alert }} /><comark-markdown-document [value]="document" [components]="{ alert: Alert }" />defineMarkdownComponent(options)
Creates a pre-configured <Markdown> with baked-in plugins and component mappings.
import { defineMarkdownComponent } from '@comark/vue' // or @comark/react, @comark/angular
export const AppMarkdown = defineMarkdownComponent({
plugins: [shiki(), toc()],
components: { alert: CustomAlert },
})Transforming nodes before render
transformNode lets you change the tag or props of an element node right before it renders, based on its props and the render context. Use it when a value is only known at render time, such as runtime data, and components alone can't express the change.
The option takes a function or an array of functions. Every renderer accepts it: the <Markdown> and <MarkdownDocument> components in Vue, React, Svelte, and Angular, defineMarkdownComponent, defineMarkdownDocumentComponent, and the @comark/html and @comark/ansi render functions.
import type { NodeTransform } from 'comark'
// Render every `::cta` as a link to the signed-in user's dashboard
const transformNode: NodeTransform = ({ tag, props, renderData }) => {
if (tag !== 'cta') return
return {
tag: 'a',
props: { ...props, href: `/users/${renderData.data.userId}/dashboard` },
}
}<MarkdownDocument :value="document" :data="{ userId: 42 }" :transform-node="transformNode" /><MarkdownDocument value={document} data={{ userId: 42 }} transformNode={transformNode} /><MarkdownDocument value={document} data={{ userId: 42 }} {transformNode} /><comark-markdown-document [value]="document" [data]="{ userId: 42 }" [transformNode]="transformNode" />const html = await renderHtmlFromDocument(document, { data: { userId: 42 }, transformNode })The function receives:
tag: the tag the renderer will use.props: the node's attributes with:bindings resolved. Every renderer resolves them the same way: the:is removed, the value is parsed as JSON, and a value that isn't JSON is looked up as a data path.:count="5"gives{ count: 5 }, and a path that doesn't exist givesundefined.node: the node as it is in the document.parent: the parent element node, when the renderer knows it. Svelte doesn't pass it.renderData:{ frontmatter, meta, data, props }, wherepropsholds the parent's props.
Return { tag?, props? } to replace either one, or nothing to keep the node as it is. The returned props are final: : keys in them aren't resolved again. The node's children then see the returned props as their parent props, so :title="props.title" in a child reads the new value.
These rules apply:
- Lookup: a returned tag is looked up like any tag, in
components, thencomponentsManifest, then as a native element. Anasprop still takes precedence in Vue, React, and Angular, so remove it frompropsif the new tag must win. - Order: with an array, each function sees the result of the previous one.
defineMarkdownComponentanddefineMarkdownDocumentComponentrun their owntransformNodebefore the instance's. - Safety: the returned tag and props are rendered as-is, like the output of a custom component. Comark doesn't sanitize them, and the security plugin runs at parse time, so its options don't apply either. You're responsible for validating anything the function builds from untrusted data, such as URLs from
data, and for returning a valid tag name. - Purity: the function must be synchronous and must not mutate
props,node, orrenderData. It runs for every element node on every render, and Angular can call it twice for a node that renders as a custom component. - Stable reference: in Vue, React, and Angular, define the function or array once, outside the render. A new array on every render makes the document render again.
Types
MarkdownDocument
interface MarkdownDocument {
nodes: Node[]
frontmatter: Record<string, any>
meta: {
toc?: Toc
summary?: Node[]
[key: string]: any
}
}RenderMarkdownOptions
interface RenderMarkdownOptions {
maxInlineAttributes?: number // Max inline attributes before switching to YAML block (default: 3)
blockAttributesStyle?: 'frontmatter' | 'codeblock' // Block attribute syntax style (default: 'codeblock')
rawHtmlText?: 'escape' | 'verbatim' // Text inside raw HTML blocks: escape tag-like `<` (default) or keep as-is
frontmatterOptions?: DumpOptions // js-yaml options for frontmatter serialization
components?: Record<string, NodeHandler | ConditionalNodeHandler> // Custom render handlers for specific elements
data?: Record<string, any> // Additional data passed to render handlers
}Node
type Node =
| string // TextNode
| [tag: string, attrs: ElementNodeAttributes, ...children: Node[]] // ElementNode
| [tag: null, attrs: ElementNodeAttributes, comment: string] // CommentNodeParserOptions
interface ParserOptions {
autoUnwrap?: boolean // default: true
autoClose?: boolean | AutoCloseFunction // default: true
unwrap?: boolean | string | string[] // strip top-level wrapper tags, e.g. 'p' (default: false)
linkify?: boolean // deprecated: use registerDefaultPlugins: false + linkify()
headingIds?: boolean | 'nested' | 'flat' // default: true ('flat') — slug from visible text; duplicates get -1, -2, … (keeps Unicode letters/numbers, omits symbol-only ids); explicit id wins
registerDefaultPlugins?: boolean // default: true
plugins?: ComarkPlugin[]
tracer?: ComarkTracer // OpenTelemetry-style tracer for parse spans
}Toc
// Exported from 'comark/plugins/toc'
interface Toc {
title: string
depth: number
searchDepth: number
links: TocLink[]
}
interface TocLink {
id: string
text: string
depth: number
children?: TocLink[]
}Imports
// Core
import { parseMarkdown, createMarkdownParser, autoCloseMarkdown } from 'comark'
import { renderMarkdown } from 'comark/render'
import type { RenderMarkdownOptions } from 'comark/render'
import { defineComarkPlugin } from 'comark'
// Plugins
import shiki from 'comark/plugins/shiki'
import emoji from 'comark/plugins/emoji'
import toc from 'comark/plugins/toc'
import summary from 'comark/plugins/summary'
import security from 'comark/plugins/security'
// HTML rendering
import { createHtmlRenderer, renderHtml, renderHtmlFromDocument } from '@comark/html'
// ANSI rendering
import {
renderAnsi,
renderAnsiFromDocument,
createAnsiRenderer,
printAnsi,
createAnsiPrinter,
} from '@comark/ansi'
// Vue
import { Markdown, MarkdownDocument, defineMarkdownComponent } from '@comark/vue'
// React
import { Markdown, MarkdownDocument, defineMarkdownComponent } from '@comark/react'
// Svelte
import { Markdown, MarkdownDocument } from '@comark/svelte'
// Types
import type {
MarkdownDocument,
ElementNode,
TextNode,
CommentNode,
ElementNodeAttributes,
Node,
ParserOptions,
ComarkPlugin,
} from 'comark'Related
- Parse API - Full parse options, plugins, and examples
- Render API - Render a
MarkdownDocumentback to markdown - Streaming API - Handle incomplete syntax for streaming
- HTML Rendering - Server-side HTML generation
- Vue Rendering - Render in Vue applications
- React Rendering - Render in React applications
- Svelte Rendering - Render in Svelte applications
- Nuxt - Nuxt module shipped in
@comark/vue - ANSI Rendering - Terminal output
- Plugins - Syntax highlighting, math, TOC, and more
- Document Model - Parsed document and node model
Streaming API
Render incomplete Markdown correctly during streaming. autoCloseMarkdown() closes unterminated syntax so AI output displays at every frame.
Why Comark?
Comark gives JavaScript applications one Markdown pipeline for standards-based parsing, serializable documents, multiple renderers, plugins, and streaming.