

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
scheduledUse case type
notificationReal-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.
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
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.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Page ID | id | |
| Page Title | properties.Name.title[0].plain_text | |
| Last Edited Time | last_edited_time | |
| Created Time | created_time | |
5 optional fields▸ show
| Status | properties.Status.select.name |
| Assignee | properties.Assignee.people[0].name |
| Last Edited By | last_edited_by.name |
| Due Date | properties.Due Date.date.start |
| Notion Page URL | url |
Step-by-Step Setup
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.
- 1Go to notion.so/my-integrations and click '+ New integration'
- 2Name the integration 'n8n Notifier' and select your workspace
- 3Under Capabilities, check 'Read content' only — uncheck Insert and Update
- 4Click Submit and copy the 'Internal Integration Token'
- 5Open your Notion database, click '···' (top right), go to Connections, and add 'n8n Notifier'
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.
- 1Click 'Add workflow' in the left sidebar
- 2Click the workflow title at the top and rename it to 'Notion → Slack Row Notifications'
- 3Click the Start node on the canvas to see your trigger options
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.
- 1Click the '+' icon on the blank canvas
- 2Search 'Schedule' and select 'Schedule Trigger'
- 3Set 'Trigger Interval' to 'Minutes'
- 4Set the value to '5'
- 5Click outside the node to close the panel
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.
- 1Click '+' after the Schedule Trigger and search 'Notion'
- 2Select 'Notion' node, set Resource to 'Database Page', Operation to 'Get Many'
- 3Click 'Credential for Notion API', select 'Create New', and paste your integration token
- 4Paste your Notion Database ID into the 'Database ID' field
- 5Toggle 'Return All' on (or set Page Size to 100 for large databases)
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.
- 1Click '+' after the Notion node and search 'Code'
- 2Select the 'Code' node (JavaScript mode is default)
- 3Paste the code from the Pro Tip section below into the code editor
- 4Click 'Test step' to verify filtered rows appear in the output panel
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;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.
- 1Click '+' after the Code node and search 'IF'
- 2Set Condition type to 'Expression'
- 3Enter the expression: {{ $input.all().length > 0 }}
- 4Leave the False branch unconnected
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'.
- 1Click '+' on the IF node's true branch and search 'Slack'
- 2Select the Slack node, set Resource to 'Message', Operation to 'Send'
- 3Click 'Credential for Slack API', select 'Create New', choose 'OAuth2'
- 4Click 'Connect my account' and authorize in the Slack popup
- 5Confirm the green 'Connected' indicator appears next to the credential name
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 }}'.
- 1Type your channel name in the 'Channel' field (include the # prefix)
- 2Click the 'Message Text' field and switch to Expression mode (lightning bolt icon)
- 3Paste your message template with dynamic {{ $json.properties... }} references
- 4Optionally set 'Message Type' to 'Block Kit' for richer formatting
- 5Click 'Test step' to send a test message to your Slack channel
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.
- 1Open the Code node from Step 5
- 2Confirm the static data read/write logic is present in your code (see Pro Tip)
- 3Run a test execution and check that the same row does not produce two Slack messages
- 4Open n8n's execution log and verify only new rows appear in Code node output
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.
- 1Click 'Save' in the top right corner
- 2Toggle the workflow status from 'Inactive' to 'Active'
- 3Wait 5 minutes, then click 'Executions' in the left sidebar
- 4Open the most recent execution and verify each node shows a green checkmark
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
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.
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.
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 Only — Add 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 Team — Add 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 Alerts — Change 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
How to Send Notion Database Alerts to Slack with Pipedream
~15 min setup
How to Send Notion Database Alerts to Slack with Power Automate
~15 min setup
How to Send Notion Database Updates to Slack with Zapier
~8 min setup
How to Send Notion Database Alerts to Slack with Make
~12 min setup
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup