Markdown transformer for notion-to-anything—converts Notion pages to clean Markdown with YAML frontmatter, rich-text formatting, and full block type coverage.
Turn your Notion workspace into publish-ready Markdown—headings, tables, code blocks, checklists, and more.
Exporting content from Notion to Markdown is surprisingly painful:
- Manual export—Notion's built-in export is click-heavy, lossy, and unsuitable for automation
- Missing annotations—Bold, italic, strikethrough, inline code, equations, and links are often dropped or mangled
- No frontmatter—Metadata like page title is lost, making static site generation difficult
- Incomplete block coverage—Callouts, toggles, synced blocks, tables, and embeds are frequently ignored
@notion-to-anything/markdown handles the full Notion block vocabulary:
- Complete rich-text rendering—Bold, italic, strikethrough, code, underline, equations, and links all map correctly
- YAML frontmatter—Page metadata is automatically included as frontmatter for static site generators
- 30+ block types—Headings, lists, to-dos, code blocks, quotes, callouts, tables, images, embeds, and more
- Composable block handlers—Every handler is individually exported so you can mix, match, or override
- GFM-compatible output—Tables, task lists, and fenced code blocks follow GitHub Flavored Markdown conventions
# npm
npm install @notion-to-anything/markdown @notion-to-anything/core @notionhq/client
# pnpm
pnpm add @notion-to-anything/markdown @notion-to-anything/core @notionhq/client
# yarn
yarn add @notion-to-anything/markdown @notion-to-anything/core @notionhq/client
@notion-to-anything/coreis a peer dependency.
import { Client } from '@notionhq/client';
import { Notion } from '@notion-to-anything/core';
import { markdown } from '@notion-to-anything/markdown';
const notion = new Notion({
client: new Client({ auth: process.env.NOTION_TOKEN }),
});
const page = await notion.getPage('page-id');
const content = await page.to(markdown);Output:
---
title: My Page Title
---
# Heading
This is a paragraph with **bold** and _italic_ text.
- Bullet item
- [x] Completed task| Feature | Description |
|---|---|
| Rich-text rendering | Bold, italic, strikethrough, code, underline, equations, and links |
| YAML frontmatter | Automatic page metadata as frontmatter (title) |
| 30+ block handlers | Headings, lists, to-dos, code, quotes, callouts, tables, images, embeds, etc. |
| Composable architecture | Every block handler exported individually for custom transformer composition |
| Nested block support | Indented children for paragraphs, lists, toggles, and callouts |
| GFM-compatible tables | Pipe-delimited tables from Notion table blocks |
| Fenced code blocks | Language-tagged code blocks preserving syntax highlighting hints |
import { Client } from '@notionhq/client';
import { Notion } from '@notion-to-anything/core';
import { markdown } from '@notion-to-anything/markdown';
const notion = new Notion({
client: new Client({ auth: process.env.NOTION_TOKEN }),
});
const page = await notion.getPage('page-id');
const md = await page.to(markdown);
// Write to file
import { writeFile } from 'node:fs/promises';
await writeFile('output.md', md);const dataSource = await notion.getDataSource('datasource-id');
const { pages } = await dataSource.search({
sorts: [{ field: 'Created', order: 'desc' }],
});
for (const page of pages) {
const md = await page.to(markdown);
await writeFile(`${page.title}.md`, md);
}Every block handler is exported individually, so you can build a custom transformer with only the handlers you need:
import {
paragraph,
heading1,
heading2,
heading3,
code,
image,
fallback,
} from '@notion-to-anything/markdown';
import { createBlockTransformer } from '@notion-to-anything/core';
import type { NotionTransformer } from '@notion-to-anything/core';
const minimalBlock = createBlockTransformer<string>({
paragraph,
heading1,
heading2,
heading3,
code,
image,
fallback, // required—handles unrecognized block types
});
const minimalMarkdown: NotionTransformer<string, string> = {
block: minimalBlock,
page: (blocks, page) => `# ${page.title}\n\n${blocks.join('\n\n')}`,
};
const output = await page.to(minimalMarkdown);| Notion Block | Markdown Output |
|---|---|
paragraph |
Plain text with rich-text annotations |
heading_1 |
# Heading |
heading_2 |
## Heading |
heading_3 |
### Heading |
bulleted_list_item |
- Item (with nested indentation) |
numbered_list_item |
1. Item (with nested indentation) |
to_do |
- [x] Item or - [ ] Item |
code |
Fenced code block with language tag |
quote |
> Quoted text |
callout |
> icon Content |
equation |
$$expression$$ |
divider |
--- |
table_of_contents |
[TOC] |
image |
 |
