npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

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

Readme

notion-to-ssg

Export Notion databases to Markdown files for static site generators like 11ty (Eleventy)

npm version License: MIT

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 permalink property in your Notion page to set the URL directly.

📦 Installation

As a CLI tool (global)

npm install -g notion-to-ssg

As a project dependency

npm install --save-dev notion-to-ssg

Quick start

npx notion-to-ssg

🚀 Quick Start

1. Get your Notion API token

  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

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_here

5. 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 --version

Programmatic 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 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

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

![An image](../assets/images/blog/my-first-blog-post-0-a1b2c3d4.jpg)

**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 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:dry

See RELEASING.md for complete release documentation.

📝 License

MIT © ZeFish

🙏 Credits

Built with:

📚 Related


Made with ❤️ for the Jamstack community