Intermediate~20 min setupCommunication & ProductivityVerified April 2026
Slack logo
Notion logo

How to Send Notion Database Updates to Slack with n8n

Polls a Notion database on a schedule, detects new or updated rows, and sends a formatted Slack message to a specified channel with the record details.

Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.

Best for

Small teams (5–30 people) who track projects or content in Notion and need Slack alerts without setting up a custom Notion webhook integration.

Not ideal for

Teams needing sub-60-second alert latency — Notion's API doesn't support native webhooks, so true real-time isn't possible without a third-party bridge.

Sync type

scheduled

Use case type

notification

Real-World Example

💡

A 12-person product team tracks sprint tasks in a Notion database. When a task status flips to 'In Review' or a new task is added, their #product-updates Slack channel gets an alert automatically. Before this workflow, the PM was manually scanning Notion twice a day and status changes sat invisible for 4–6 hours before anyone noticed.

What Will This Cost?

Drag the slider to your expected monthly volume.

/mo
505005K50K

Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.

Prices shown for annual billing. Based on published pricing as of April 2026.

Estimated ROI

1000

min saved/mo

$583

labor value/mo

Free

no platform cost

Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.

Implementation

Skip the setup

Import this workflow directly into n8n

Copy the pre-built n8n blueprint and paste it straight into n8n. All modules, filters, and field mappings are already configured — you just need to connect your accounts.

Before You Start

Make sure you have everything ready.

Notion Internal Integration Token with 'Read content' capability, shared to the target database
Slack OAuth app with 'chat:write' scope, or a bot token with permission to post in the target channel
n8n instance (cloud or self-hosted v1.0+) with access to the Code node (not restricted by admin policy)
The 32-character Notion Database ID from the target database URL
The target Slack channel name and confirmation that the bot/app has been added to that channel

Field Mapping

Map these fields between your apps.

FieldAPI Name
Required
Page IDid
Page Titleproperties.Name.title[0].plain_text
Last Edited Timelast_edited_time
Created Timecreated_time
5 optional fields▸ show
Statusproperties.Status.select.name
Assigneeproperties.Assignee.people[0].name
Last Edited Bylast_edited_by.name
Due Dateproperties.Due Date.date.start
Notion Page URLurl

Step-by-Step Setup

1

notion.so/my-integrations > New Integration > Capabilities

Create a Notion Integration and Share Your Database

Before n8n can read your Notion database, you need an internal integration token. Go to notion.so/my-integrations, click 'New integration', name it (e.g. 'n8n Notifier'), and set the capability to 'Read content'. Copy the Internal Integration Token — you'll paste it into n8n next. Then open the Notion database you want to monitor, click the three-dot menu in the top right, scroll to 'Connections', and add your new integration by name.

  1. 1Go to notion.so/my-integrations and click '+ New integration'
  2. 2Name the integration 'n8n Notifier' and select your workspace
  3. 3Under Capabilities, check 'Read content' only — uncheck Insert and Update
  4. 4Click Submit and copy the 'Internal Integration Token'
  5. 5Open your Notion database, click '···' (top right), go to Connections, and add 'n8n Notifier'
What you should see: Your integration appears under Connections in the Notion database menu with a checkmark. The token is a string starting with 'secret_'.
Common mistake — If you skip sharing the database with the integration, n8n will authenticate fine but return zero results — no error, just empty responses. Always connect the integration to the specific database.
2

n8n Dashboard > Add Workflow

Create a New Workflow in n8n

Open your n8n instance (cloud at app.n8n.io or self-hosted). Click 'Add workflow' in the left sidebar. Give it a clear name like 'Notion → Slack Row Notifications'. You'll land on a blank canvas with a single Start node. This is where you'll build the polling trigger.

  1. 1Click 'Add workflow' in the left sidebar
  2. 2Click the workflow title at the top and rename it to 'Notion → Slack Row Notifications'
  3. 3Click the Start node on the canvas to see your trigger options
What you should see: You have a blank workflow canvas with the workflow name showing in the top bar and no active executions.
3

Canvas > + > Schedule Trigger

Add a Schedule Trigger Node

Since Notion doesn't offer native webhooks, you'll poll on a schedule. Click the '+' button on the canvas, search for 'Schedule Trigger', and add it. Set the interval to every 5 minutes — this is the lag time between a Notion change and the Slack message. For most teams, 5 minutes is fine. If your team needs faster alerts, 1 minute is the floor before you hit Notion API rate limits under load.

  1. 1Click the '+' icon on the blank canvas
  2. 2Search 'Schedule' and select 'Schedule Trigger'
  3. 3Set 'Trigger Interval' to 'Minutes'
  4. 4Set the value to '5'
  5. 5Click outside the node to close the panel
