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

notro-loader

v0.2.0

Published

A Content loader for Astro that uses the Notion Public API

Readme

notro-loader

npm License: MIT

An Astro Content Loader library that fetches Notion database content via the Markdown Content API into Astro Content Collections.

[!TIP] This package is part of the mosugi/notro monorepo. See the blog template at templates/blog/ for a full working example.

What NotroContent renders

NotroContent compiles Notion markdown into HTML. Each Notion block type maps to a semantic HTML element by default. You can replace any element with your own styled component via the components prop.

| Notion block | Default HTML | notro-ui component | |---|---|---| | Paragraph | <p> | ColoredParagraph | | Heading 1–4 | <h1><h4> | H1H4 | | Callout | <aside> | Callout | | Quote | <blockquote> | Quote | | Toggle | <details> + <summary> | Toggle + ToggleTitle | | Divider | <hr> | — | | Code | <pre> | — | | Image | <img> | ImageBlock | | Video | <figure> | Video | | Audio | <figure> | Audio | | File | <div> | FileBlock | | PDF | <figure> | PdfBlock | | Table | <table> | TableBlock | | Table of contents | <nav> | TableOfContents | | Columns / Column | <div> / <div> | Columns / Column | | Page link | <a> | PageRef | | Database link | <a> | DatabaseRef | | Empty block | <div> | EmptyBlock | | Inline text (colored/underline) | <span> | StyledSpan | | @mention | <span> | Mention | | Date mention | <time> | MentionDate |

notro-ui is an optional style layer. See notro-ui for details.

Installation

npx astro add notro-loader

This installs the package and automatically adds the notro() integration to astro.config.mjs.

Alternatively, install manually:

npm install notro-loader

Setup

1. astro.config.mjs

astro add notro-loader configures this automatically. If you installed manually, add:

import { defineConfig } from "astro/config";
import { notro } from "notro-loader/integration";

export default defineConfig({
  integrations: [notro()],
});

This registers @astrojs/mdx with the required plugin pipeline and the Astro JSX renderer that NotroContent depends on at runtime.

2. src/content.config.ts

Define your collection using the loader function. Extend pageWithMarkdownSchema with your database properties. Use the notroProperties shorthand for concise property schemas.

import { defineCollection } from "astro:content";
import { loader, pageWithMarkdownSchema, notroProperties } from "notro-loader";
import { z } from "zod";

const posts = defineCollection({
  loader: loader({
    queryParameters: {
      data_source_id: import.meta.env.NOTION_DATASOURCE_ID,
      filter: {
        property: "Public",
        checkbox: { equals: true },
      },
    },
    clientOptions: {
      auth: import.meta.env.NOTION_TOKEN,
    },
  }),
  schema: pageWithMarkdownSchema.extend({
    properties: z.object({
      Name: notroProperties.title,
      Description: notroProperties.richText,
      Public: notroProperties.checkbox,
      Tags: notroProperties.multiSelect,
      Date: notroProperties.date,
    }),
  }),
});

export const collections = { posts };

3. Page component

Option A — Headless (no styling)

NotroContent from notro-loader renders semantic HTML with no classes.

---
import { NotroContent, getPlainText } from "notro-loader";

const { entry } = Astro.props;
const title = getPlainText(entry.data.properties.Name);
---

<h1>{title}</h1>
<NotroContent markdown={entry.data.markdown} />

Option B — With notro-ui

Install the styled components once:

npx notro-ui init

Then pass the component map to NotroContent:

---
import { NotroContent, getPlainText } from "notro-loader";
import { notroComponents } from "@/components/notro";

const { entry } = Astro.props;
---

<NotroContent markdown={entry.data.markdown} components={notroComponents} />

Components are copied into src/components/notro/ so you can edit them directly.

Markdown processing (remark-notro)

notro-loader delegates Notion Markdown preprocessing and directive syntax support to the remark-notro package.

remark-notro is used inside notro-loader's MDX compile pipeline and is applied automatically when using NotroContent.

