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 🙏

© 2026 – Pkg Stats / Ryan Hefner

notion-to-site

v0.1.0

Published

Sync any Notion database to static markdown, MDX, or JSON files

Downloads

103

Readme

notion-to-site

Sync any Notion database to local markdown, MDX, or JSON files.

  • One command sync. Point nts at a Notion database and get structured content files with full frontmatter.
  • All block types. Paragraphs, headings, images, callouts, columns, tables, equations, toggles, synced blocks, bookmarks, video, audio, file, PDF, embeds.
  • Incremental updates. Track what changed since the last sync. Only re-fetch updated pages.

Install

npm install -g notion-to-site

Quick start

1. Create a Notion integration

Go to notion.so/my-integrations and create an internal integration. Copy the API key.

2. Share your database

Open your Notion database, click the ... menu, then "Connections", and add your integration.

3. Create a config file

nts init

This creates nts.config.js in your project. Open it and set your database ID:

// nts.config.js
export default {
  database: 'your-database-id-here',
  output: './content',
  adapter: 'markdown',
  author: 'Your Name',
  linkPrefix: '/blog',
  images: { download: true, outputDir: './public/images', format: 'webp', quality: 80 },
  schema: { strict: false },
  sync: { concurrency: 5, deletions: true },
  content: { toc: false, stripBackLinks: true },
}

4. Set your API key and sync

export NOTION_API_KEY=ntn_your_key_here
nts sync

Your content files will appear in ./content.

Config reference

| Field | Type | Default | Description | |---|---|---|---| | database | string | (required) | Your Notion database ID | | output | string | './content' | Directory for output files | | adapter | 'markdown' \| 'mdx' \| 'json' | 'markdown' | Output format | | author | string | '' | Default author name when not set on the page | | linkPrefix | string | '/blog' | URL prefix for internal Notion links | | images.download | boolean | true | Download images locally | | images.outputDir | string | './public/images' | Where to save downloaded images | | images.format | 'webp' \| 'original' | 'webp' | Convert images to WebP or keep original format | | images.quality | number | 80 | WebP compression quality (1-100) | | schema.strict | boolean | false | Validate frontmatter with Zod on every sync | | sync.concurrency | number | 5 | Number of pages to sync in parallel | | sync.deletions | boolean | true | Delete local files for pages removed from Notion | | content.toc | boolean | false | Insert a table of contents after the first heading | | content.stripBackLinks | boolean | true | Remove back-navigation links from Notion pages | | watch.interval | number | 60 | Polling interval in seconds for nts watch |

CLI commands

| Command | Description | |---|---| | nts init | Create nts.config.js in the current directory | | nts sync | Full sync of your Notion database to local files | | nts sync --incremental | Only sync pages changed since the last run | | nts sync --db <id> | Override the database ID from config | | nts watch | Poll and incrementally sync on a timer | | nts watch --interval <seconds> | Set the polling interval (default: 60) | | nts validate | Validate all output files against the Zod schema | | nts status | Show sync state, tracked pages, and statistics |

Output format

Each synced page produces a file with YAML frontmatter and rendered content. Here is a sample:

---
id: my-first-post
path: /content/my-first-post.md
type: post
intent: ""
version: "1.0"
created: "2026-01-15"
last_updated: "2026-03-20"
source:
  platform: notion
  page_id: abc12345-def6-7890-abcd-ef1234567890
meta:
  title: My First Post
  seo_title: My First Post
  author: Your Name
  description: A short summary extracted from the first paragraph.
  canonical: ""
  category:
    - Engineering
  main_tag: Tutorial
  tags:
    - tutorial
    - getting-started
  featured: false
  featured_at: []
  language: en
  post_type: Post
  status: Published
  reading_time: 4
  word_count: 812
  comment: ""
  cover_image: ""
---

# My First Post

Your content starts here...

Frontmatter fields

  • id -- The slug, derived from the page title or a custom Slug property in Notion.
  • path -- Relative file path in the output directory.
  • type -- From the Type/Kind property in Notion, defaults to post.
  • created / last_updated -- Dates from Notion page metadata.
  • source.page_id -- The original Notion page ID for traceability.
  • meta.seo_title -- From an SEO Title property, or falls back to the page title.
  • meta.description -- From a Description property, or auto-extracted from the first paragraph.
  • meta.language -- From a Language property, or auto-detected from the content.
  • meta.reading_time -- Estimated minutes to read (200 words/minute).
  • meta.word_count -- Total word count of the content body.

Framework guides

Next.js

Place your config output in ./content and use a library like gray-matter to parse frontmatter at build time. If you use contentlayer or next-mdx-remote, set adapter: 'mdx' and point the output to your content directory. Images go in ./public/images by default, so they are served statically.

Astro

Astro's content collections work well with the markdown adapter. Set output to ./src/content/blog and define a matching collection schema in src/content/config.ts. Astro will pick up the frontmatter fields automatically. Use adapter: 'mdx' if your pages use components.

SvelteKit

Use mdsvex for markdown processing. Set output to a directory your routes can import from, and parse frontmatter with gray-matter in your +page.ts load function. The JSON adapter (adapter: 'json') is also a good fit if you prefer to load content as data rather than rendered markup.

How it works

nts connects to the Notion API, fetches every page in your database, and converts each one to markdown using custom block-type renderers. It extracts metadata from Notion page properties (title, tags, status, author, etc.), builds structured frontmatter, resolves internal links between pages, and writes the result to your chosen output format.

Environment variables

| Variable | Description | |---|---| | NOTION_API_KEY | Your Notion integration API key (required) |

You can set this in a .env or .env.local file in your project root. Both are loaded automatically.

License

MIT