What you should see: The Schedule Trigger node shows '5 minutes' as the interval on the canvas label.
Common mistake — Don't set this below 1 minute. Notion's API allows 3 requests per second per integration. If your database grows large, each poll does multiple paginated requests and you'll hit rate limits quickly at sub-minute intervals.
n8n
+
click +
search apps
Slack
SL
Slack
Add a Schedule Trigger Node
Slack
SL
module added
4

Canvas > + > Notion > Database Page > Get Many

Add a Notion Node to Query the Database

Click '+' after the Schedule Trigger and search for 'Notion'. Select the Notion node and set Operation to 'Get Many' under the 'Database Page' resource. Connect your Notion credentials by clicking 'Credential for Notion API' and pasting your Internal Integration Token from Step 1. Set the Database ID — you can find this in the Notion database URL: it's the 32-character string after the last slash and before the '?v=' parameter. Enable 'Return All' if your database has under 500 rows; otherwise set a page size of 100.

  1. 1Click '+' after the Schedule Trigger and search 'Notion'
  2. 2Select 'Notion' node, set Resource to 'Database Page', Operation to 'Get Many'
  3. 3Click 'Credential for Notion API', select 'Create New', and paste your integration token
  4. 4Paste your Notion Database ID into the 'Database ID' field
  5. 5Toggle 'Return All' on (or set Page Size to 100 for large databases)
What you should see: Click 'Test step' — the node should return a list of Notion rows as JSON objects. Each row includes 'id', 'created_time', 'last_edited_time', and your database properties.
Common mistake — The Database ID in the URL looks like '8a3f...d4b2' — copy only the 32-character hex string. If you copy the full URL or include the view ID after '?v=', the node will throw a 'Could not find database' error.
5

Canvas > + > Code

Add a Code Node to Filter New and Updated Rows

This is the critical step. Notion has no built-in 'changed since last poll' filter via the API, so you need to compare timestamps in code. Add a Code node after the Notion node. You'll write logic that checks each row's 'created_time' and 'last_edited_time' against a threshold (the last poll time minus a buffer). The pro tip section below has the full working code. This node outputs only the rows that are new or changed since the last execution.

  1. 1Click '+' after the Notion node and search 'Code'
  2. 2Select the 'Code' node (JavaScript mode is default)
  3. 3Paste the code from the Pro Tip section below into the code editor
  4. 4Click 'Test step' to verify filtered rows appear in the output panel
What you should see: The Code node outputs only rows modified or created within the last polling window. If nothing changed, it outputs an empty array and downstream nodes won't fire.
Common mistake — If you skip this filter step and connect Notion directly to Slack, you'll get a Slack message for every row in your database on every poll — potentially hundreds of messages per hour.

Paste this into the Code node (Step 5). It reads the last-processed timestamp from n8n static workflow data, filters Notion rows to only those created or edited after that threshold, marks new vs. updated rows, persists the new high-water mark timestamp, and deduplicates by page ID across executions. The POLL_WINDOW_MINUTES buffer (set to 6) overlaps slightly with the 5-minute schedule to catch rows that land on a boundary.

JavaScript — Code Node// n8n Code Node — Filter new/updated Notion rows and deduplicate
▸ Show code
// n8n Code Node — Filter new/updated Notion rows and deduplicate
// Paste this into the Code node between the Notion node and the IF node
const POLL_WINDOW_MINUTES = 6; // Slightly wider than your 5-min schedule interval

... expand to see full code

// n8n Code Node — Filter new/updated Notion rows and deduplicate
// Paste this into the Code node between the Notion node and the IF node

const POLL_WINDOW_MINUTES = 6; // Slightly wider than your 5-min schedule interval

// Get persisted state from n8n static workflow data
const staticData = $getWorkflowStaticData('global');
const lastRunTimestamp = staticData.lastRunTimestamp
  ? new Date(staticData.lastRunTimestamp)
  : new Date(Date.now() - POLL_WINDOW_MINUTES * 60 * 1000);

const processedIds = new Set(staticData.processedIds || []);

const now = new Date();
const cutoff = new Date(now.getTime() - POLL_WINDOW_MINUTES * 60 * 1000);
// Use the later of: lastRunTimestamp or the rolling window
const threshold = lastRunTimestamp > cutoff ? lastRunTimestamp : cutoff;

const allItems = $input.all();
const filteredItems = [];

