Notion ORMGitHub

API Reference

Complete reference for the Notion ORM API. The sections follow a learning progression: start with runtime access, then databases, then agents, then advanced usage.

Runtime access

Example
import NotionORM from "@haustle/notion-orm";

const notion = new NotionORM({
  auth: process.env.NOTION_KEY!,
});

// DatabaseClient
const db = notion.databases.yourDatabaseName;
// AgentClient
const agent = notion.agents.yourAgentName;
notion.databasesRecord<string, DatabaseClient>
Generated database client map keyed by camelCase database name
notion.agentsRecord<string, AgentClient>
Generated agent client map keyed by camelCase agent name

Database client

Properties

id
Notion data source ID used by this client instance
name
Human-readable database name captured during generation

Methods

findUnique({ where: { id } })
Retrieves a single page by its Notion ID
findFirst(args?)
Returns the first matching row or null
findMany(args?)
Query rows. Returns Partial<Schema>[] by default, PaginateResult with after, or AsyncIterable with stream
count(args?)
Returns the total number of matching rows
create({ properties, icon?, cover?, markdown? })
Creates a page with typed properties and optional markdown body content
createMany({ properties })
Creates multiple pages sequentially
update({ where: { id }, properties })
Updates a single page by ID
updateMany({ where, properties })
Updates all pages matching a filter
upsert({ where, create, update })
Creates or updates depending on whether a match exists
delete({ where: { id } })
Archives a single page by ID
deleteMany({ where })
Archives all pages matching a filter

findUnique

Retrieves a single page by its Notion page ID. Returns null for missing or partial pages. Supports select / omit projection with the same semantics as findMany.

Example
const page = await db.findUnique({
  where: { id: "page-id-here" },
  select: ["bookName", "status"],
});

findFirst

Returns the first row matching the filter, or null if none match.

Example
const book = await db.findFirst({
  where: { bookName: { equals: "Creativity, Inc." } },
});

Args: Same as findMany but without stream, after, or size.


findMany

Queries the database with optional filtering, sorting, projection, pagination, and streaming.

Overloads:

No stream or after
Promise<Partial<Schema>[]>
after: string | null
Promise<PaginateResult<Schema>>
stream: number
AsyncIterable<Partial<Schema>>

Args:

whereQueryFilter
no
sortByQuerySort<ColumnTypes>
no
sizenumber
no
selectreadonly (keyof Schema & string)[]
no
omitreadonly (keyof Schema & string)[]
no
streamnumber
no
afterstring | null
no

select and omit are mutually exclusive — providing both throws at runtime.

Example
// Simple query — returns all rows
const allBooks = await db.findMany();

// Filtered + sorted + projected
const recent = await db.findMany({
  where: { publishDate: { on_or_after: "2025-01-01" } },
  sortBy: [{ property: "publishDate", direction: "descending" }],
  select: ["bookName", "publishDate"],
  size: 20,
});

// Cursor pagination
const firstPage = await db.findMany({ after: null, size: 10 });
const secondPage = await db.findMany({ after: firstPage.nextCursor, size: 10 });
// firstPage.data, firstPage.hasMore, firstPage.nextCursor

// Streaming large datasets
for await (const row of db.findMany({ stream: 100 })) {
  console.log(row.bookName);
}

PaginateResult

Returned by findMany when after is provided.

dataPartial<Schema>[]
Current page of results
nextCursorstring | null
Pass to after for the next page, null when done
hasMoreboolean
Whether more pages remain

Filtering

Filters are typed by your generated schema. Single-property filters and compound and/or are supported.

Single filter:

Example
await db.findMany({
  where: { genre: { contains: "Sci-Fi" } },
});

Compound filters:

Example
await db.findMany({
  where: {
    and: [
      { genre: { contains: "Sci-Fi" } },
      { numberOfPages: { greater_than: 200 } },
      {
        or: [
          { status: { equals: "In progress" } },
          { status: { equals: "Not started" } },
        ],
      },
    ],
  },
});

Filter operators vary by property type — see the Notion filter reference for the full list.


count

Returns the total number of rows matching an optional filter. Paginates internally to count all results.

Example
const total = await db.count();
const filtered = await db.count({
  where: { status: { equals: "Done" } },
});

create

Creates a single page. Properties are typed to your generated schema.

