A TypeScript ORM for Notion databases. Query, create, update, and delete records with type safety—no more wrestling with deeply nested API responses.
https://interactive-inc.github.io/open-notion-client/
Working directly with the Notion API is painful. A simple text value comes buried in layers of nested objects:
{
"properties": {
"Name": {
"type": "title",
"title": [{
"text": { "content": "Hello World" },
"annotations": { "bold": false, "italic": false }
}]
}
}
}notion-client flattens this to { name: "Hello World" } and gives you a clean, familiar ORM interface.
- Type-safe schema definitions with full TypeScript inference
- Intuitive CRUD operations that feel like Prisma or Drizzle
- Powerful query builder with filters, sorting, and pagination
- Automatic conversion between Notion's complex JSON and simple values
- Markdown support for page content with bidirectional conversion
- Built-in memory cache to reduce API calls
- Support for all Notion property types
bun add @interactive-inc/notion-clientimport { Client } from "@notionhq/client"
import { NotionTable } from "@interactive-inc/notion-client"
const client = new Client({ auth: process.env.NOTION_TOKEN })
const tasks = new NotionTable({
client,
dataSourceId: "your-database-id",
properties: {
title: { type: "title" },
status: { type: "select", options: ["todo", "doing", "done"] },
priority: { type: "number" },
tags: { type: "multi_select", options: ["bug", "feature"] },
} as const,
})const task = await tasks.create({
properties: {
title: "Implement new feature",
status: "todo",
priority: 1,
tags: ["feature"],
},
})// Find multiple records
const allTasks = await tasks.findMany({
where: { status: "doing" },
sorts: [{ field: "priority", direction: "desc" }],
count: 10,
})
// Find one record
const task = await tasks.findOne({
where: { title: "Implement new feature" },
})
// Find by ID
const task = await tasks.findById("page-id")// Update one
await tasks.update(task.id, {
properties: { status: "done" },
})
// Update many
await tasks.updateMany({
where: { status: "todo" },
update: { properties: { status: "doing" } },
})// Archive one
await tasks.delete(task.id)
// Archive many
await tasks.deleteMany({ status: "done" })
// Restore archived
await tasks.restore(task.id)await tasks.upsert({
where: { title: "Daily standup" },
create: {
properties: { title: "Daily standup", status: "todo" },
},
update: {
properties: { status: "todo" },
},
})const results = await tasks.findMany({
where: {
priority: { greater_than_or_equal_to: 5 },
status: { does_not_equal: "done" },
tags: { contains: "bug" },
},
})Supported operators: equals, does_not_equal, contains, does_not_contain, greater_than, less_than, greater_than_or_equal_to, less_than_or_equal_to, before, after, is_empty, is_not_empty
const results = await tasks.findMany({
where: {
or: [
{ status: "todo" },
{
and: [
{ priority: { greater_than_or_equal_to: 5 } },
{ tags: { contains: "urgent" } },
],
},
],
},
})| Type | Description | Example Value |
|---|---|---|
title |
Page title (required) | "Task name" |
rich_text |
Text content | "Description" |
number |
Numeric value | 42 |
select |
Single select | "option1" |
multi_select |
Multiple select | ["tag1", "tag2"] |
status |
Status field | "In Progress" |
checkbox |
Boolean | true |
url |
URL string | "https://..." |
email |
Email address | "user@example.com" |
phone_number |
Phone number | "555-123-4567" |
date |
Date value | { start: "2024-01-01" } |
files |
File attachments | [{ url: "..." }] |
people |
User references | [{ id: "user-id" }] |
relation |
Relations | ["page-id"] |
formula |
Computed value | Read-only |
created_time |
Creation timestamp | Read-only |
last_edited_time |
Last edit timestamp | Read-only |
Create pages with Markdown content that automatically converts to Notion blocks:
const post = await posts.create({
properties: { title: "Blog Post" },
body: `
# Introduction
This paragraph has **bold** and *italic* text.
## Features
- Feature A
- Feature B
\`\`\`typescript
const hello = "world"
\`\`\`
`,
})import { NotionMarkdown } from "@interactive-inc/notion-client"
const markdown = new NotionMarkdown({
heading_1: "heading_2", // Convert H1 to H2
heading_2: "heading_3", // Convert H2 to H3
})
const posts = new NotionTable({
client,
dataSourceId: "database-id",
properties: { title: { type: "title" } } as const,
markdown,
})Reduce API calls with the built-in memory cache:
import { NotionMemoryCache } from "@interactive-inc/notion-client"
const cache = new NotionMemoryCache()
const tasks = new NotionTable({
client,
dataSourceId: "database-id",
properties: { title: { type: "title" } } as const,
cache,
})
// Fetch with caching enabled
const task = await tasks.findById("page-id", { cache: true })
// Clear cache when needed
tasks.clearCache()MIT