for (const item of allItems) {
  const row = item.json;
  const pageId = row.id;
  const createdAt = new Date(row.created_time);
  const editedAt = new Date(row.last_edited_time);

  const isNew = createdAt >= threshold;
  const isUpdated = editedAt >= threshold && !isNew;

  // Skip if we've already sent a notification for this exact edit cycle
  const dedupeKey = `${pageId}_${row.last_edited_time}`;
  if (processedIds.has(dedupeKey)) continue;

  if (isNew || isUpdated) {
    // Resolve the page title safely
    const titleArr = row.properties?.Name?.title;
    const title = titleArr && titleArr.length > 0
      ? titleArr[0].plain_text
      : '(Untitled)';

    const status = row.properties?.Status?.select?.name || 'No status';
    const assignee = row.properties?.Assignee?.people?.[0]?.name || 'Unassigned';
    const dueDate = row.properties?.['Due Date']?.date?.start || 'No due date';
    const editedBy = row.last_edited_by?.name || 'Unknown';
    const pageUrl = row.url || '';

    filteredItems.push({
      json: {
        pageId,
        dedupeKey,
        changeType: isNew ? 'NEW' : 'UPDATED',
        title,
        status,
        assignee,
        dueDate,
        editedBy,
        pageUrl,
      }
    });

    // Track this ID so future polls don't re-send it
    processedIds.add(dedupeKey);
  }
}

// Persist state — cap processedIds to last 500 to avoid unbounded growth
const idsArray = Array.from(processedIds);
staticData.processedIds = idsArray.slice(-500);
staticData.lastRunTimestamp = now.toISOString();

return filteredItems;
Slack
SL
trigger
filter
Condition
= "New"
yes — passes through
no — skipped
Notion
NO
notified
6

Canvas > + > IF

Add an IF Node to Handle Empty Results

When nothing changed in Notion, the Code node returns an empty array. You need an IF node to stop the workflow there instead of sending blank Slack messages or hitting errors. Add an IF node after the Code node. Set the condition to check if the item count is greater than 0 using the expression '{{ $input.all().length > 0 }}'. Connect the 'true' branch to your next step. The 'false' branch needs no connection — n8n will stop silently.

  1. 1Click '+' after the Code node and search 'IF'
  2. 2Set Condition type to 'Expression'
  3. 3Enter the expression: {{ $input.all().length > 0 }}
  4. 4Leave the False branch unconnected
What you should see: The IF node shows two output branches: 'true' (items exist) and 'false' (nothing to send). Only the true branch continues.
7

Canvas > + > Slack > Credentials > OAuth2

Connect Slack Credentials

Click '+' on the true branch of the IF node and search 'Slack'. Select the Slack node. For credentials, click 'Credential for Slack API' and choose 'OAuth2' — this is the recommended method. Click 'Connect', sign into your Slack workspace, and authorize n8n to post messages. You'll need the 'chat:write' scope at minimum. If you want to tag users in notifications, also add 'users:read'.

  1. 1Click '+' on the IF node's true branch and search 'Slack'
  2. 2Select the Slack node, set Resource to 'Message', Operation to 'Send'
  3. 3Click 'Credential for Slack API', select 'Create New', choose 'OAuth2'
  4. 4Click 'Connect my account' and authorize in the Slack popup
  5. 5Confirm the green 'Connected' indicator appears next to the credential name
What you should see: The Slack credential shows a green dot and your workspace name in the credential selector. The node is ready to send.
Common mistake — If your Slack workspace uses Enterprise Grid, the OAuth app must be approved by an admin before the connection works. A personal Slack bot token (xoxb-) works as an alternative but requires manually adding the bot to each channel.
8

Slack Node > Parameters > Channel / Message Text

Configure the Slack Message

