> For the complete documentation index, see [llms.txt](/llms.txt).
> The full corpus is at [llms-full.txt](/llms-full.txt).

# Markdown

> Render Portable Text as Markdown strings using @portabletext/markdown.

import {PackageManagers} from 'starlight-package-managers'

:::note[Prerequisites]
This guide covers `@portabletext/markdown` **v1.x** ([changelog](https://github.com/portabletext/editor/releases)). No framework dependencies. Works in Node.js, Deno, edge runtimes, and browsers.
:::

`@portabletext/markdown` renders Portable Text blocks as Markdown strings. Use it to generate Markdown for static site generators, README files, AI prompts, or any system that consumes Markdown.

Looking to convert Markdown into Portable Text? See the [Markdown to Portable Text](/conversion/markdown-to-portable-text/) conversion guide.

## Install

<PackageManagers pkg="@portabletext/markdown" />

## Basic usage

`portableTextToMarkdown` takes an array of Portable Text blocks and returns a Markdown string. Standard block styles, decorators, and links are handled automatically.

```ts
import {portableTextToMarkdown} from '@portabletext/markdown'

const markdown = portableTextToMarkdown(blocks)
```

## Custom type renderers

Custom block types (objects in the blocks array) are not rendered by default. Register a renderer for each type you use.

```ts
portableTextToMarkdown(blocks, {
  types: {
    chart: ({value}) => `![${value.title}](${value.imageUrl})`,
  },
})
```

Type renderers receive `value` (the block object), `index` (position in the array), and `isInline` (whether the object appears inline or as a block). Return an empty string to skip an element entirely.

```ts
portableTextToMarkdown(blocks, {
  types: {
    image: ({value, isInline}) => {
      if (isInline) return ''
      return `![${value.alt || ''}](${value.src})`
    },
  },
})
```

## Custom block style renderers

Override how block styles render by providing a `block` map. Each renderer receives `value` (the block) and `children` (the already-rendered content of the block).

```ts
portableTextToMarkdown(blocks, {
  block: {
    h1: ({children}) => `# ${children} #`,
    blockquote: ({children}) => `<blockquote>${children}</blockquote>`,
  },
})
```

## Built-in type renderers

The package exports default renderers for common block types. Import and register the ones you need.

```ts
import {
  DefaultCalloutRenderer,
  DefaultCodeBlockRenderer,
  DefaultHorizontalRuleRenderer,
  DefaultHtmlRenderer,
  DefaultImageRenderer,
  DefaultTableRenderer,
  portableTextToMarkdown,
} from '@portabletext/markdown'

portableTextToMarkdown(blocks, {
  types: {
    'callout': DefaultCalloutRenderer,
    'code': DefaultCodeBlockRenderer,
    'horizontal-rule': DefaultHorizontalRuleRenderer,
    'html': DefaultHtmlRenderer,
    'image': DefaultImageRenderer,
    'table': DefaultTableRenderer,
  },
})
```

| Renderer                        | Expected fields         | Output                 |
| ------------------------------- | ----------------------- | ---------------------- |
| `DefaultCalloutRenderer`        | `tone`, `content`       | `> [!TYPE]\n> content` |
| `DefaultCodeBlockRenderer`      | `code`, `language?`     | ` ```lang\ncode\n``` ` |
| `DefaultHorizontalRuleRenderer` | (none required)         | `---`                  |
| `DefaultHtmlRenderer`           | `html`                  | Raw HTML string        |
| `DefaultImageRenderer`          | `src`, `alt?`, `title?` | `![alt](src "title")`  |
| `DefaultTableRenderer`          | `rows`, `headerRows?`   | Markdown table         |

## Renderer map keys

| Key                 | What it renders                            |
| ------------------- | ------------------------------------------ |
| `types`             | Custom block and inline objects            |
| `marks`             | Annotations and decorators                 |
| `block`             | Block styles (headings, blockquotes, etc.) |
| `listItem`          | List items                                 |
| `hardBreak`         | Line breaks within blocks                  |
| `unknownType`       | Fallback for unregistered types            |
| `unknownBlockStyle` | Fallback for unregistered block styles     |
| `unknownListItem`   | Fallback for unregistered list items       |
| `unknownMark`       | Fallback for unregistered marks            |

By default, unknown types render as JSON code blocks. Unknown marks, block styles, and list items pass through their children unchanged.

## Supported features

| Feature          | PT to Markdown |
| ---------------- | :------------: |
| Headings (h1-h6) |       ✅       |
| Paragraphs       |       ✅       |
| Bold             |       ✅       |
| Italic           |       ✅       |
| Inline code      |       ✅       |
| Strikethrough    |       ✅       |
| Links            |       ✅       |
| Blockquotes      |       ✅       |
| Ordered lists    |       ✅       |
| Unordered lists  |       ✅       |
| Nested lists     |       ✅       |
| Code blocks      |      ✅\*      |
| Horizontal rules |      ✅\*      |
| Images           |      ✅\*      |
| Tables           |      ✅\*      |
| HTML blocks      |      ✅\*      |
| Callouts         |      ✅\*      |

\* Requires registering the built-in renderer (see above).

:::note
This package uses markdown-it as its Markdown parser. Remark and unified plugins are not compatible.
:::

## Further reading

- [Markdown to Portable Text](/conversion/markdown-to-portable-text/) for converting Markdown into PT blocks
- [`@portabletext/markdown` on GitHub](https://github.com/portabletext/editor/tree/main/packages/markdown) for full API documentation and changelog