database/query
Query the pages (rows) of a Notion database with optional filter and sort conditions. Supports full pagination via ReturnAll or cursor-based paging. This is the primary operation for reading structured data from Notion databases.
When to Use
- Sprint report generation: Query all tasks with
Status = "In Progress"to build a daily or weekly sprint status report. Combine with a sort onPrioritydescending to show the most urgent items first. - Daily digest of recent changes: Filter pages by
last_edited_timewithin the past 24 hours to detect all records updated since yesterday. Use this to drive a digest notification to Slack or email. - Personal task dashboard: Query tasks where the
Assigneeproperty equals the current user's Notion user ID. Return all assigned tasks sorted by due date for a personal work dashboard. - Paginated bulk export: Set
ReturnAll: trueto retrieve all rows in a large database, then transform each row for export to a CSV, external database, or analytics system. - Period reporting: Filter by a date range on a
DueorCreatedproperty to produce period-specific reports — for example, all records created in May 2024.
Configuration
Authentication
| Field | Required | Description |
|---|---|---|
AuthenticationMethod | Required | "ApiToken" or "OAuth2". |
ApiToken | Required | Notion Internal Integration Token starting with secret_. |
ClientId | OAuth2 | OAuth2 client ID. |
ClientSecret | OAuth2 | OAuth2 client secret. |
AccessToken | OAuth2 | OAuth2 access token. |
Operation Fields
| Field | Required | Description |
|---|---|---|
Database | Required | Target Notion database UUID. |
Filter | Optional | Notion filter object. Omit to return all pages. See filter syntax below. |
Sorts | Optional | Array of Notion sort objects. Example: [{"property":"Due","direction":"ascending"}]. |
ReturnAll | Optional | When true, automatically pages through all results. Default: false. |
Limit | Optional | Max results per call (1–100). Used when ReturnAll is false. Default: 100. |
StartCursor | Optional | Pagination cursor from a previous call's next_cursor. |
Sample Configuration
{
"resource": "database",
"operation": "query",
"AuthenticationMethod": "ApiToken",
"ApiToken": "{{ $credentials.notionApiToken }}",
"Database": "{{ $vars.sprintDatabaseId }}",
"Filter": {
"property": "Status",
"select": { "equals": "In Progress" }
},
"Sorts": [
{ "property": "Priority", "direction": "descending" }
],
"ReturnAll": false,
"Limit": 50
}
Compound filters: Combine multiple conditions using
and or or at the top level. Example: {"and":[{"property":"Status","select":{"equals":"In Progress"}},{"property":"Assignee","people":{"contains":"user-uuid"}}]}
Validation Errors
| Error Code | Cause |
|---|---|
NOTION_VALIDATION_FAILED | Database field is empty or not a valid UUID. |
NOTION_INVALID_REQUEST | Filter or sort object is malformed — check property names match the database schema exactly (case-sensitive). |
NOTION_CONFLICT | Filter references a property type that does not exist in the database (e.g., filtering as select on a text property). |
NOTION_PERMISSION_DENIED | Integration not shared with this database. |
NOTION_NOT_FOUND | Database UUID does not exist. |
NOTION_RATE_LIMITED | Rate limit exceeded. ReturnAll on large databases can trigger this — use Limit + cursor loop with a Delay node. |
Output
Success Port
| Field | Type | Description |
|---|---|---|
results | array | Array of page objects. Each page contains id, url, created_time, last_edited_time, archived, parent, and a properties object with all property values. |
has_more | boolean | Whether additional results exist past the current page. |
next_cursor | string | Cursor for next call when has_more is true. Null otherwise. |
object | string | Always "list". |
Error Port
On failure the error port receives errorCode, message, and httpStatus. Handle NOTION_INVALID_REQUEST by logging the filter and database ID for debugging.
Sample Output
{
"object": "list",
"results": [
{
"id": "e5f6a7b8-c9d0-1234-efab-345678901234",
"url": "https://www.notion.so/e5f6a7b8c9d01234efab345678901234",
"created_time": "2024-06-01T08:00:00.000Z",
"last_edited_time": "2024-06-10T14:00:00.000Z",
"archived": false,
"properties": {
"Name": {
"type": "title",
"title": [{ "plain_text": "Build login page" }]
},
"Status": {
"type": "select",
"select": { "name": "In Progress" }
},
"Assignee": {
"type": "people",
"people": [{ "name": "Sarah Chen", "id": "user-uuid-1" }]
},
"Due": {
"type": "date",
"date": { "start": "2024-06-15" }
}
}
}
],
"has_more": false,
"next_cursor": null
}
Expression Reference
| Expression | Type | Use |
|---|---|---|
{{ $output.queryDatabase.results.length }} | number | Count matching pages |
{{ $output.queryDatabase.results[0].id }} | string | First page UUID — pass to page/update |
{{ $output.queryDatabase.results[0].properties.Name.title[0].plain_text }} | string | Extract page title text |
{{ $output.queryDatabase.results[0].properties.Status.select.name }} | string | Read select property value |
{{ $output.queryDatabase.results[0].properties.Due.date.start }} | string | Read date property value |
{{ $output.queryDatabase.next_cursor }} | string | Pass to next call for manual pagination |
Node Policies & GuardRails
| Policy | Detail |
|---|---|
| Property names are case-sensitive | Filter and sort property names must exactly match the database schema (e.g., "Status" not "status"). A mismatch causes NOTION_INVALID_REQUEST. Verify names with database/get first. |
| Pagination for large databases | Databases with thousands of rows should use cursor-based manual pagination instead of ReturnAll to avoid rate limiting. Store the cursor in a workflow variable between runs. |
| Filter by last_edited_time for sync | For incremental sync patterns, filter by last_edited_time using a timestamp filter: {"timestamp":"last_edited_time","last_edited_time":{"after":"2024-06-01T00:00:00Z"}}. |
| Archived pages excluded by default | The query API excludes archived pages by default. To include them, add a specific filter: {"property":"...","archived":true} — however this is rarely needed in practice. |
| Credentials in Credentials Manager | Always use {{ $credentials.notionApiToken }} — never hard-code the token. |
Workflow Examples
Example 1 — Sprint Status Report
Query all "In Progress" tasks, loop through each, extract key fields, and post a formatted summary to Slack.
// Step 1: database/query
{
"Database": "{{ $vars.sprintDbId }}",
"Filter": { "property": "Status", "select": { "equals": "In Progress" } },
"Sorts": [{ "property": "Priority", "direction": "descending" }],
"ReturnAll": true
}
// Step 2: Loop over results
// Step 3 per item: build message line
const title = item.properties.Name.title[0].plain_text;
const assignee = item.properties.Assignee.people[0]?.name ?? "Unassigned";
const due = item.properties.Due.date?.start ?? "No date";
return `- ${title} (${assignee}) — Due: ${due}`;
// Step 4: Slack/postMessage with joined lines
Example 2 — Daily Updated Records Digest
Every morning, find all pages edited in the past 24 hours and email a digest to the team lead.
// Step 1: ScheduledTrigger (daily 8am)
// Step 2: Function node — compute yesterday ISO string
const yesterday = new Date(Date.now() - 86400000).toISOString();
return { yesterday };
// Step 3: database/query
{
"Database": "{{ $vars.crmDbId }}",
"Filter": {
"timestamp": "last_edited_time",
"last_edited_time": { "after": "{{ $json.yesterday }}" }
},
"ReturnAll": true
}
// Step 4: EmailSmtp with results count and list
Example 3 — Paginated Export to External Database
Export all rows from a large Notion database into an external SQL database using cursor-based pagination.
// Step 1: database/query (Limit: 100, StartCursor: {{ $vars.cursor ?? null }})
// Step 2: Loop over results → insert each row into MongoDB/Postgres
// Step 3: IfCondition — has_more === true
// True: VariableAssignment cursor = next_cursor → loop back to Step 1
// False: StopWorkflow