Set the Channel field to your target channel name (e.g. '#project-updates'). In the Message Text field, build a dynamic message using n8n expressions that pull values from the Notion row. Set Message Type to 'Block Kit' for formatted output, or use plain text for simplicity. A good plain-text template: '📋 Notion Update: *{{ $json.properties.Name.title[0].plain_text }}* — Status: {{ $json.properties.Status.select.name }} — Updated by: {{ $json.last_edited_by.name }}'.

  1. 1Type your channel name in the 'Channel' field (include the # prefix)
  2. 2Click the 'Message Text' field and switch to Expression mode (lightning bolt icon)
  3. 3Paste your message template with dynamic {{ $json.properties... }} references
  4. 4Optionally set 'Message Type' to 'Block Kit' for richer formatting
  5. 5Click 'Test step' to send a test message to your Slack channel
What you should see: A test message appears in your Slack channel showing the Notion row data. Property names and values render as expected — not as raw JSON.
Common mistake — Notion property names in the API response are case-sensitive and match exactly what you named them in Notion. If your column is called 'Task Name' (with a space), the expression is $json.properties['Task Name'].title[0].plain_text — not $json.properties.TaskName.
9

Code Node > JavaScript Editor

Add Deduplication Using n8n Static Data

To prevent the same row triggering a Slack message twice across poll cycles, store the last-processed row IDs in n8n's static workflow data. In the Code node from Step 5, use $getWorkflowStaticData('global') to read and write a Set of processed IDs. The pro tip code handles this automatically. This is the most important production safeguard for polling-based notification workflows.

  1. 1Open the Code node from Step 5
  2. 2Confirm the static data read/write logic is present in your code (see Pro Tip)
  3. 3Run a test execution and check that the same row does not produce two Slack messages
  4. 4Open n8n's execution log and verify only new rows appear in Code node output
What you should see: On the second poll, rows already sent to Slack are filtered out by the Code node. Only genuinely new or re-edited rows pass through.
Common mistake — n8n's static workflow data is reset if you delete and recreate the workflow. If you migrate the workflow to a new instance, the deduplication history is lost and you may get a burst of duplicate notifications on first run.
10

Workflow Canvas > Top Bar > Active Toggle

Set the Poll Interval and Activate the Workflow

Go back to the Schedule Trigger node and confirm the interval. For most teams, 5 minutes balances freshness against API usage. Click 'Save' in the top right, then toggle the workflow from 'Inactive' to 'Active' using the toggle in the top bar. n8n will now run the workflow automatically on your schedule. Check the Executions tab after 10 minutes to confirm it's running and the Notion node is returning data.

  1. 1Click 'Save' in the top right corner
  2. 2Toggle the workflow status from 'Inactive' to 'Active'
  3. 3Wait 5 minutes, then click 'Executions' in the left sidebar
  4. 4Open the most recent execution and verify each node shows a green checkmark
What you should see: The workflow status shows 'Active' with a green indicator. The Executions list shows completed runs at your chosen interval.
Common mistake — The default polling interval is often 15 minutes. If you need faster delivery, check whether your plan supports shorter intervals before assuming it's a bug.

Going live

Production Checklist

Before you turn this on for real, confirm each item.

Troubleshooting

Common errors and how to fix them.

Frequently Asked Questions

Common questions about this workflow.

Analysis

VerdictWhy n8n for this workflow

Use n8n for this if your team is self-hosting to keep data internal, if you want to write custom deduplication or filtering logic without paying per-operation, or if you need to route notifications to different channels based on complex conditions (e.g. priority + assignee + status combined). On n8n cloud, this workflow costs roughly $0 in execution credits for a team with 200 Notion changes per month — you're just paying the flat plan fee. Pick Zapier instead if your team has no one willing to look at a Code node and you need setup done in 10 minutes flat.

Cost

Real cost math: each 5-minute poll is one workflow execution. That's 288 executions per day, 8,640 per month. On n8n Cloud's Starter plan ($20/month), you get 2,500 executions — you'll burn through those in under 9 days of polling. You need the Pro plan at $50/month for 10,000 executions. Self-hosted n8n is free regardless of execution count, which is why teams with consistent high-frequency polling almost always go self-hosted for this use case. Zapier charges per task: at 200 Notion changes per month, that's 200 tasks at $0.05–$0.10 each depending on your plan — roughly $10–$20/month on top of your base plan, but with zero server management.

Tradeoffs

Make handles this use case better in one specific way: its Notion 'Watch Database Items' module uses Make's own polling infrastructure, so you set an interval and Make handles the timestamp tracking for you — no Code node required. Zapier's Notion trigger works similarly but fires slightly slower (2–5 minute minimum). Power Automate has no native Notion connector; you'd need a premium HTTP connector and build the entire polling logic yourself, which takes longer than n8n. Pipedream has a solid Notion source that handles polling natively, and its component-based architecture makes multi-database setups cleaner than n8n's canvas. n8n still wins for teams who want full control, self-hosting, and the ability to add complex JavaScript logic without paying per step.

Three things you'll hit after going live. First: Notion's 'last_edited_time' updates even when an integration reads a page in certain configurations — this can cause phantom 'update' notifications for rows nobody touched. Test this by running two polls back-to-back without touching Notion and checking if the same rows appear. Second: if a team member bulk-imports 50 rows into Notion at once, your first poll after that will send 50 Slack messages in rapid succession. Add a rate-limit step or switch to a digest model if bulk imports are common in your workflow. Third: Notion's API returns property values in deeply nested structures that differ by property type — a multi-select field looks nothing like a date field. Budget 30–60 minutes mapping your specific database properties before the Slack messages look clean.

Ideas for what to build next

  • Filter Notifications by Status Change OnlyAdd a second IF node after the Code node that checks if the Status field specifically changed — not just any field. Store the previous status in static data alongside the page ID and only send a Slack message when it differs.
  • Route Alerts to Different Channels by Assignee or TeamAdd a Switch node after the Code node that checks the Assignee or a Team property and routes each row to a different Slack channel — engineering tasks go to #eng-updates, design tasks to #design-updates.
  • Send a Daily Digest Instead of Per-Row AlertsChange the Schedule Trigger to run once at 9am, remove the per-row Slack node, and replace it with an Aggregate node that batches all changes into a single formatted Slack message listing everything that moved overnight.

Related guides

Was this guide helpful?
Slack + Notion overviewn8n profile →