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

@porti-esp/ntn-engine

v3.0.1

Published

Notion database engine for querying and managing Notion databases

Readme

@porti-esp/ntn-engine

A TypeScript engine for querying and managing Notion databases via the Notion API.

Installation

npm install @porti/ntn-engine
# or
pnpm add @porti/ntn-engine

Environment Variables

NOTION_TOKEN=your_notion_integration_token

# Required only if you use webhooks
NOTION_WEBHOOK_VERIFICATION_TOKEN=your_webhook_verification_token

To get your NOTION_TOKEN, go to https://www.notion.so/profile/integrations, create an integration, and copy the Internal Integration Secret. Make sure the integration has access to the databases you want to query.

Quick Start

Import the pre-built singleton and call methods directly — no setup required.

import { engine } from "@porti/ntn-engine";

const entries = await engine.getEntries("My Projects");
const entry   = await engine.getEntry("My Projects", "My first project");
const content = await engine.getEntryContent("My Projects", "My first project");
const schema  = await engine.getSchema("My Projects");
const blocks  = await engine.getPageContent("page-id-here");

If you need a custom configuration (e.g. caching), instantiate your own:

import { NotionEngine } from "@porti/ntn-engine";

const myEngine = new NotionEngine({ enabled: true, ttl: 300 });

Write Operations

// Create an entry
await engine.createEntry("My Pages", {
    Name: { title: [{ text: { content: "New page" } }] },
    Status: { select: { name: "In progress" } },
});

// Update an entry by its page title
await engine.updateEntry("My Pages", "New page", {
    Status: { select: { name: "Done" } },
});

// Delete an entry by its page title (moves to trash)
await engine.deleteEntry("My Pages", "New page");

Querying with Filters

getEntries accepts standard Notion query parameters:

const filtered = await engine.getEntries("My Pages", {
    filter: {
        property: "Status",
        select: { equals: "In progress" },
    },
    sorts: [{ property: "Created", direction: "descending" }],
});

Using IDs Directly

Every title-based method has a *ById variant. Use these when you already have the page ID to skip the title lookup:

const pageId = "3d4f1a2b-...";

const entry   = await engine.getEntryById("My Pages", pageId);
const content = await engine.getEntryContentById("My Pages", pageId);

await engine.updateEntryById("My Pages", pageId, { Status: { select: { name: "Done" } } });
await engine.deleteEntryById("My Pages", pageId);
await engine.appendImageBlockById("My Pages", pageId, { data: buffer, filename: "photo.png" });
await engine.setFilePropertyById("My Pages", pageId, "Attachments", [{ data: buffer, filename: "doc.pdf" }]);

Cache

Pass cache options to the constructor to avoid redundant API calls:

const engine = new NotionEngine({ enabled: true, ttl: 300 }); // TTL in seconds, defaults to 60

Cache is invalidated automatically on any write operation (create, update, delete, file upload).

You can also invalidate manually — useful when integrating with external webhook handlers:

// Invalidate a specific datasource (by its Notion ID)
engine.invalidateCache("<datasource-id>");

// Clear the entire cache
engine.invalidateCache();

File Uploads

import fs from "fs";

// Append an image block to a page (by title)
await engine.appendImageBlock("My Pages", "New page", {
    data: fs.readFileSync("./photo.png"),
    filename: "photo.png",
});

// Set a files-type property on an entry (by title)
await engine.setFileProperty("My Pages", "New page", "Attachments", [
    { data: fs.readFileSync("./doc.pdf"), filename: "doc.pdf" },
]);

Webhooks

NotionEngine has built-in webhook handling that verifies the request signature and automatically invalidates the cache for the affected database.

1. Add the env var

NOTION_WEBHOOK_VERIFICATION_TOKEN=your_webhook_verification_token

2. Create a webhook endpoint

// Express example
import express from "express";
import { NotionEngine } from "@porti/ntn-engine";

const app = express();
const engine = new NotionEngine();

app.post("/notion-webhook", express.json(), async (req, res) => {
    try {
        const signature = req.headers["x-notion-signature"] as string;
        await engine.handleWebhook(req.body, signature);
        res.sendStatus(200);
    } catch (err) {
        res.sendStatus(400);
    }
});

3. Register and verify the webhook in Notion

  1. Go to your integration settings → Webhooks tab → create a new webhook pointing to your endpoint.
  2. Notion sends a POST with a verification_token in the body. The engine logs it to the console:
    [NOTION VERIFICATION TOKEN]: abc123...
  3. Copy that token into NOTION_WEBHOOK_VERIFICATION_TOKEN in your .env, and paste it in the Notion integration webhook settings → click Verify.

After verification, every incoming request is validated against the x-notion-signature header. Invalid signatures throw, so wrap handleWebhook in a try/catch and respond with 400 on failure.

Page Content

getPageContent fetches all blocks of a page. By default, nested children are pre-loaded recursively:

// Recursive (default) — all nested children pre-loaded in one call
const blocks = await engine.getPageContent("page-id");

// Non-recursive — only direct children of the given page/block
const directChildren = await engine.getPageContent("page-id", false);

// Also available on NotionFetcher directly:
import { ntn } from "@porti/ntn-engine/fetcher";
const blocks = await ntn.getPageContent("page-id", false);

The same recursive flag is available on getEntryContent and getEntryContentById.

Internal Mappings (NotionFetcher)

After getAllDatasources() has been called (triggered automatically by any engine method), ntn.dsId2DbId exposes the datasource ID → parent database ID mapping:

import { ntn } from "@porti/ntn-engine/fetcher";

await ntn.getAllDatasources(); // populates the map
const databaseId = ntn.dsId2DbId["<datasource-id>"];

This is useful when working with Notion webhooks that send database_id instead of data_source_id, and you need to map back to the datasource.

Framework Environments (Astro, Vite, Next.js, …)

In framework environments that use import.meta.env instead of process.env, instantiate NotionFetcher directly and pass the token explicitly:

import { NotionFetcher } from "@porti/ntn-engine/fetcher";

const fetcher = new NotionFetcher({ notionToken: import.meta.env.NOTION_TOKEN });

The NotionEngine class is framework-safe to import — it does not read environment variables at module load time.

CLI — Generate TypeScript Interfaces

The package ships a CLI tool that generates TypeScript interfaces for all your databases into ./generated/ntn_interfaces.ts.

npx ntn-gen-ifaces

Requires NOTION_TOKEN in the environment. Import the generated types:

import type { NTN_MyProjects } from "./generated/ntn_interfaces";