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

@sug1t0m0/notion-typed-client

v6.0.0

Published

Type-safe wrapper for Notion SDK with database schema injection

Downloads

1,036

Readme

notion-typed-client

npm version npm downloads Test and Lint Auto Release License: MIT TypeScript Node.js

Type-safe code generation tool for Notion API clients

⚠️ Early Stage Notice: E2E testing revealed that this library still has bugs. Thank you to everyone who has downloaded and tried it - your interest means a lot! My sincere apologies for any issues you've encountered. I'm committed to improving this library until it's production-ready for my own use cases and yours.

Overview

notion-typed-client solves the type-safety issues of generic Notion API clients. It fetches database schemas from Notion and automatically generates TypeScript type definitions and type-safe API clients with validation capabilities.

Key Features

  • 🔒 Complete Type Safety - Represents actual options for select/multi_select/status properties as types
  • 🔄 Automatic Schema Sync - Fetches latest schema information from Notion API and generates types
  • Runtime Validation - Runtime value validation with AJV
  • 🎯 Workflow-Specific - Generates clients optimized for specific database structures
  • 🔧 Auto-Configuration Updates - Automatic ID resolution and notionName change detection
  • 🔌 Dependency Injection - Inject custom Notion client implementations for testing and customization

Installation

npm install -D @sug1t0m0/notion-typed-client
# or
pnpm add -D @sug1t0m0/notion-typed-client
# or
yarn add -D @sug1t0m0/notion-typed-client

Quick Start

1. Initial Setup

npx notion-typed-client init

Set your Notion API key as an environment variable or in your project's .env file:

NOTION_API_KEY=your_notion_api_key_here

Note: For examples and testing, see the /example and /e2e directories which have their own .env.example files.

2. Create Configuration File

notion-typed.config.ts:

import { NotionTypedConfig } from 'notion-typed-client';

const config: NotionTypedConfig = {
  databases: [
    {
      id: null,  // OK to be null initially, will be auto-resolved later
      name: 'TaskDatabase',
      displayName: 'Task Database',
      notionName: 'Tasks',
      properties: [
        {
          id: null,
          name: 'title',
          displayName: 'Title',
          notionName: 'Title',
          type: 'title'
        },
        {
          id: null,
          name: 'status',
          displayName: 'Status',
          notionName: 'Status',
          type: 'status'
          // options will be fetched automatically
        },
        {
          id: null,
          name: 'priority',
          displayName: 'Priority',
          notionName: 'Priority',
          type: 'select'
          // options will be fetched automatically
        },
        {
          id: null,
          name: 'tags',
          displayName: 'Tags',
          notionName: 'Tags',
          type: 'multi_select'
          // options will be fetched automatically
        },
        {
          id: null,
          name: 'customField',
          displayName: 'Custom Field',
          notionName: 'Custom Field',
          type: null  // type will also be auto-detected from Notion
        }
      ]
    }
  ],
  output: {
    path: './src/generated',
    clientName: 'NotionClient'
  }
};

export default config;

3. Fetch Schema and Generate Client

# Fetch schema, resolve IDs, and generate types in one command
npx notion-typed-client build

# Or run individually
npx notion-typed-client fetch    # Fetch schema and resolve IDs
npx notion-typed-client generate  # Generate types and client

4. Use the Generated Client

Basic Usage

import { NotionTypedClient } from './generated/client';
import { TaskDatabase } from './generated/types';
import { Client } from '@notionhq/client';

// Create official Notion client
const notionClient = new Client({
  auth: process.env.NOTION_API_KEY!
});

// Use with generated typed client
const client = new NotionTypedClient({
  client: notionClient
});

// Type-safe page creation
const newTask = await client.createPage('TaskDatabase', {
  title: 'New Task',           // string type
  status: 'In Progress',        // Only 'Not Started' | 'In Progress' | 'Done' allowed
  priority: 'High',             // Only 'Low' | 'Medium' | 'High' allowed
  tags: ['urgent', 'review']    // Only defined options allowed
});

// Type-safe query with filter support
const tasks = await client.queryDatabase('TaskDatabase', {
  filter: {
    property: 'status',
    status: {
      equals: 'In Progress'  // Type-safe: only valid status options allowed
    }
  }
});

// Compound filters (and/or) are also type-safe
const filteredTasks = await client.queryDatabase('TaskDatabase', {
  filter: {
    and: [
      { property: 'status', status: { equals: 'In Progress' } },
      { property: 'priority', select: { equals: 'High' } }
    ]
  }
});