video / audio / file / pdf |
[Caption](url) |
bookmark / embed |
[Caption](url) |
link_preview |
[Link Preview](url) |
table + table_row |
Pipe-delimited table rows |
column_list / column |
Children joined |
toggle |
Content with indented children |
template |
Template: content with children |
synced_block |
Children joined |
link_to_page |
[Page Link](page://id) or [Database Link](database://id) |
child_page / child_database |
null (skipped) |
unsupported / transcription |
null (skipped) |
| Annotation | Markdown Output |
|---|---|
| Bold | **text** |
| Italic | _text_ |
| Strikethrough | ~~text~~ |
| Code | `text` |
| Underline | <u>text</u> |
| Equation | $expression$ |
| Link | [text](url) |
| Requirement | Value |
|---|---|
| Node.js | >= 18 |
| TypeScript | 5.x+ |
| Module format | ESM only |
| Dependencies | None (zero runtime dependencies) |
| Peer deps | @notion-to-anything/core >= 1.0.0 |
| Issue | Solution |
|---|---|
| Missing annotations in output | Ensure the Notion API returns rich-text annotations—check that the page is not restricted |
| No frontmatter in output | Frontmatter is always included; if blank, the page may lack a title property |
| Tables render incorrectly | Notion tables require at least one table_row child—empty tables produce no output |
| Cannot import (CJS) | @notion-to-anything/markdown is ESM-only; use dynamic import() in CommonJS or migrate to ESM |
@notion-to-anything/core not found |
Install it as a peer dependency: pnpm add @notion-to-anything/core |
| Custom handler not invoked | Handler keys use camelCase (heading1, bulletedListItem, toDo), not snake_case |
Does this produce GitHub Flavored Markdown?
Yes. Tables use pipe syntax, task lists use - [x]/- [ ], and code blocks use triple-backtick fencing with language tags—all GFM-compatible.
Can I override individual block handlers?
Yes. Import the handlers you want, define your own for the rest, and pass them all to createBlockTransformer(). A fallback handler is always required.
How is frontmatter generated?
The page function prepends YAML frontmatter with the page title. Values are escaped using the escapeYaml() utility to handle special characters safely.
What happens with unsupported block types?
The fallback handler returns null, which filters the block out of the final output.
More help: GitHub Issues · Discussions
| Package | Description |
|---|---|
@notion-to-anything/core |
Core engine—required as peer dependency |
@notion-to-anything/json |
JSON transformer preserving full block structure |
notion-to-anything |
Convenience re-export of the core package |
See CONTRIBUTING.md for full guidelines.
- Fork & Clone:
git clone https://github.com/alvis/notion-to-anything-monorepo.git - Install:
pnpm install - Develop:
pnpm test:watchfor development mode - Test:
pnpm test && pnpm lint - Submit: Create a pull request
Found a vulnerability? Please email alvis@hilbert.space with details. We aim to respond within 48 hours and patch as quickly as possible.
MIT (c) 2025-2026 Alvis HT Tang
Free for personal and commercial use. See LICENSE for details.
Star on GitHub · View on npm · Documentation
From Notion blocks to publish-ready Markdown—every annotation, every block type.