--- title: Markdown description: Parse and render Markdown with Bun's built-in Markdown API, supporting GFM extensions and custom rendering callbacks --- **Unstable API** — This API is under active development and may change in future versions of Bun. Bun includes a fast, built-in Markdown parser written in Zig. It supports GitHub Flavored Markdown (GFM) extensions and provides three APIs: - `Bun.markdown.html()` — render Markdown to an HTML string - `Bun.markdown.render()` — render Markdown with custom callbacks for each element - `Bun.markdown.react()` — render Markdown to React JSX elements --- ## `Bun.markdown.html()` Convert a Markdown string to HTML. ```ts const html = Bun.markdown.html("# Hello **world**"); // "

Hello world

\n" ``` GFM extensions like tables, strikethrough, and task lists are enabled by default: ```ts const html = Bun.markdown.html(` | Feature | Status | |-------------|--------| | Tables | ~~done~~ | | Strikethrough| ~~done~~ | | Task lists | done | `); ``` ### Options Pass an options object as the second argument to configure the parser: ```ts const html = Bun.markdown.html("some markdown", { tables: true, // GFM tables (default: true) strikethrough: true, // GFM strikethrough (default: true) tasklists: true, // GFM task lists (default: true) tagFilter: true, // GFM tag filter for disallowed HTML tags autolinks: true, // Autolink URLs, emails, and www. links }); ``` All available options: | Option | Default | Description | | ---------------------- | ------- | ----------------------------------------------------------- | | `tables` | `false` | GFM tables | | `strikethrough` | `false` | GFM strikethrough (`~~text~~`) | | `tasklists` | `false` | GFM task lists (`- [x] item`) | | `autolinks` | `false` | Enable autolinks — see [Autolinks](#autolinks) | | `headings` | `false` | Heading IDs and autolinks — see [Heading IDs](#heading-ids) | | `hardSoftBreaks` | `false` | Treat soft line breaks as hard breaks | | `wikiLinks` | `false` | Enable `[[wiki links]]` | | `underline` | `false` | `__text__` renders as `` instead of `` | | `latexMath` | `false` | Enable `$inline$` and `$$display$$` math | | `collapseWhitespace` | `false` | Collapse whitespace in text | | `permissiveAtxHeaders` | `false` | ATX headers without space after `#` | | `noIndentedCodeBlocks` | `false` | Disable indented code blocks | | `noHtmlBlocks` | `false` | Disable HTML blocks | | `noHtmlSpans` | `false` | Disable inline HTML | | `tagFilter` | `false` | GFM tag filter for disallowed HTML tags | #### Autolinks Pass `true` to enable all autolink types, or an object for granular control: ```ts // Enable all autolinks (URL, WWW, email) Bun.markdown.html("Visit www.example.com", { autolinks: true }); // Enable only specific types Bun.markdown.html("Visit www.example.com", { autolinks: { url: true, www: true }, }); ``` #### Heading IDs Pass `true` to enable both heading IDs and autolink headings, or an object for granular control: ```ts // Enable heading IDs and autolink headings Bun.markdown.html("## Hello World", { headings: true }); // '

Hello World

\n' // Enable only heading IDs (no autolink) Bun.markdown.html("## Hello World", { headings: { ids: true } }); // '

Hello World

\n' ``` --- ## `Bun.markdown.render()` Parse Markdown and render it using custom JavaScript callbacks. This gives you full control over the output format — you can generate HTML with custom classes, React elements, ANSI terminal output, or any other string format. ```ts const result = Bun.markdown.render("# Hello **world**", { heading: (children, { level }) => `${children}`, strong: children => `${children}`, paragraph: children => `

${children}

`, }); // '

Hello world

' ``` ### Callback signature Each callback receives: 1. **`children`** — the accumulated content of the element as a string 2. **`meta`** (optional) — an object with element-specific metadata Return a string to replace the element's rendering. Return `null` or `undefined` to omit the element from the output entirely. If no callback is registered for an element, its children pass through unchanged. ### Block callbacks | Callback | Meta | Description | | ------------ | ------------------------------------------- | ---------------------------------------------------------------------------------------- | | `heading` | `{ level: number, id?: string }` | Heading level 1–6. `id` is set when `headings: { ids: true }` is enabled | | `paragraph` | — | Paragraph block | | `blockquote` | — | Blockquote block | | `code` | `{ language?: string }` | Fenced or indented code block. `language` is the info-string when specified on the fence | | `list` | `{ ordered: boolean, start?: number }` | Ordered or unordered list. `start` is the start number for ordered lists | | `listItem` | `{ checked?: boolean }` | List item. `checked` is set for task list items (`- [x]` / `- [ ]`) | | `hr` | — | Horizontal rule | | `table` | — | Table block | | `thead` | — | Table head | | `tbody` | — | Table body | | `tr` | — | Table row | | `th` | `{ align?: "left" \| "center" \| "right" }` | Table header cell. `align` is set when alignment is specified | | `td` | `{ align?: "left" \| "center" \| "right" }` | Table data cell. `align` is set when alignment is specified | | `html` | — | Raw HTML content | ### Inline callbacks | Callback | Meta | Description | | --------------- | ---------------------------------- | ---------------------------- | | `strong` | — | Strong emphasis (`**text**`) | | `emphasis` | — | Emphasis (`*text*`) | | `link` | `{ href: string, title?: string }` | Link | | `image` | `{ src: string, title?: string }` | Image | | `codespan` | — | Inline code (`` `code` ``) | | `strikethrough` | — | Strikethrough (`~~text~~`) | | `text` | — | Plain text content | ### Examples #### Custom HTML with classes ```ts const html = Bun.markdown.render("# Title\n\nHello **world**", { heading: (children, { level }) => `${children}`, paragraph: children => `