If you want to use remark-notro directly (in a custom unified pipeline or @mdx-js/mdx's evaluate()), import it from the remark-notro package directly rather than from notro-loader.

// ✅ Import directly from remark-notro
import { remarkNfm, preprocessNotionMarkdown } from "remark-notro";

// ❌ Not needed from notro-loader (internal use only)
// import { remarkNfm } from "notro-loader";

Notion API limitations

Reference: Retrieve a page as Markdown – Notion API

Content truncation (truncated)

GET /v1/pages/{page_id}/markdown truncates content at approximately 20,000 blocks.

  • Detectable via truncated: true in the response, but there is no pagination API to fetch the rest
  • notro logs a warning when truncated === true and continues the build with the available content
  • Workaround: split large Notion pages into multiple smaller pages
⚠ Page abc123: markdown content was truncated by the Notion API (~20,000 block limit).
  No pagination is available for this endpoint.
  Consider splitting this Notion page into smaller pages to avoid truncation.

Unrenderable blocks (unknown_block_ids)

unknown_block_ids in the response lists block IDs that the Notion API could not convert to Markdown (unsupported block types, etc.).

  • These blocks are silently omitted from the markdown field
  • There is no way to retrieve their content via this endpoint
  • notro logs the block IDs as a warning and continues the build
⚠ Page abc123: 2 block(s) could not be rendered to Markdown by the Notion API and were omitted.
  Block IDs: xxxxxxxx-..., yyyyyyyy-...

API errors and automatic retries

| Error | Handling | |---|---| | 429 rate_limited / 500 internal_server_error / 503 service_unavailable | Retry with exponential backoff (1s / 2s / 4s, up to 3 times) | | 401 unauthorized / 403 restricted_resource / 404 object_not_found | No retry. Logs a warning and skips the page | | Other unexpected errors | Logs a warning and skips the page (build continues) |

Environment variables

| Variable | Description | |---|---| | NOTION_TOKEN | Notion Internal Integration Token | | NOTION_DATASOURCE_ID | Notion data source ID |

API Reference

loader(options)

Astro Content Loader. Pass Notion API dataSources.query parameters via queryParameters.

pageWithMarkdownSchema

Base Zod schema returned by the loader. Extends pageObjectResponseSchema with markdown: z.string(). Extend with .extend() for custom schemas.

Property schemas

Use the notroProperties shorthand to define database property types in content.config.ts (see notroProperties).

Individual schemas (e.g. titlePropertyPageObjectResponseSchema) remain exported for backwards compatibility.

Components

| Component | Description | |---|---| | NotroContent | Renders Notion Markdown to HTML. Unstyled by default; pass components to customize | | DatabaseCover | Renders a Notion cover image with optimization | | DatabaseProperty | Renders a Notion property value by type | | compileMdxCached | Low-level MDX compile API. Use when building a custom NotroContent |

notroProperties

Zod schema shorthands for defining property schemas in content.config.ts. Each key maps to a Notion property type.

import { notroProperties } from "notro-loader";

// notroProperties.title       → titlePropertyPageObjectResponseSchema
// notroProperties.richText    → richTextPropertyPageObjectResponseSchema
// notroProperties.checkbox    → checkboxPropertyPageObjectResponseSchema
// notroProperties.multiSelect → multiSelectPropertyPageObjectResponseSchema
// notroProperties.select      → selectPropertyPageObjectResponseSchema
// notroProperties.date        → datePropertyPageObjectResponseSchema
// notroProperties.number      → numberPropertyPageObjectResponseSchema
// notroProperties.url         → urlPropertyPageObjectResponseSchema
// notroProperties.email       → emailPropertyPageObjectResponseSchema
// notroProperties.phoneNumber → phoneNumberPropertyPageObjectResponseSchema
// notroProperties.files       → filesPropertyPageObjectResponseSchema
// notroProperties.people      → peoplePropertyPageObjectResponseSchema
// notroProperties.relation    → relationPropertyPageObjectResponseSchema
// notroProperties.rollup      → rollupPropertyPageObjectResponseSchema
// notroProperties.formula     → formulaPropertyPageObjectResponseSchema
// notroProperties.uniqueId    → uniqueIdPropertyPageObjectResponseSchema
// notroProperties.status      → statusPropertyPageObjectResponseSchema
// notroProperties.createdTime → createdTimePropertyPageObjectResponseSchema
// notroProperties.createdBy   → createdByPropertyPageObjectResponseSchema
// notroProperties.lastEditedTime → lastEditedTimePropertyPageObjectResponseSchema
// notroProperties.lastEditedBy   → lastEditedByPropertyPageObjectResponseSchema
// notroProperties.button      → buttonPropertyPageObjectResponseSchema
// notroProperties.verification → verificationPropertyPageObjectResponseSchema

Utilities

| Function | Description | |---|---| | getPlainText(property) | Extracts plain text from Title, Rich Text, Select, Multi-select, Number, URL, Email, Phone, Date, and Unique ID properties | | getMultiSelect(property) | Returns the options array for a multi-select property. Returns an empty array for unsupported types or undefined — no type guard needed | | hasTag(property, tagName) | Returns whether a multi-select property contains the given tag name. Safe to call without a type guard | | buildLinkToPages(entries, options) | Builds a linkToPages map from collection entries. Pass to NotroContent for resolving inter-page Notion links | | colorToCSS(color) | Converts a Notion color name to an inline CSS style string (for use in custom components) |