DEV Community

Pirate Prentice
Pirate Prentice

Posted on

n8n Notion Node: Sync Databases, Manage Pages, and Build Automated Workflows (Free Workflow JSON)

Notion has become the default workspace for thousands of teams — but its real power unlocks when you connect it to the rest of your stack. The n8n Notion node lets you read and write Notion databases, create and update pages, manage blocks, and trigger workflows from Notion events. This guide covers the operations you'll use most, the gotchas that trip up new users, and three production patterns that actually ship.


Prerequisites

  • n8n (self-hosted or cloud)
  • A Notion account with at least one database
  • A Notion integration (internal or public) with the right capabilities

Setting up the Notion integration:

  1. Go to notion.so/my-integrationsNew integration
  2. Give it a name, select your workspace, set capabilities: Read content, Update content, Insert content
  3. Copy the Internal Integration Secret
  4. In your Notion database/page: ⋯ menu → Add connections → your integration name (Notion requires explicit page-level connection grants)

In n8n: Credentials → New → Notion API → paste your Internal Integration Secret.


Operations at a Glance

Operation What It Does
Database: Get Fetch database metadata (schema, properties)
Database: Get Many List databases the integration has access to
Database: Query Filter and sort records in a database
Page: Create Add a new page/row to a database
Page: Get Fetch a page by its ID
Page: Update Update page properties
Page: Archive Archive (soft-delete) a page
Block: Get Many Read the content blocks of a page
Block: Append Add new blocks to a page

Key Configuration Concepts

Finding Your Database ID

Your Notion database URL looks like:
https://www.notion.so/myworkspace/abc123def456...?v=...

The database ID is the alphanumeric string between the last / and the ?v=. In n8n, you can paste the full URL — the node parses it automatically.

Property Types Matter

Notion properties have types (title, rich_text, number, select, multi_select, date, checkbox, url, email, phone_number, relation, formula, rollup). The n8n Notion node exposes these as typed fields. A common error: trying to set a formula or rollup field — those are computed by Notion, not writable via API.

Filtering in Database: Query

Filters use Notion's compound filter syntax. In n8n's UI, you build these with the visual filter builder. For complex compound filters, use the JSON input mode:

{
  "and": [
    { "property": "Status", "select": { "equals": "Active" } },
    { "property": "MRR", "number": { "greater_than": 100 } }
  ]
}
Enter fullscreen mode Exit fullscreen mode

3 Production Workflow Patterns

Pattern 1: CRM-to-Notion Database Sync

Use case: New leads come in from a Typeform or webhook. You want them in a Notion CRM database — deduplicated, enriched, and linked to the right company page.

Workflow:

  1. Webhook Trigger — receives lead data (name, email, company, source).
  2. Notion – Database: Query — filter: { "property": "Email", "email": { "equals": "{{ $json.email }}" } }. Returns existing record if the lead exists.
  3. IF node{{ $json.results.length > 0 }} → true (existing) or false (new).
    • False branch (new lead): Notion – Page: Create — database ID: your CRM DB, properties: name (title), email, company, source, Status (select: "New"), Created (date: today).
    • True branch (existing lead): Notion – Page: Update — page ID from query results, update Last Seen date and append to Notes.
  4. Merge node — rejoin and send a Slack notification either way.

Key config on Page: Create:

  • Properties → Title → set to lead name
  • Use the "Additional Fields" section for non-title properties
  • Date fields require ISO 8601 format: {{ $now.toISO() }}

Pattern 2: Automated Weekly KPI Report to Notion

Use case: Every Monday morning, pull KPI data from Stripe (revenue, new customers, refunds) and write it to a Notion database as a new weekly report page — so the team has a running log of weekly metrics in Notion without manual copy-paste.

Workflow:

  1. Schedule Trigger — every Monday at 8:00 AM.
  2. HTTP Request node — Stripe API: GET /v1/charges?created[gte]={{ $now.startOf('week').subtract(1, 'week').toUnixInteger() }}&created[lte]={{ $now.startOf('week').toUnixInteger() }} to get last week's charges.
  3. Code node — aggregate: sum amounts, count customers, sum refunds, compute net revenue.
  4. Notion – Page: Create — in your KPI Reports database:
    • Title: Week of {{ $now.startOf('week').subtract(1, 'week').toFormat('MMM d') }}
    • MRR (number): net revenue
    • New Customers (number): customer count
    • Refunds (number): refund total
    • Status (select): "Published"
  5. Notion – Block: Append — append a rich text block to the new page with a formatted summary paragraph.
  6. Slack node — notify #metrics channel with the Notion page URL.

Gotcha: Notion page IDs from Page: Create are returned in the response as id. Use {{ $json.id }} in Block: Append to reference the just-created page.


Pattern 3: Notion-Triggered Fulfillment Workflow

Use case: Your team manages client project approvals in Notion. When a project page's Status property changes to "Approved", automatically send the client a welcome email, create a Trello card for the team, and update a Google Sheet.

Notion doesn't support real-time webhooks natively — you'll poll with the Schedule Trigger and check for recently-updated records.