${children}

`, strong: children => `${children}`, }); ``` #### Stripping all formatting ```ts const plaintext = Bun.markdown.render("# Hello **world**", { heading: children => children, paragraph: children => children, strong: children => children, emphasis: children => children, link: children => children, image: () => "", code: children => children, codespan: children => children, }); // "Hello world" ``` #### Omitting elements Return `null` or `undefined` to remove an element from the output: ```ts const result = Bun.markdown.render("# Title\n\n![logo](img.png)\n\nHello", { image: () => null, // Remove all images heading: children => children, paragraph: children => children + "\n", }); // "Title\nHello\n" ``` #### ANSI terminal output ```ts const ansi = Bun.markdown.render("# Hello\n\nThis is **bold** and *italic*", { heading: (children, { level }) => `\x1b[1;4m${children}\x1b[0m\n`, paragraph: children => children + "\n", strong: children => `\x1b[1m${children}\x1b[22m`, emphasis: children => `\x1b[3m${children}\x1b[23m`, }); ``` #### Code block syntax highlighting ````ts const result = Bun.markdown.render("```js\nconsole.log('hi')\n```", { code: (children, meta) => { const lang = meta?.language ?? ""; return `
${children}
`; }, }); ```` ### Parser options Parser options are passed as a separate third argument: ```ts const result = Bun.markdown.render( "Visit www.example.com", { link: (children, { href }) => `[${children}](${href})`, paragraph: children => children, }, { autolinks: true }, ); ``` --- ## `Bun.markdown.react()` Render Markdown directly to React elements. Returns a `` that you can use as a component return value. ```tsx function Markdown({ text }: { text: string }) { return Bun.markdown.react(text); } ``` ### Server-side rendering Works with `renderToString()` and React Server Components: ```tsx import { renderToString } from "react-dom/server"; const html = renderToString(Bun.markdown.react("# Hello **world**")); // "

Hello world

" ``` ### Component overrides Replace any HTML element with a custom React component by passing it in the second argument, keyed by tag name: ```tsx function Code({ language, children }) { return (
      {children}
    
); } function Link({ href, title, children }) { return ( {children} ); } function Heading({ id, children }) { return (

{children}

); } const el = Bun.markdown.react( content, { pre: Code, a: Link, h2: Heading, }, { headings: { ids: true } }, ); ``` #### Available overrides Every HTML tag produced by the parser can be overridden: | Option | Props | Description | | ------------ | ---------------------------- | --------------------------------------------------------------- | | `h1`–`h6` | `{ id?, children }` | Headings. `id` is set when `headings: { ids: true }` is enabled | | `p` | `{ children }` | Paragraph | | `blockquote` | `{ children }` | Blockquote | | `pre` | `{ language?, children }` | Code block. `language` is the info string (e.g. `"js"`) | | `hr` | `{}` | Horizontal rule (no children) | | `ul` | `{ children }` | Unordered list | | `ol` | `{ start, children }` | Ordered list. `start` is the first item number | | `li` | `{ checked?, children }` | List item. `checked` is set for task list items | | `table` | `{ children }` | Table | | `thead` | `{ children }` | Table head | | `tbody` | `{ children }` | Table body | | `tr` | `{ children }` | Table row | | `th` | `{ align?, children }` | Table header cell | | `td` | `{ align?, children }` | Table data cell | | `em` | `{ children }` | Emphasis (`*text*`) | | `strong` | `{ children }` | Strong (`**text**`) | | `a` | `{ href, title?, children }` | Link | | `img` | `{ src, alt?, title? }` | Image (no children) | | `code` | `{ children }` | Inline code | | `del` | `{ children }` | Strikethrough (`~~text~~`) | | `br` | `{}` | Hard line break (no children) | ### React 18 and older By default, elements use `Symbol.for('react.transitional.element')` as the `$$typeof` symbol. For React 18 and older, pass `reactVersion: 18` in the options (third argument): ```tsx function Markdown({ text }: { text: string }) { return Bun.markdown.react(text, undefined, { reactVersion: 18 }); } ``` ### Parser options All [parser options](#options) are passed as the third argument: ```tsx const el = Bun.markdown.react("## Hello World", undefined, { headings: { ids: true }, autolinks: true, }); ```