// Fetch all pages from a database (pagination handled automatically)
const allTasks = await client.queryDatabaseAll('TaskDatabase', {
  filter: {
    property: 'status',
    status: { does_not_equal: 'Archived' }
  },
  sorts: [
    { property: 'priority', direction: 'descending' },  // Code names are auto-converted
    { property: 'dueDate', direction: 'ascending' }     // to Notion property names
  ]
});
console.log(`Total tasks: ${allTasks.length}`);

// Memory-efficient iteration through large datasets
for await (const task of client.queryDatabaseIterator('TaskDatabase', {
  filter: { property: 'status', status: { equals: 'Pending' } },
  page_size: 50  // Process in smaller chunks
})) {
  // Process each task individually
  await processTask(task);
  
  // Can break early if needed
  if (shouldStop(task)) {
    break;
  }
}

// Page update with validation
const updated = await client.updatePage('page-id', 'TaskDatabase', {
  status: 'Done'  // Also validated at runtime
});

Dependency Injection (Advanced)

You can inject your own Notion client implementation for testing or customization:

import { NotionTypedClient } from './generated/client';
import type { NotionClientInterface } from '@sug1t0m0/notion-typed-client';

// Mock client for testing
const mockClient: NotionClientInterface = {
  databases: {
    retrieve: jest.fn(),
    query: jest.fn(),
  },
  pages: {
    create: jest.fn(),
    retrieve: jest.fn(),
    update: jest.fn(),
  },
  search: jest.fn(),
};

// Use injected client
const client = new NotionTypedClient({
  client: mockClient
});

// Your custom client with logging, retry logic, etc.
class CustomNotionClient implements NotionClientInterface {
  private baseClient: Client;
  
  constructor(options: { auth: string }) {
    this.baseClient = new Client(options);
  }
  
  databases = {
    retrieve: async (args) => {
      console.log('Retrieving database:', args.database_id);
      return this.baseClient.databases.retrieve(args);
    },
    query: async (args) => {
      console.log('Querying database:', args.database_id);
      return this.baseClient.databases.query(args);
    },
  };
  
  pages = {
    create: async (args) => {
      console.log('Creating page');
      return this.baseClient.pages.create(args);
    },
    retrieve: async (args) => {
      console.log('Retrieving page:', args.page_id);
      return this.baseClient.pages.retrieve(args);
    },
    update: async (args) => {
      console.log('Updating page:', args.page_id);
      return this.baseClient.pages.update(args);
    },
  };
  
  search = async (args) => {
    console.log('Searching:', args.query);
    return this.baseClient.search(args);
  };
}

const customClient = new CustomNotionClient({
  auth: process.env.NOTION_API_KEY!
});

const client = new NotionTypedClient({
  client: customClient
});

Pagination Methods

The generated client includes three methods for querying databases, each optimized for different use cases:

queryDatabase - Standard pagination

Returns a single page of results with pagination metadata. Ideal for UI pagination and controlled data fetching.

const page = await client.queryDatabase('TaskDatabase', {
  filter: { property: 'status', status: { equals: 'Active' } },
  sorts: [{ property: 'created_time', direction: 'descending' }],
  page_size: 20,
  start_cursor: previousCursor  // For fetching next page
});
// page.results: Array of up to 20 items
// page.has_more: boolean indicating if more pages exist
// page.next_cursor: cursor for fetching the next page

queryDatabaseAll - Fetch all records

Automatically fetches all pages and returns them as a single array. Best for data analysis and small to medium datasets.

const allTasks = await client.queryDatabaseAll('TaskDatabase', {
  filter: { property: 'status', status: { equals: 'Active' } },
  sorts: [{ property: 'created_time', direction: 'descending' }],
  page_size: 100  // Optional: controls internal pagination size
});
// allTasks: Array containing ALL matching records
// ⚠️ Use with caution for large datasets (memory usage)

queryDatabaseIterator - Memory-efficient iteration

Returns an async iterator that fetches pages on-demand. Perfect for processing large datasets without loading everything into memory.

// Process millions of records with minimal memory usage
for await (const task of client.queryDatabaseIterator('TaskDatabase', {
  filter: { property: 'archived', checkbox: { equals: false } },
  page_size: 50
})) {
  await sendEmailNotification(task);
  
  // Can exit early if needed
  if (await shouldStopProcessing()) {
    break;
  }
}

When to use each method:

  • queryDatabase: UI pagination, manual cursor control, specific page fetching
  • queryDatabaseAll: Reports, data analysis, small datasets (< 1000 items)
  • queryDatabaseIterator: Batch processing, ETL, large datasets, memory-constrained environments

CLI Commands

init

Creates configuration file template and necessary files.

npx notion-typed-client init [options]

Options:
  --config <path>  Configuration file path (default: "./notion-typed.config.ts")
  --force         Overwrite existing files

fetch

Fetches schema information from Notion API, resolves IDs, and updates configuration file.

