Export Notion databases to Markdown files for static site generators like 11ty, with automatic image downloading and flexible configuration
npm install notion-to-ssg> Export Notion databases to Markdown files for static site generators like 11ty (Eleventy)


A flexible and powerful tool to export your Notion databases as Markdown files with YAML front matter, perfect for static site generators like 11ty (Eleventy), Hugo, Jekyll, and more.
- ๐ Zero hardcoded properties - Works with any Notion database schema
- ๐ Automatic Markdown conversion - Converts Notion blocks to clean Markdown
- ๐ผ๏ธ Image downloading - Downloads and saves images locally with content-based hashing to prevent duplicates
- ๐ง Flexible configuration - YAML or JSON config files
- ๐ฏ Multiple databases - Export multiple databases in one run
- ๐งน Smart cleanup - Automatically removes old content and images before syncing (configurable)
- ๐ท๏ธ Smart slugification - Customizable URL-friendly slug generation
- ๐ฆ Front matter support - All Notion properties exported as YAML front matter
- ๐ Cover & icon support - Downloads page covers and icons
- ๐ Property normalization - Handles all Notion property types (relations, rollups, formulas, etc.)
- ๐ Permalink override - Use a permalink property in your Notion page to set the URL directly.
``bash`
npm install -g notion-to-ssg
`bash`
npm install --save-dev notion-to-ssg
`bash`
npx notion-to-ssg
1. Go to https://www.notion.so/my-integrations
2. Create a new integration
3. Copy the "Internal Integration Token"
4. Share your database with the integration
Your database ID is in the URL:
``
https://www.notion.so/[workspace]/[database-id]?v=[view-id]
Example: https://www.notion.so/myworkspace/a1b2c3d4e5f6... โ database ID is a1b2c3d4e5f6...
Create notion.config.yml in your project root:
`yaml`
databases:
- databaseId: "your-database-id-here"
srcDir: "src/posts"
srcDirImages: "src/assets/images"
basePath: "/blog"
layout: "layouts/post.njk"
Create a .env file:
`env`
NOTION_TOKEN=secret_your_token_here
`bash`
notion-to-ssg
`bashUse default config file (notion.config.yml or notion.config.json)
notion-to-ssg
$3
`javascript
const { exportNotionToSSG } = require('notion-to-ssg');async function exportMyContent() {
const results = await exportNotionToSSG({
notionToken: process.env.NOTION_TOKEN,
configPath: './my-config.yml', // Optional
// Or provide config directly:
config: {
databases: [
{
databaseId: 'your-database-id',
srcDir: 'src/posts',
basePath: '/blog',
layout: 'layouts/post.njk'
}
]
}
});
console.log(results);
}
`$3
Add to your
package.json:`json
{
"scripts": {
"notion:export": "notion-to-ssg",
"prebuild": "npm run notion:export",
"build": "eleventy"
}
}
`โ๏ธ Configuration
$3
#### Required fields
-
databaseId - Your Notion database ID
- srcDir - Output directory for markdown files
- basePath - URL base path (e.g., /blog)
- layout - Template layout file (e.g., layouts/post.njk)#### Optional fields
-
srcDirImages - Directory for downloaded images (default: src/images/notion)
- cleanBeforeSync - Clean old content before syncing (default: true)
- When true: Removes all .md files in srcDir and all images in srcDirImages before fetching
- When false: Keeps existing files (may result in orphaned content)
- Recommended: Keep as true to ensure deleted Notion pages are removed
- excludeProperties - Array of property names to exclude from front matter
- slug - Slug generation configuration
- from - Property to use: "title", "id", or any property name (default: "title")
- fallback - Fallback if primary source is empty (default: "id")
- lower - Convert to lowercase (default: true)
- permalink - URL pattern (default: "{basePath}/{slug}/"). Note: If a page has a property named permalink, its value will be used as the final URL, overriding this setting.
- frontMatter - Additional static fields to add to all pages$3
`yaml
databases:
- databaseId: "abc123def456"
srcDir: "src/blog"
srcDirImages: "src/assets/images/blog"
basePath: "/blog"
layout: "layouts/post.njk"
cleanBeforeSync: true # default: true (removes old content before sync)
excludeProperties:
- "Internal Notes"
- "Status"
- "Private Field"
slug:
from: "title"
fallback: "id"
lower: true
permalink: "/blog/{slug}/"
frontMatter:
tags: "post"
templateEngineOverride: "njk,md"
eleventyExcludeFromCollections: false - databaseId: "xyz789uvw012"
srcDir: "src/projects"
srcDirImages: "src/assets/images/projects"
basePath: "/work"
layout: "layouts/project.njk"
permalink: "/work/{slug}/"
frontMatter:
tags: "project"
`๐ Output Example
Given a Notion page with these properties:
- Title: "My First Blog Post"
- Date: 2025-01-15
- Tags:
["javascript", "tutorial"]
- Author: "John Doe"
- Published: true
- permalink: /blog/a-custom-url-from-notion/The exported Markdown file (
my-first-blog-post.md) will look like:`markdown
---
layout: layouts/post.njk
title: My First Blog Post
permalink: /blog/a-custom-url-from-notion/
notionPageId: abc123def456
Date: '2025-01-15'
Tags:
- javascript
- tutorial
Author: "John Doe"
Published: true
tags: post
templateEngineOverride: njk,md
---My First Blog Post
This is the content from the Notion page, converted to Markdown.
A heading
- Bullet points work
- All Notion blocks are converted
Bold and italic text are preserved.
`๐จ Notion Property Type Support
All Notion property types are automatically converted:
| Notion Type | Output Format |
|------------|---------------|
| Title | Plain text string |
| Rich Text | Plain text string |
| Number | Number |
| Select | String (option name) |
| Multi-select | Array of strings |
| Date | ISO date string |
| People | Array of names/emails |
| Files & Media | Array of URLs |
| Checkbox | Boolean |
| URL | String |
| Email | String |
| Phone | String |
| Formula | Extracted value based on result type |
| Relation | Array of page IDs |
| Rollup | Computed value |
| Status | String (status name) |
๐ผ๏ธ Image Handling
- Automatic download - All Notion-hosted images are downloaded locally
- Smart caching - Images are cached in-memory to avoid re-downloading during the same run
- Content-based hashing - Images are hashed by content (not URL) to prevent duplicates
- Same image with different Notion URLs = single file on disk
- Saves storage and prevents duplicate images
- Unique filenames - Format:
{slug}-{index}-{contenthash}.{ext}
- Relative paths - Generated paths work with 11ty and other SSGs
- Cover images - Page cover images saved as coverImage in front matter
- Icons - Page icons saved as iconImage in front matter๐งน Content Cleanup
By default,
cleanBeforeSync: true ensures your output directory stays in sync with Notion:- Before sync: Removes all
.md files from srcDir and all images from srcDirImages
- After sync: Only current Notion content exists on disk
- Benefits:
- Deleted Notion pages are automatically removed
- Renamed pages don't leave orphaned files
- Old images are cleaned up
- Output directory matches Notion exactlyTo disable cleanup (and handle it manually):
`yaml
databases:
- databaseId: "your-db-id"
cleanBeforeSync: false # Keep existing files
# ... other config
`๐ง Use Cases
$3
Perfect for building blogs, documentation sites, and portfolios:
`yaml
databases:
- databaseId: "your-blog-db"
srcDir: "src/blog"
basePath: "/blog"
layout: "layouts/post.njk"
frontMatter:
tags: "post"
`$3
`yaml
databases:
- databaseId: "your-content-db"
srcDir: "content/posts"
basePath: "/posts"
layout: "post"
`$3
`yaml
databases:
- databaseId: "your-db"
srcDir: "_posts"
basePath: "/blog"
layout: "post"
`$3
Use with GitHub Actions or other CI/CD:
`yaml
.github/workflows/export-notion.yml
name: Export Notion
on:
schedule:
- cron: '0 /6 ' # Every 6 hours
workflow_dispatch:jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm run notion:export
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
- run: npm run build
`๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
See CONTRIBUTING.md for detailed guidelines.
๐ Releasing
This project uses release-it for automated releases.
For maintainers:
`bash
Interactive release
npm run releaseOr specify version bump
npm run release:patch # 1.0.0 โ 1.0.1
npm run release:minor # 1.0.0 โ 1.1.0
npm run release:major # 1.0.0 โ 2.0.0Dry run to test
npm run release:dry
``See RELEASING.md for complete release documentation.
MIT ยฉ ZeFish
Built with:
- @notionhq/client - Official Notion SDK
- notion-to-md - Notion to Markdown converter
- js-yaml - YAML parser
- slugify - URL slug generator
- 11ty (Eleventy)
- Notion API Documentation
---
Made with โค๏ธ for the Jamstack community