notion-to-ssg
v2.0.7
Published
Export Notion databases to Markdown files for static site generators like 11ty, with automatic image downloading and flexible configuration
Maintainers
Readme
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.
✨ Features
- 🚀 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
permalinkproperty in your Notion page to set the URL directly.
📦 Installation
As a CLI tool (global)
npm install -g notion-to-ssgAs a project dependency
npm install --save-dev notion-to-ssgQuick start
npx notion-to-ssg🚀 Quick Start
1. Get your Notion API token
- Go to https://www.notion.so/my-integrations
- Create a new integration
- Copy the "Internal Integration Token"
- Share your database with the integration
2. Get your database ID
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...
3. Create a configuration file
Create notion.config.yml in your project root:
databases:
- databaseId: "your-database-id-here"
srcDir: "src/posts"
srcDirImages: "src/assets/images"
basePath: "/blog"
layout: "layouts/post.njk"4. Set your environment variable
Create a .env file:
NOTION_TOKEN=secret_your_token_here5. Run the export
notion-to-ssg📖 Usage
CLI
# Use default config file (notion.config.yml or notion.config.json)
notion-to-ssg
# Specify a custom config file
notion-to-ssg -c my-config.yml
notion-to-ssg --config custom-config.json
# Show help
notion-to-ssg --help
# Show version
notion-to-ssg --versionProgrammatic API
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);
}npm scripts
Add to your package.json:
{
"scripts": {
"notion:export": "notion-to-ssg",
"prebuild": "npm run notion:export",
"build": "eleventy"
}
}⚙️ Configuration
Configuration File Options
Required fields
databaseId- Your Notion database IDsrcDir- Output directory for markdown filesbasePath- 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.mdfiles insrcDirand all images insrcDirImagesbefore fetching - When
false: Keeps existing files (may result in orphaned content) - Recommended: Keep as
trueto ensure deleted Notion pages are removed
- When
excludeProperties- Array of property names to exclude from front matterslug- Slug generation configurationfrom- 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 namedpermalink, its value will be used as the final URL, overriding this setting.frontMatter- Additional static fields to add to all pages
Example: Full Configuration
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:
---
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
coverImagein front matter - Icons - Page icons saved as
iconImagein front matter
🧹 Content Cleanup
By default, cleanBeforeSync: true ensures your output directory stays in sync with Notion:
- Before sync: Removes all
.mdfiles fromsrcDirand all images fromsrcDirImages - 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 exactly
To disable cleanup (and handle it manually):
databases:
- databaseId: "your-db-id"
cleanBeforeSync: false # Keep existing files
# ... other config🔧 Use Cases
11ty (Eleventy)
Perfect for building blogs, documentation sites, and portfolios:
databases:
- databaseId: "your-blog-db"
srcDir: "src/blog"
basePath: "/blog"
layout: "layouts/post.njk"
frontMatter:
tags: "post"Hugo
databases:
- databaseId: "your-content-db"
srcDir: "content/posts"
basePath: "/posts"
layout: "post"Jekyll
databases:
- databaseId: "your-db"
srcDir: "_posts"
basePath: "/blog"
layout: "post"Automated Workflows
Use with GitHub Actions or other CI/CD:
# .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:
# Interactive release
npm run release
# Or 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.0
# Dry run to test
npm run release:drySee RELEASING.md for complete release documentation.
📝 License
MIT © ZeFish
🙏 Credits
Built with:
- @notionhq/client - Official Notion SDK
- notion-to-md - Notion to Markdown converter
- js-yaml - YAML parser
- slugify - URL slug generator
📚 Related
Made with ❤️ for the Jamstack community