npx notion-typed-client fetch [options]

Options:
  --config <path>  Configuration file path
  --dry-run       Preview changes without applying them

generate

Generates type definitions and client code from fetched schema.

npx notion-typed-client generate [options]

Options:
  --config <path>  Configuration file path
  --watch         Watch for file changes and auto-generate

build

Runs fetch and generate sequentially.

npx notion-typed-client build [options]

validate

Validates consistency between configuration and actual Notion schema.

npx notion-typed-client validate [options]

Configuration File Details

DatabaseConfig

| Property | Type | Description | |-----------|-----|------| | id | string \| null | Database ID (searches by notionName if null) | | name | string | Type name in TypeScript | | displayName | string | Display name for logs | | notionName | string | Actual name in Notion | | properties | PropertyConfig[] | Array of property configurations |

PropertyConfig

| Property | Type | Description | |-----------|-----|------| | id | string \| null | Property ID (searches by notionName if null) | | name | string | Property name in TypeScript | | displayName | string | Display name for logs | | notionName | string | Actual property name in Notion | | type | NotionPropertyType \| null | Property type (auto-detected from Notion if null) |

Supported Property Types

  • title - Title
  • rich_text - Rich Text
  • number - Number
  • select - Single Select (options auto-fetched)
  • multi_select - Multi Select (options auto-fetched)
  • status - Status (options and groups auto-fetched)
  • date - Date
  • people - People
  • files - Files
  • checkbox - Checkbox
  • url - URL
  • email - Email
  • phone_number - Phone Number
  • formula - Formula
  • relation - Relation
  • rollup - Rollup
  • created_time - Created Time
  • created_by - Created By
  • last_edited_time - Last Edited Time
  • last_edited_by - Last Edited By

Auto-Update Features

ID Resolution

On first run, entries with id: null are automatically resolved using notionName and the configuration file is updated.

Name Change Detection

When IDs already exist, detects name changes in Notion and prompts to update notionName in the configuration file.

Auto-Fetching Options

Options for select/multi_select/status properties are automatically fetched from Notion API and reflected in type definitions. No manual management needed.

Generated Files

By default, the following files are generated in the ./notion-typed-codegen/ directory:

  • types.ts - TypeScript type definitions
  • client.ts - Type-safe Notion client
  • validators.ts - Runtime validators
  • schemas.json - JSON Schema definitions

Frequently Asked Questions (FAQ)

Q: Why do I need this tool?

A: The official Notion SDK is generic and doesn't provide database-specific type information. This tool generates type-safe clients based on your database structure, significantly improving the development experience.

Q: What happens when options are changed?

A: Running npx notion-typed-client build fetches the latest schema and updates type definitions. Added, removed, or changed options are automatically reflected.

Q: Can I manage multiple databases?

A: Yes, you can add multiple database configurations to the databases array in notion-typed.config.ts.

Q: Can I customize the generated code?

A: Generated code is overwritten, so don't edit it directly. If customization is needed, create your own class that wraps the generated client.

Best Practices

1. Environment Variable Management

# .env.local (development)
NOTION_API_KEY=secret_development_key

# .env.production (production)
NOTION_API_KEY=secret_production_key

2. CI/CD Usage

# GitHub Actions example
- name: Generate Notion types
  env:
    NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
  run: |
    npx notion-typed-client build
    npx tsc --noEmit

3. Type Reuse

// Use generated types in other files
import type { PlansDatabase } from './notion-typed-codegen/types';

function processPlans(plans: PlansDatabase[]): void {
  // Type-safe processing
}

Troubleshooting

Invalid API Key

Verify that the correct Notion API key is set in your environment variables or project's .env file.

Database Not Found

  • Check that your Notion API key has access permissions to the database
  • Verify that notionName matches exactly (case-sensitive)

Type Errors

Run npx notion-typed-client build to regenerate types with the latest schema.

Release Management

This project follows Semantic Versioning.

Automatic Releases

  • feat: prefix commits trigger minor version bumps
  • fix: prefix commits trigger patch version bumps
  • BREAKING CHANGE: or commits with ! trigger major version bumps

Manual Releases

Run the "Release" workflow from the GitHub Actions tab to select version type.

Version Management

  • MAJOR (x.0.0): Breaking changes
  • MINOR (1.x.0): New features (backward compatible)
  • PATCH (1.1.x): Bug fixes

Conventional Commits

Commit messages should follow this format:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Examples:

feat(cli): add new init command
fix: resolve type generation bug
docs: update README with examples
BREAKING CHANGE: remove deprecated API

To use the configured commit message template:

git config commit.template .gitmessage

License

MIT

Contributing

Issues and Pull Requests are welcome!

Links