Example
const result = await db.create({
  properties: {
    bookName: "The Dream Machine",
    genre: ["Non-fiction"],
    numberOfPages: 460,
  },
  icon: { type: "emoji", emoji: "📗" },
});
// result.id — the new page ID

Pass markdown to add body content to the page in a single call. This uses Notion's enhanced markdown format — headings, lists, code blocks, quotes, checklists, and more are all supported.

Example
await db.create({
  properties: {
    bookName: "Reading Notes",
  },
  markdown: "# Key Takeaways\n\n- **Creativity requires candor** — honest feedback loops matter\n- Protect the new — early ideas are fragile\n\n> \"Quality is the best business plan.\"",
});

markdown is mutually exclusive with children / content.


createMany

Creates multiple pages sequentially and returns all responses.

Example
const results = await db.createMany({
  properties: [
    { bookName: "Book A", genre: ["Sci-Fi"], numberOfPages: 300 },
    { bookName: "Book B", genre: ["Biography"], numberOfPages: 250 },
  ],
});

update

Updates a single page by ID. Requires at least one property.

Example
await db.update({
  where: { id: "page-id" },
  properties: { status: "Done", numberOfPages: 512 },
});

updateMany

Finds all pages matching a filter and applies the same property updates to each.

Example
await db.updateMany({
  where: { status: { equals: "Draft" } },
  properties: { status: "In progress" },
});

upsert

Finds the first row matching where. If found, applies update; otherwise runs create.

Example
await db.upsert({
  where: { bookName: { equals: "The Dream Machine" } },
  create: {
    bookName: "The Dream Machine",
    genre: ["Non-fiction"],
    numberOfPages: 460,
  },
  update: { numberOfPages: 500 },
});

delete

Archives a single page by ID (Notion does not hard-delete pages).

Example
await db.delete({ where: { id: "page-id" } });

deleteMany

Archives all pages matching a filter.

Example
await db.deleteMany({
  where: { status: { equals: "Archived" } },
});

Supported database properties

titlestring
"The Dream Machine"
rich_textstring
"Long-form notes from the page"
numbernumber
460
date{ start: string; end: string }
{ start: "2026-03-01", end: "2026-03-02" }
statusstring
"In progress"
selectstring
"Non-fiction"
multi_selectstring[]
["Sci-Fi", "Biography"]
checkboxboolean
true
emailstring
"tyrus@haustle.studio"
phone_numberstring
"0000000000"
urlstring
"https://developers.notion.com/"
filesArray<{ name: string; url: string }>
[{ name: "brief.pdf", url: "https://..." }]
peoplestring[]
["1f4e6f4a-5b58-4d91-a7fc-2f5f2a0f6bb1"]
relationstring[]
["6f7f9cbf-8d45-48f8-a194-661e73f7f5d9"]
created_bystring
"Ada Lovelace"
last_edited_bystring
"user_123"
created_timestring
"2026-03-01T10:30:00.000Z"
last_edited_timestring
"2026-03-01T13:15:00.000Z"
unique_idstring
"TASK-42"

Unsupported properties

These property types are intentionally excluded from the generated schema and client surface:

formula
Formula values are computed by Notion at read time, and the result shape depends on the expression plus the current value type. Because that contract is not stable enough for generated schema types, formula properties are skipped entirely instead of being exposed as readable-but-limited fields.
rollup
Rollup values are polymorphic and require additional normalization before we can provide safe, predictable filter contracts.

Formula properties are therefore omitted from generated database modules and are unavailable in typed reads, projections, or filters.

Agent client

Properties

id
Notion agent ID used by this client instance
name
Human-readable agent name
icon
Normalized agent icon metadata (or null)

Methods

chat({ message, threadId? })Sends a message and creates/resumes a thread
await agent.chat({ message: "Hello" })
chatStream({ message, threadId?, onMessage? })Streams messages and returns final ThreadInfo
await agent.chatStream({ message: "Hi", onMessage: (m) => {} })
pollThread(threadId, options?)Polls until thread processing completes
await agent.pollThread(threadId)
getMessages(threadId, { role? })Gets full (or role-filtered) message history
await agent.getMessages(threadId, { role: "agent" })
AgentClient.getAgentResponse(threadInfo)Joins every agent message in ThreadInfo into one string
AgentClient.getAgentResponse(thread)
listThreads()Lists recent threads with id, title, and status
await agent.listThreads()
getThreadInfo(threadId)Fetches a single thread record
await agent.getThreadInfo(threadId)
getThreadTitle(threadId)Convenience helper to fetch just the thread title
await agent.getThreadTitle(threadId)

