When to Use
- Schema inspection before page creation: Call
database/get to read the property types and select options of a database before using database/createPage. This prevents type mismatch errors when setting field values on new rows.
- Incremental sync gating: Compare the
last_edited_time returned by this operation against a stored timestamp. If the database has not changed since the last run, skip the full query and save API quota.
- Pre-import access verification: Before starting a bulk import loop, call
database/get to confirm the integration has access to the target database. A NOTION_PERMISSION_DENIED error here stops the workflow cleanly before any partial writes occur.
- Dynamic form building: Read property definitions — especially
select and multi_select options — to populate dropdown choices in a BizFirst Form that lets users pick values aligned to the database schema.
- Report embedding: Retrieve the database
url field and embed it as a hyperlink in an automated email or Slack message so recipients can open the source database directly.
Configuration
Authentication
| Field | Required | Description |
AuthenticationMethod | Required | "ApiToken" or "OAuth2". |
ApiToken | Required | Notion Internal Integration Token. Starts with secret_. Store in Credentials Manager. |
ClientId | OAuth2 | OAuth2 client ID — managed via BizFirst Credentials Manager. |
ClientSecret | OAuth2 | OAuth2 client secret — managed via BizFirst Credentials Manager. |
AccessToken | OAuth2 | OAuth2 access token — managed via BizFirst Credentials Manager. |
Operation Fields
| Field | Required | Description |
Database | Required | Notion database UUID. Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Copy from the database URL in Notion. |
Sample Configuration
{
"resource": "database",
"operation": "get",
"AuthenticationMethod": "ApiToken",
"ApiToken": "{{ $credentials.notionApiToken }}",
"Database": "{{ $vars.projectDatabaseId }}"
}
Validation Errors
| Error Code | Cause |
NOTION_VALIDATION_FAILED | Database field is empty or not a valid UUID format. |
NOTION_UNAUTHORIZED | ApiToken is invalid or the integration is not connected to the workspace. |
NOTION_PERMISSION_DENIED | The integration has not been shared with this database. Open the database in Notion and add the integration under Connections. |
NOTION_NOT_FOUND | No database exists with the given UUID, or the UUID refers to a page rather than a database. |
NOTION_RATE_LIMITED | 3 requests/second limit exceeded. Add a Delay node before retrying. |
NOTION_INVALID_CONFIG | Authentication fields are missing or misconfigured. |
Output
Success Port
| Field | Type | Description |
id | string | Database UUID. |
title | string | Plain-text database title extracted from the rich-text title array. |
url | string | Full Notion URL to open the database in the browser. |
created_time | string | ISO 8601 timestamp of database creation. |
last_edited_time | string | ISO 8601 timestamp of the last edit to the database or any of its pages. |
properties | object | Map of property name to property schema definition. Each entry includes type and type-specific metadata such as select options or relation targets. |
parent | object | Parent object describing whether the database is a child of a page or the workspace root. |
Error Port
On failure the error port receives an object with errorCode (string), message (string), and httpStatus (number). Use an IfCondition node on the error port to handle NOTION_PERMISSION_DENIED separately from transient errors.
Sample Output
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Sprint Tasks",
"url": "https://www.notion.so/a1b2c3d4e5f67890abcdef1234567890",
"created_time": "2024-01-15T09:00:00.000Z",
"last_edited_time": "2024-06-10T14:32:00.000Z",
"properties": {
"Name": { "id": "title", "type": "title" },
"Status": {
"id": "abc1",
"type": "select",
"select": {
"options": [
{ "name": "Backlog", "color": "gray" },
{ "name": "In Progress", "color": "blue" },
{ "name": "Done", "color": "green" }
]
}
},
"Assignee": { "id": "abc2", "type": "people" },
"Due": { "id": "abc3", "type": "date" },
"Priority": {
"id": "abc4",
"type": "select",
"select": {
"options": [
{ "name": "High", "color": "red" },
{ "name": "Medium", "color": "yellow" },
{ "name": "Low", "color": "green" }
]
}
}
},
"parent": { "type": "workspace", "workspace": true }
}
Expression Reference
| Expression | Type | Example Value | Use |
{{ $output.getDatabase.title }} | string | "Sprint Tasks" | Include database name in notification messages |
{{ $output.getDatabase.url }} | string | Full Notion URL | Embed as hyperlink in emails or Slack messages |
{{ $output.getDatabase.last_edited_time }} | string | "2024-06-10T14:32:00.000Z" | Compare against stored timestamp for incremental sync |
{{ $output.getDatabase.properties.Status.select.options }} | array | Array of option objects | Populate a select dropdown with valid values |
{{ $output.getDatabase.id }} | string | UUID string | Pass to subsequent database/query or database/createPage calls |
Node Policies & GuardRails
| Policy | Detail |
| Share integration first | The integration must be added to each database via Notion's Connections menu before any API call. A missing share results in NOTION_PERMISSION_DENIED — this is not an authentication issue and cannot be fixed by rotating the token. |
| Use UUIDs, not titles | Database titles can be renamed in Notion. Always reference databases by their UUID stored in a workflow variable or credential, never by a hardcoded title string. |
| Cache schema between runs | If your workflow only creates pages without needing fresh property options, cache the schema output in a workflow variable and skip database/get on subsequent runs. This reduces API calls and avoids rate limiting. |
| Respect rate limit | 3 requests/second per integration. When calling database/get inside a loop, add a Delay node (400 ms) between iterations. |
| Store token in Credentials Manager | Never hard-code ApiToken in workflow configuration. Always reference it as {{ $credentials.notionApiToken }}. |
| Validate UUID format before call | Use a Function node to validate the UUID format (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) before calling this node to surface config errors early. |
Workflow Examples
Example 1 — Incremental Sync Guard
Before querying all pages, check whether the database has been edited since the last sync run. If unchanged, skip the expensive query.
// Step 1: database/get
{
"resource": "database",
"operation": "get",
"Database": "{{ $vars.sprintDbId }}"
}
// Step 2: IfCondition
// Condition: {{ $output.getDatabase.last_edited_time }} > {{ $vars.lastSyncTime }}
// True branch: proceed to database/query
// False branch: StopWorkflow (no changes)
Example 2 — Dynamic Select Options for BizFirst Form
Read the Status property options from the database schema, then pass them to a Form node to build a dropdown dynamically.
// Step 1: database/get
{
"resource": "database",
"operation": "get",
"Database": "{{ $vars.taskDbId }}"
}
// Step 2: Function node — extract option names
const options = $output.getDatabase.properties.Status.select.options.map(o => o.name);
return { statusOptions: options };
// Step 3: Form node — use statusOptions as dropdown choices
Example 3 — Pre-Import Access Check
Before starting a 500-row bulk import, verify access to the database. Fail fast with a clear error message instead of discovering the problem mid-import.
// Step 1: database/get (with error port wired to notification)
// Error port: Slack/postMessage
{
"channel": "#ops-alerts",
"text": "Bulk import blocked: {{ $error.message }} ({{ $error.errorCode }})"
}
// Success port: proceed to Loop + database/createPage