--- name: notion-core-workflow-a description: 'Query, filter, and manage Notion databases and pages. Use when building database queries with filters and sorts, creating/updating pages with typed properties, or reading page content. Trigger with phrases like "notion database query", "notion filter", "notion create page", "notion update properties", "notion CRUD". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - productivity - notion compatibility: Designed for Claude Code --- # Notion Core Workflow A — Databases & Pages ## Overview Primary workflow for Notion integrations: querying databases with filters/sorts, creating pages with typed properties, updating page properties, and retrieving page content. ## Prerequisites - Completed `notion-install-auth` setup - A Notion database shared with your integration - Understanding of your database's property schema ## Instructions ### Step 1: Retrieve Database Schema ```typescript import { Client } from '@notionhq/client'; const notion = new Client({ auth: process.env.NOTION_TOKEN }); async function getDatabaseSchema(databaseId: string) { const db = await notion.databases.retrieve({ database_id: databaseId }); // db.properties contains the schema for (const [name, prop] of Object.entries(db.properties)) { console.log(`${name}: ${prop.type}`); // For select/multi_select, show options: if (prop.type === 'select') { console.log(' Options:', prop.select.options.map(o => o.name)); } } return db.properties; } ``` ### Step 2: Query with Filters Notion filters use a unique nested structure based on property type: ```typescript async function queryWithFilters(databaseId: string) { const response = await notion.databases.query({ database_id: databaseId, filter: { and: [ { property: 'Status', select: { equals: 'In Progress' }, }, { property: 'Priority', select: { does_not_equal: 'Low' }, }, { or: [ { property: 'Assignee', people: { contains: 'user-uuid-here' }, }, { property: 'Tags', multi_select: { contains: 'Urgent' }, }, ], }, ], }, sorts: [ { property: 'Priority', direction: 'ascending' }, { property: 'Created', direction: 'descending' }, ], page_size: 50, }); return response.results; } ``` ### Step 3: Filter Syntax by Property Type ```typescript // Text (title, rich_text, url, email, phone_number) { property: 'Name', title: { contains: 'search term' } } { property: 'Description', rich_text: { starts_with: 'Draft' } } { property: 'Email', email: { equals: 'user@example.com' } } // Number { property: 'Score', number: { greater_than: 80 } } { property: 'Price', number: { less_than_or_equal_to: 100 } } // Select / Multi-select { property: 'Status', select: { equals: 'Done' } } { property: 'Tags', multi_select: { contains: 'Bug' } } // Date { property: 'Due Date', date: { before: '2026-04-01' } } { property: 'Created', date: { past_week: {} } } { property: 'Updated', date: { on_or_after: '2026-01-01' } } // Checkbox { property: 'Archived', checkbox: { equals: false } } // People { property: 'Assignee', people: { contains: 'user-uuid' } } // Relation { property: 'Project', relation: { contains: 'page-uuid' } } // Formula (filter on the result type) { property: 'Computed', formula: { number: { greater_than: 0 } } } // Rollup (filter on the aggregated result) { property: 'Total', rollup: { number: { greater_than: 100 } } } // Timestamp (no property name needed) { timestamp: 'last_edited_time', last_edited_time: { after: '2026-03-01' } } ``` ### Step 4: Create a Page with All Property Types ```typescript async function createFullPage(databaseId: string) { return notion.pages.create({ parent: { database_id: databaseId }, icon: { emoji: '📋' }, properties: { // Title (required — every database has exactly one) Name: { title: [{ text: { content: 'New Task' } }], }, // Rich text Description: { rich_text: [ { text: { content: 'This is ' } }, { text: { content: 'important' }, annotations: { bold: true, color: 'red' } }, ], }, // Number Score: { number: 95 }, // Select Status: { select: { name: 'In Progress' } }, // Multi-select Tags: { multi_select: [{ name: 'API' }, { name: 'Backend' }], }, // Date (with optional end and timezone) 'Due Date': { date: { start: '2026-04-15', end: '2026-04-20' }, }, // Checkbox Urgent: { checkbox: true }, // URL Link: { url: 'https://developers.notion.com' }, // Email Contact: { email: 'team@example.com' }, // People (array of user objects) Assignee: { people: [{ id: 'user-uuid-here' }], }, // Relation (array of page references) Project: { relation: [{ id: 'related-page-uuid' }], }, }, }); } ``` ### Step 5: Update Page Properties ```typescript async function updatePage(pageId: string) { return notion.pages.update({ page_id: pageId, properties: { Status: { select: { name: 'Done' } }, Score: { number: 100 }, Urgent: { checkbox: false }, }, }); } // Archive (soft delete) a page async function archivePage(pageId: string) { return notion.pages.update({ page_id: pageId, archived: true, }); } ``` ### Step 6: Paginate Through All Results ```typescript async function getAllPages(databaseId: string) { const allPages = []; let cursor: string | undefined = undefined; do { const response = await notion.databases.query({ database_id: databaseId, start_cursor: cursor, page_size: 100, // max is 100 }); allPages.push(...response.results); cursor = response.has_more ? response.next_cursor ?? undefined : undefined; } while (cursor); return allPages; } ``` ## Output - Database schema retrieved with property types and options - Filtered and sorted query results - Pages created with typed properties - Pages updated and archived ## Error Handling | Error | Cause | Solution | |-------|-------|----------| | `validation_error` | Property name mismatch or wrong type | Use `databases.retrieve` to check schema | | `object_not_found` | Database not shared with integration | Add integration via Connections | | `rate_limited` (429) | >3 requests/second average | Respect `Retry-After` header | | Empty `results` | Filter too restrictive or no data | Test with no filter first | ## Examples ### Extract Property Values Helper ```typescript function getPropertyValue(property: any): string | number | boolean | null { switch (property.type) { case 'title': return property.title.map((t: any) => t.plain_text).join(''); case 'rich_text': return property.rich_text.map((t: any) => t.plain_text).join(''); case 'number': return property.number; case 'select': return property.select?.name ?? null; case 'multi_select': return property.multi_select.map((s: any) => s.name).join(', '); case 'date': return property.date?.start ?? null; case 'checkbox': return property.checkbox; case 'url': return property.url; case 'email': return property.email; case 'formula': return property.formula?.[property.formula.type] ?? null; default: return null; } } ``` ## Resources - [Query a Database](https://developers.notion.com/reference/post-database-query) - [Filter Database Entries](https://developers.notion.com/reference/post-database-query-filter) - [Create a Page](https://developers.notion.com/reference/post-page) - [Page Property Values](https://developers.notion.com/reference/page-property-values) - [Database Object](https://developers.notion.com/reference/database) ## Next Steps For block-level content operations, see `notion-core-workflow-b`.