chat

Sends a user message. Omit threadId to start a new thread; pass a prior threadId to continue. The response includes the thread id and status — it does not embed the assistant’s reply text; call getMessages or use chatStream when you need message bodies.

Example
const first = await notion.agents.helpBot.chat({
  message: "Give me a high-protein dinner idea under 30 minutes.",
});
// first.threadId, first.status, first.isNewChat

const followUp = await notion.agents.helpBot.chat({
  threadId: first.threadId,
  message: "Now make it vegetarian.",
});

chat return shape:

threadIdstring
Thread to pass to chat, getMessages, etc.
statusThreadStatus
Processing state from the Agents API
isNewChatboolean
true when threadId was omitted in the request

chatStream

Streams message chunks via onMessage and resolves to ThreadInfo with the full transcript when the stream completes.

Example
import { AgentClient } from "@haustle/notion-orm";

const thread = await notion.agents.helpBot.chatStream({
  message: "How do I reset my password?",
  onMessage: (msg) => {
    if (msg.role === "agent") process.stdout.write(msg.content);
  },
});

const plainText = AgentClient.getAgentResponse(thread);

Resume an existing thread by passing threadId:

Example
const continued = await notion.agents.helpBot.chatStream({
  threadId: thread.threadId,
  message: "Shorter version, bullet points only.",
  onMessage: (msg) => {
    /* … */
  },
});

ThreadInfo

Returned by chatStream. Use messages for the transcript, or AgentClient.getAgentResponse for a single concatenated agent reply.

threadIdstring
Stable thread identifier used to continue a conversation
agentIdstring
Agent identifier that produced the response
messagesArray<{ role: "user" | "agent"; content: string }>
Full message history available after the stream

Message items:

roleuser | agent
Message author
contentstring
Plain text content

AgentClient.getAgentResponse

Static helper: joins every agent message in a ThreadInfo into a single string (useful with chatStream results).

Example
import { AgentClient } from "@haustle/notion-orm";

const thread = await notion.agents.helpBot.chatStream({
  message: "One paragraph about our refund policy.",
});
const answer = AgentClient.getAgentResponse(thread);

pollThread

Waits until the thread finishes processing (configurable backoff). Often used after chat before reading messages.

Example
const chat = await notion.agents.helpBot.chat({
  message: "Summarize yesterday’s standup notes.",
});
await notion.agents.helpBot.pollThread(chat.threadId);

// Optional: tune polling (defaults: maxAttempts 60, baseDelayMs 1000, …)
await notion.agents.helpBot.pollThread(chat.threadId, {
  maxAttempts: 30,
  initialDelayMs: 500,
});

getMessages

Loads the full history for a thread. Pass role to return only user or only agent messages.

Example
await notion.agents.helpBot.pollThread(threadId);

const all = await notion.agents.helpBot.getMessages(threadId);
const agentOnly = await notion.agents.helpBot.getMessages(threadId, {
  role: "agent",
});

listThreads

Returns recent threads for this agent, each with id, title, and status.

Example
const threads = await notion.agents.helpBot.listThreads();
for (const t of threads) {
  console.log(t.id, t.title, t.status);
}

getThreadInfo

Fetches a single thread by ID. Returns the Agents SDK ThreadListItem (includes id, title, status, and related fields).

Example
const info = await notion.agents.helpBot.getThreadInfo("thread-id-here");
// info.id, info.title, info.status, …

getThreadTitle

Shortcut when you only need the title string.

Example
const title = await notion.agents.helpBot.getThreadTitle("thread-id-here");

Generated exports

For script-level usage without the NotionORM wrapper:

@haustle/notion-orm/build/db/<databaseName>
<databaseName>(auth) factory, DatabaseSchemaType, QuerySchemaType, generated Zod schema, generated option tuples (for select/status/multi-select), schema/type aliases
@haustle/notion-orm/build/agents/<agentName>
<agentName>(auth) factory that returns an AgentClient
@haustle/notion-orm/build/db
databases barrel object (all database factories)
@haustle/notion-orm/build/agents
agents barrel object (all agent factories)