Workflow:

  1. Schedule Trigger — every 5 minutes.
  2. Notion – Database: Query — filter for pages where Status = "Approved" AND Last Edited Time is after your last-check timestamp (store this in a Set node or a simple n8n static variable).
  3. IF node — skip if results are empty.
  4. Split In Batches node — process each newly-approved project one at a time.
  5. Resend node — send welcome email to {{ $json.properties.Client_Email.email }}.
  6. HTTP Request node — Trello API: create a card in the "Active Projects" list.
  7. Google Sheets node — append a row to the project tracker sheet.
  8. Notion – Page: Update — set Automation_Sent checkbox to true so re-runs skip this record.

Key config: The Automation_Sent checkbox flag is the dedup mechanism. Without it, every poll cycle re-processes all "Approved" records.


6 Common Gotchas

1. You must connect the integration to each page/database explicitly
Even if your integration has the right capabilities, Notion requires you to manually share each database or page with the integration via ⋯ → Add connections. "Not found" errors are almost always this. Fix: share the database with your integration in Notion's UI.

2. Title property is always required for Page: Create
Every Notion page has exactly one title property. You must set it — its name varies by database (could be "Name", "Title", "Task", etc.). Map it under the title input, not as an additional field.

3. Rich text values are arrays of text objects, not plain strings
When reading page content, rich text properties return [{ "plain_text": "...", "annotations": {...} }]. To get the plain string value: {{ $json.properties.Notes.rich_text[0].plain_text }}. For multi-block rich text: map and join.

4. Formula and rollup fields are read-only
You can read computed values from formula/rollup fields, but writing to them throws an error. Only update fields whose source data lives in that page.

5. Notion rate limits: 3 requests/second per integration
Loops over large databases will hit rate limits quickly. Add a Wait node (400ms) between iterations, or use Database: Query pagination (page_size max 100) and the has_more / next_cursor response fields to paginate manually.

6. Archived pages don't appear in Database: Query by default
If a page was archived, it won't show up in query results (Notion excludes archived records). To fetch archived pages, you need to hit the Search endpoint directly via HTTP Request. For most use cases, just make sure you're not accidentally archiving records you still need.


Free Workflow JSON

This implements Pattern 1 (Webhook → Notion CRM sync with dedup). Import via n8n → Import from JSON.

{
  "name": "Webhook → Notion CRM Sync (Dedup)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "notion-crm-intake",
        "responseMode": "responseNode"
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [240, 300]
    },
    {
      "parameters": {
        "operation": "query",
        "databaseId": "YOUR_NOTION_DATABASE_ID",
        "filterJson": "={ \"property\": \"Email\", \"email\": { \"equals\": \"{{ $json.body.email }}\" } }"
      },
      "name": "Search Existing Lead",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2,
      "position": [460, 300]
    },
    {
      "parameters": {
        "conditions": {
          "number": [{ "value1": "={{ $json.results.length }}", "operation": "larger", "value2": 0 }]
        }
      },
      "name": "Lead Exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [680, 300]
    },
    {
      "parameters": {
        "operation": "create",
        "databaseId": "YOUR_NOTION_DATABASE_ID",
        "title": "={{ $('Webhook').item.json.body.name }}",
        "propertiesUi": {
          "propertyValues": [
            { "key": "Email", "emailValue": "={{ $('Webhook').item.json.body.email }}" },
            { "key": "Company", "textValue": "={{ $('Webhook').item.json.body.company }}" },
            { "key": "Status", "selectValue": "New" }
          ]
        }
      },
      "name": "Create New Lead",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2,
      "position": [900, 400]
    },
    {
      "parameters": {
        "operation": "update",
        "pageId": "={{ $json.results[0].id }}",
        "propertiesUi": {
          "propertyValues": [
            { "key": "Last Seen", "dateValue": "={{ $now.toISO() }}" }
          ]
        }
      },
      "name": "Update Last Seen",
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2,
      "position": [900, 200]
    }
  ],
  "connections": {
    "Webhook": { "main": [[{ "node": "Search Existing Lead", "type": "main", "index": 0 }]] },
    "Search Existing Lead": { "main": [[{ "node": "Lead Exists?", "type": "main", "index": 0 }]] },
    "Lead Exists?": {
      "main": [
        [{ "node": "Update Last Seen", "type": "main", "index": 0 }],
        [{ "node": "Create New Lead", "type": "main", "index": 0 }]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Replace YOUR_NOTION_DATABASE_ID with your database ID and configure your Notion API credential.


Wrapping Up

The n8n Notion node covers the full database lifecycle — querying, creating, updating, and archiving pages. The patterns with the highest leverage are the sync patterns: pulling data from other systems into Notion as a structured record store, and using Notion as a lightweight approval layer that triggers downstream automation. The gotcha that bites most new users: sharing the integration with each database in Notion's UI — "not found" errors are almost always a missing connection grant, not a credential problem.


Want the full n8n Workflow Starter Pack? 25+ production-ready workflow templates covering lead capture, Stripe automation, AI pipelines, and more — $29 on Gumroad.

Need a custom n8n workflow built for your business? Done-for-you service — $99. Describe your automation, get a working workflow delivered.

Top comments (1)

Collapse
 
pirateprentice profile image
Pirate Prentice

Are you using the Notion node to sync CRM records, generate weekly KPI reports, or trigger fulfillment from a Notion database? Drop your use case in the comments — always curious what people are building with Notion + n8n.