This workflow corresponds to n8n.io template #17549 — we link there as the canonical source.
This workflow follows the Notion → Slack recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Spawn recurring Notion tasks from a rules database on a schedule",
"tags": [],
"nodes": [
{
"id": "10a4ac70-7f7f-4dac-862e-4aa3be328de6",
"name": "When Daily Schedule Fires",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
576,
288
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 6
}
]
}
},
"typeVersion": 1.3
},
{
"id": "b269b54f-4e73-46b1-a12a-23be78a27378",
"name": "Read Recurrence Rules",
"type": "n8n-nodes-base.notion",
"position": [
832,
288
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "list",
"value": "YOUR_NOTION_RULES_DATABASE_ID",
"cachedResultName": "Recurrence rules"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "36f47629-0642-4218-a869-39b1023c9174",
"name": "Select Rules Due Today",
"type": "n8n-nodes-base.code",
"position": [
1072,
288
],
"parameters": {
"jsCode": "const TIMEZONE = 'UTC';\n\nfunction todayIn(tz) {\n const fmt = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short' });\n const parts = fmt.formatToParts(new Date());\n const bag = {};\n for (let i = 0; i < parts.length; i++) { bag[parts[i].type] = parts[i].value; }\n const y = Number(bag.year);\n const m = Number(bag.month);\n const d = Number(bag.day);\n return {\n iso: bag.year + '-' + bag.month + '-' + bag.day,\n year: y,\n month: m,\n day: d,\n weekday: String(bag.weekday).slice(0, 3).toLowerCase(),\n daysInMonth: new Date(Date.UTC(y, m, 0)).getUTCDate()\n };\n}\n\nfunction toUtcDays(value) {\n const bits = String(value == null ? '' : value).slice(0, 10).split('-');\n if (bits.length !== 3) return null;\n const stamp = Date.UTC(Number(bits[0]), Number(bits[1]) - 1, Number(bits[2]));\n if (!isFinite(stamp)) return null;\n return Math.round(stamp / 86400000);\n}\n\nfunction shortDays(raw) {\n return String(raw == null ? '' : raw).toLowerCase().split(',').map(function (s) { return s.trim().slice(0, 3); }).filter(function (s) { return s.length === 3; });\n}\n\nconst today = todayIn(TIMEZONE);\nconst todayDays = toUtcDays(today.iso);\nconst nthThisMonth = Math.floor((today.day - 1) / 7) + 1;\nconst isLastOfKind = today.day + 7 > today.daysInMonth;\nconst rules = $input.all().map(function (i) { return i.json; });\nconst due = [];\n\nfor (let r = 0; r < rules.length; r++) {\n const rule = rules[r];\n const ruleId = String(rule.rule_id == null ? '' : rule.rule_id).trim();\n const title = String(rule.task_title == null ? '' : rule.task_title).trim();\n if (!ruleId || !title) continue;\n if (rule.active === false) continue;\n\n const freq = String(rule.frequency == null ? '' : rule.frequency).trim().toLowerCase();\n let hit = false;\n\n if (freq === 'every_n_days') {\n const step = Math.max(1, Math.floor(Number(rule.interval_days) || 1));\n const anchor = toUtcDays(rule.anchor_date);\n if (anchor !== null && todayDays >= anchor) { hit = (todayDays - anchor) % step === 0; }\n } else if (freq === 'weekly') {\n hit = shortDays(rule.weekdays).indexOf(today.weekday) >= 0;\n } else if (freq === 'monthly_day') {\n const wanted = Math.floor(Number(rule.day_of_month) || 0);\n if (wanted >= 1 && wanted <= 31) {\n hit = today.day === wanted || (wanted > today.daysInMonth && today.day === today.daysInMonth);\n }\n } else if (freq === 'monthly_nth_weekday') {\n const wantedDay = shortDays(rule.nth_weekday)[0];\n const nth = Math.floor(Number(rule.nth) || 0);\n if (wantedDay === today.weekday) { hit = nth === -1 ? isLastOfKind : nth === nthThisMonth; }\n }\n\n if (hit) {\n due.push({ json: { rule_id: ruleId, task_title: title, frequency: freq, spawn_date: today.iso, timezone: TIMEZONE } });\n }\n}\n\nreturn due;"
},
"typeVersion": 2
},
{
"id": "54503278-7fde-4e45-aecf-a77d25b5b06f",
"name": "Get Spawn Ledger",
"type": "n8n-nodes-base.dataTable",
"position": [
1376,
288
],
"parameters": {
"filters": {
"conditions": []
},
"operation": "get",
"returnAll": true,
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "cadence_last_spawned"
}
},
"executeOnce": true,
"typeVersion": 1.1,
"alwaysOutputData": true
},
{
"id": "c8803cf1-f21f-4434-affc-b49c830f90a8",
"name": "Filter Already Spawned",
"type": "n8n-nodes-base.code",
"position": [
1632,
288
],
"parameters": {
"jsCode": "const dueRules = $('Select Rules Due Today').all().map(function (i) { return i.json; });\nconst ledgerRows = $input.all().map(function (i) { return i.json; });\nconst stamped = {};\n\nfor (let i = 0; i < ledgerRows.length; i++) {\n const row = ledgerRows[i];\n const key = String(row.rule_id == null ? '' : row.rule_id).trim();\n if (!key) continue;\n stamped[key] = String(row.last_spawned_on == null ? '' : row.last_spawned_on).slice(0, 10);\n}\n\nconst fresh = [];\nfor (let r = 0; r < dueRules.length; r++) {\n const rule = dueRules[r];\n if (stamped[rule.rule_id] === rule.spawn_date) continue;\n fresh.push({ json: rule });\n}\n\nreturn fresh;"
},
"typeVersion": 2
},
{
"id": "ae826333-bbd1-4543-884d-73feb9c181ec",
"name": "Create Notion Task Page",
"type": "n8n-nodes-base.notion",
"position": [
1936,
288
],
"parameters": {
"title": "={{ $json.task_title }}",
"options": {},
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "list",
"value": "YOUR_NOTION_TASKS_DATABASE_ID",
"cachedResultName": "Tasks"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Due|date",
"date": "={{ $json.spawn_date }}",
"type": "date",
"includeTime": false
}
]
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "dfa5a556-861d-4adf-8b21-5beb749a6800",
"name": "Record Spawn In Ledger",
"type": "n8n-nodes-base.dataTable",
"position": [
2192,
288
],
"parameters": {
"columns": {
"value": {
"rule_id": "={{ $('Filter Already Spawned').item.json.rule_id }}",
"rule_title": "={{ $('Filter Already Spawned').item.json.task_title }}",
"last_spawned_on": "={{ $('Filter Already Spawned').item.json.spawn_date }}"
},
"schema": [
{
"id": "rule_id",
"type": "string",
"display": true,
"required": false,
"displayName": "rule_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_spawned_on",
"type": "string",
"display": true,
"required": false,
"displayName": "last_spawned_on",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rule_title",
"type": "string",
"display": true,
"required": false,
"displayName": "rule_title",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"rule_id"
]
},
"filters": {
"conditions": [
{
"keyName": "rule_id",
"keyValue": "={{ $('Filter Already Spawned').item.json.rule_id }}"
}
]
},
"options": {},
"matchType": "allConditions",
"operation": "upsert",
"dataTableId": {
"__rl": true,
"mode": "name",
"value": "cadence_last_spawned"
}
},
"typeVersion": 1.1
},
{
"id": "083252da-ecb1-4085-bd37-5b92b17aec8d",
"name": "Post Spawn Summary",
"type": "n8n-nodes-base.slack",
"position": [
2432,
288
],
"parameters": {
"text": "=Spawned {{ $('Filter Already Spawned').all().length }} recurring task(s) for {{ $('Filter Already Spawned').first().json.spawn_date }}: {{ $('Filter Already Spawned').all().map(i => i.json.task_title).join(', ') }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "YOUR_SLACK_CHANNEL"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"executeOnce": true,
"typeVersion": 2.5
},
{
"id": "f0a3d0a7-3e51-4ce7-b4b1-9c797a8bdcf5",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-128,
-48
],
"parameters": {
"width": 564,
"height": 920,
"content": "## Spawn recurring Notion tasks from a rules database on a schedule\n\n### How it works\n1. A daily Schedule Trigger reads a Notion rules database where every row describes one recurring to-do.\n2. A Code node tests each rule against today's date in a configurable timezone: every N days, weekly on chosen weekdays, monthly on day D, and the nth weekday of the month.\n3. A Data Table named `cadence_last_spawned` is read, and any rule already stamped with today's date is dropped so a repeat run creates nothing.\n4. Notion creates one page per due rule, with the due date set to today.\n5. The ledger is upserted on `rule_id` and Slack gets a one line summary of what was created.\n\n### Setup steps\n- [ ] Connect your Notion credential on `Read Recurrence Rules` and `Create Notion Task Page`.\n- [ ] Replace `YOUR_NOTION_RULES_DATABASE_ID` and `YOUR_NOTION_TASKS_DATABASE_ID` with your own database IDs.\n- [ ] Give the rules database the properties `rule_id`, `task_title`, `frequency`, `interval_days`, `anchor_date`, `weekdays`, `day_of_month`, `nth`, `nth_weekday`, and `active`.\n- [ ] Give the tasks database a date property named `Due`.\n- [ ] Create a Data Table called `cadence_last_spawned` with the columns `rule_id`, `last_spawned_on`, and `rule_title`.\n- [ ] Connect your Slack credential and pick the channel on `Post Spawn Summary`.\n\n### Customization\nSet `TIMEZONE` at the top of `Select Rules Due Today` to the zone your day boundary should follow, and add another branch in that node if you need a pattern the four built in ones cannot express."
},
"typeVersion": 1
},
{
"id": "7c29a1c0-1679-4382-80dc-a6e67b98cdc9",
"name": "Section Read Rules",
"type": "n8n-nodes-base.stickyNote",
"position": [
512,
192
],
"parameters": {
"color": 7,
"width": 732,
"height": 280,
"content": "## Read rules and pick today"
},
"typeVersion": 1
},
{
"id": "616243ff-c58d-4e24-806e-f2ead64bc30c",
"name": "Section Skip Spawned",
"type": "n8n-nodes-base.stickyNote",
"position": [
1296,
192
],
"parameters": {
"color": 7,
"width": 512,
"height": 280,
"content": "## Skip what already spawned"
},
"typeVersion": 1
},
{
"id": "38254fc2-6107-41e7-9dcc-0c8ea42fbe77",
"name": "Section Create And Record",
"type": "n8n-nodes-base.stickyNote",
"position": [
1856,
192
],
"parameters": {
"color": 7,
"width": 780,
"height": 280,
"content": "## Create tasks and record them"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Get Spawn Ledger": {
"main": [
[
{
"node": "Filter Already Spawned",
"type": "main",
"index": 0
}
]
]
},
"Read Recurrence Rules": {
"main": [
[
{
"node": "Select Rules Due Today",
"type": "main",
"index": 0
}
]
]
},
"Filter Already Spawned": {
"main": [
[
{
"node": "Create Notion Task Page",
"type": "main",
"index": 0
}
]
]
},
"Record Spawn In Ledger": {
"main": [
[
{
"node": "Post Spawn Summary",
"type": "main",
"index": 0
}
]
]
},
"Select Rules Due Today": {
"main": [
[
{
"node": "Get Spawn Ledger",
"type": "main",
"index": 0
}
]
]
},
"Create Notion Task Page": {
"main": [
[
{
"node": "Record Spawn In Ledger",
"type": "main",
"index": 0
}
]
]
},
"When Daily Schedule Fires": {
"main": [
[
{
"node": "Read Recurrence Rules",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
notionApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs daily to read recurrence rules from a Notion database, creates due task pages in a Notion tasks database, prevents duplicate spawns using an n8n Data Table ledger, and posts a summary of created tasks to a Slack channel. Runs every day at 06:00 on a schedule.…
Source: https://n8n.io/workflows/17549/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow fully automates your team's daily standup process using Slack for communication, Notion for structured data storage, and Redis for real-time session management.
This workflow is an automated employee time tracking and reporting system that monitors weekly work hours via TMetric, then delivers personalized summaries directly to each team member on Slack. It co
Code Filter. Uses stickyNote, notion, noOp, splitInBatches. Scheduled trigger; 29 nodes.
This template is for everyone who manages their blog entries in Notion and want to have an easy way to transform them to Webflow.
🌸 Affirmation Sender + Weekly Gratitude Digest v2