{
"id": "k9abwUyVzl7OCsAl",
"meta": {
"instanceId": "fb8bc2e315f7f03c97140b30aa454a27bc7883a19000fa1da6e6b571bf56ad6d"
},
"name": "Realtime Notion Todoist 2-way Sync Template",
"tags": [
{
"id": "RKga6I6NviNI12bx",
"name": "template",
"createdAt": "2024-09-19T19:09:21.997Z",
"updatedAt": "2024-09-19T19:09:21.997Z"
}
],
"nodes": [
{
"id": "5e0488a3-270d-46b3-8b4b-f4ee459a3016",
"name": "Get projects",
"type": "n8n-nodes-base.httpRequest",
"position": [
-700,
260
],
"parameters": {
"url": "https:\/\/api.todoist.com\/rest\/v2\/projects",
"options": [],
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.20000000000000017763568394002504646778106689453125
},
{
"id": "e52b0a6d-4697-4c42-91c9-6c786752877c",
"name": "Get sections",
"type": "n8n-nodes-base.httpRequest",
"position": [
180,
260
],
"parameters": {
"url": "https:\/\/api.todoist.com\/rest\/v2\/sections",
"options": [],
"sendQuery": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "project_id",
"value": "={{ $json.project_id }}"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.20000000000000017763568394002504646778106689453125
},
{
"id": "8af0c04f-a46e-4fb8-ab5a-09952a3c1360",
"name": "Get Notion Databases",
"type": "n8n-nodes-base.notion",
"position": [
-1580,
260
],
"parameters": {
"resource": "database",
"operation": "getAll"
},
"credentials": {
"notionApi": {
"id": "ObmaBA0dJss3JJPv",
"name": "Notion (Test)"
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "09e43e00-6df6-4844-bda3-f5e8be0aed3a",
"name": "Prep Dropdown",
"type": "n8n-nodes-base.code",
"position": [
-1360,
260
],
"parameters": {
"jsCode": "let dropDownValues = [];\n\nfor (const item of $input.all()) {\n if (item.json.name == \"Inbox\") continue;\n dropDownValues.push({\"option\": item.json.name});\n}\n\nreturn { \"options\": JSON.stringify(dropDownValues) };"
},
"typeVersion": 2
},
{
"id": "426872eb-228a-4b1c-b992-cf17893d29d2",
"name": "Prep Dropdown1",
"type": "n8n-nodes-base.code",
"position": [
-480,
260
],
"parameters": {
"jsCode": "let dropDownValues = [];\n\nfor (const item of $input.all()) {\n if (item.json.name == \"Inbox\") continue;\n dropDownValues.push({\"option\": item.json.name});\n}\n\nreturn { \"options\": JSON.stringify(dropDownValues) };"
},
"typeVersion": 2
},
{
"id": "bebc6443-6187-4b97-82ff-45b6ce75cd7e",
"name": "Generate config",
"type": "n8n-nodes-base.code",
"position": [
400,
260
],
"parameters": {
"jsCode": "let sections = [];\nfor (const item of $('Get sections').all()) {\n sections.push({ id: item.json.id, name: item.json.name }); \n}\n\nreturn { json: \n{\n \"database_id\": $('Get Notion Database ID').first().json.database_id,\n \"project_id\": $('Get Todoist Project ID').first().json.project_id,\n \"sections\": sections\n} \n};"
},
"typeVersion": 2
},
{
"id": "52692c9c-a490-4c2c-8df4-93870bf52ded",
"name": "Choose Notion Database",
"type": "n8n-nodes-base.form",
"position": [
-1140,
260
],
"webhookId": "971affb1-c55c-4025-9b4b-743c8f3fcfcf",
"parameters": {
"options": {
"buttonLabel": "Continue"
},
"defineForm": "json",
"jsonOutput": "=[\n {\n \"fieldLabel\": \"Select Notion Database\",\n \"fieldType\": \"dropdown\",\n \"requiredField\": true,\n \"fieldOptions\": {\n \"values\": {{ $json.options }}\n }\n }\n]"
},
"typeVersion": 1
},
{
"id": "7db73660-3f99-42b6-843d-ee4adf758441",
"name": "Choose Todoist Project",
"type": "n8n-nodes-base.form",
"position": [
-260,
260
],
"webhookId": "971affb1-c55c-4025-9b4b-743c8f3fcfcf",
"parameters": {
"options": {
"buttonLabel": "Continue"
},
"defineForm": "json",
"jsonOutput": "=[\n {\n \"fieldLabel\": \"Select Todoist Project\",\n \"fieldType\": \"dropdown\",\n \"requiredField\": true,\n \"fieldOptions\": {\n \"values\": {{ $json.options }}\n }\n }\n]"
},
"typeVersion": 1
},
{
"id": "3fe51a88-765f-469e-9846-191506b0a894",
"name": "Verify security token",
"type": "n8n-nodes-base.if",
"position": [
-580,
980
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "286fafb0-a200-4963-a19f-e63162c484b4",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('OAuth redirect').first().json.query.state }}",
"rightValue": "={{ $json.state }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "7a040b65-5cc3-44d6-8bf6-c6bb7dfe5128",
"name": "Generate security token",
"type": "n8n-nodes-base.crypto",
"position": [
-800,
620
],
"parameters": {
"action": "generate",
"dataPropertyName": "state"
},
"typeVersion": 1
},
{
"id": "7033d868-2d83-45bb-a48b-f4fd6eb14e0d",
"name": "Store variables",
"type": "n8n-nodes-base.code",
"position": [
-580,
620
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const staticData = $getWorkflowStaticData('global');\nstaticData.clientID = $('Todoist Webhook Setup Helper').first().json['Client ID'];\nstaticData.clientSecret = $('Todoist Webhook Setup Helper').first().json['Client secret'];\nstaticData.state = $json.state;\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "d538d40a-599b-4255-a5e0-83d9b72f671e",
"name": "Get variables",
"type": "n8n-nodes-base.code",
"position": [
-800,
980
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const staticData = $getWorkflowStaticData('global');\n$input.item.json.clientID = staticData.clientID;\n$input.item.json.clientSecret = staticData.clientSecret;\n$input.item.json.state = staticData.state;\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "7c895d16-e522-4b63-bee6-0cea3e3177dd",
"name": "Redirect to Auth Page",
"type": "n8n-nodes-base.form",
"position": [
-360,
620
],
"webhookId": "41b4bc95-2938-4a53-a051-7a3079001329",
"parameters": {
"operation": "completion",
"redirectUrl": "=https:\/\/todoist.com\/oauth\/authorize?client_id={{ $json['Client ID'] }}&scope=task:add,data:read_write,data:delete&state={{ $json.state }}",
"respondWith": "redirect"
},
"typeVersion": 1
},
{
"id": "b72c6f9f-f821-4859-b75d-192541ce0d34",
"name": "OAuth redirect",
"type": "n8n-nodes-base.webhook",
"position": [
-1020,
980
],
"webhookId": "7aee8b09-29e3-4e12-9b66-c6e8ab080bf7",
"parameters": {
"path": "7aee8b09-29e3-4e12-9b66-c6e8ab080bf7",
"options": [],
"httpMethod": [
"GET"
],
"responseMode": "responseNode",
"multipleMethods": true
},
"typeVersion": 2
},
{
"id": "d0adcb66-4817-44f9-ad7e-320cff86aefe",
"name": "Exchange Tokens",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
-360,
880
],
"parameters": {
"url": "https:\/\/todoist.com\/oauth\/access_token",
"method": "POST",
"options": [],
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "client_id",
"value": "={{ $json.clientID }}"
},
{
"name": "client_secret",
"value": "={{ $json.clientSecret }}"
},
{
"name": "code",
"value": "={{ $('OAuth redirect').first().json.query.code }}"
}
]
}
},
"typeVersion": 4.20000000000000017763568394002504646778106689453125
},
{
"id": "4fda8614-308c-4d66-ac7d-c2491ab5a86a",
"name": "Respond with success",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-140,
780
],
"parameters": {
"options": [],
"respondWith": "text",
"responseBody": "=Developer App activated successfully. The window can be closed now."
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "347fb99f-861f-483a-bc39-7341fe41439f",
"name": "Respond with error",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
-140,
980
],
"parameters": {
"options": [],
"respondWith": "text",
"responseBody": "Something went wrong."
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "754727b6-1122-46b2-a956-79ed277c8b32",
"name": "Get Notion Database ID",
"type": "n8n-nodes-base.code",
"position": [
-920,
260
],
"parameters": {
"jsCode": "let database_id = null;\n\nfor (const item of $('Get Notion Databases').all()) {\n if (item.json.name == $('Choose Notion Database').first().json['Select Notion Database']) {\n database_id = item.json.id;\n }\n}\n\nreturn { json: { database_id: database_id } };"
},
"typeVersion": 2
},
{
"id": "565f4144-549e-4adc-b51b-2d87767c3dc3",
"name": "Get Todoist Project ID",
"type": "n8n-nodes-base.code",
"position": [
-40,
260
],
"parameters": {
"jsCode": "let project_id = null;\n\nfor (const item of $('Get projects').all()) {\n if (item.json.name == $('Choose Todoist Project').first().json['Select Todoist Project']) {\n project_id = item.json.id;\n }\n}\n\nreturn { json: { project_id: project_id } };"
},
"typeVersion": 2
},
{
"id": "6f79cd9e-493a-46b2-a3fb-853852a299be",
"name": "Notion-Todoist Sync Setup Helper",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1800,
260
],
"webhookId": "7edf515f-bd69-4875-a5d8-eca8b4679896",
"parameters": {
"options": {
"buttonLabel": "Begin"
},
"formTitle": "Notion-Todoist Sync Setup Helper",
"formFields": {
"values": [
{
"fieldLabel": " ",
"placeholder": "Click \"Begin\" to continue"
}
]
},
"formDescription": "This tool gathers all necessary information and builds a JSON config which is needed as globals in the sync workflows."
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "684e5eea-8148-4ddf-8972-dab890083ce2",
"name": "Todoist Webhook Setup Helper",
"type": "n8n-nodes-base.formTrigger",
"position": [
-1020,
620
],
"webhookId": "feb96ecb-a7c0-41d3-9368-2299cdce492e",
"parameters": {
"options": [],
"formTitle": "Todoist Webhook Setup Helper",
"formFields": {
"values": [
{
"fieldLabel": "Client ID",
"requiredField": true
},
{
"fieldLabel": "Client secret",
"requiredField": true
}
]
},
"responseMode": "lastNode",
"formDescription": "This tool helps activating the Todoist Webhook for a registered Developer App by connecting to the App vie OAuth."
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "05f938b9-f2b3-4535-8fea-5c378bbf7944",
"name": "Return config JSON",
"type": "n8n-nodes-base.form",
"position": [
620,
260
],
"webhookId": "739f32d1-356d-429d-8698-a166485cc36f",
"parameters": {
"options": [],
"operation": "completion",
"completionTitle": "Copy this to the Globals Nodes",
"completionMessage": "={{ $json.toJsonString() }}"
},
"typeVersion": 1
},
{
"id": "e5101689-8b9d-4896-aad6-b39ea2ec654d",
"name": "Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
2980,
600
],
"parameters": {
"filters": {
"projectId": "={{ $('Globals').first().json.project_id }}"
},
"operation": "getAll",
"returnAll": true
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 5000
},
{
"id": "c588831b-99c1-4c4b-9d33-4cb14687d896",
"name": "Notion",
"type": "n8n-nodes-base.notion",
"position": [
2980,
400
],
"parameters": {
"simple": false,
"options": [],
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals').first().json.database_id }}"
},
"filterJson": "={\n \"and\": [\n {\n \"and\": [\n {\n \"property\": \"Status\",\n \"status\": {\n \"does_not_equal\": \"Done\"\n }\n },\n {\n \"property\": \"Status\",\n \"status\": {\n \"does_not_equal\": \"Obsolete\"\n }\n }\n ]\n },\n {\n \"or\": [\n {\n \"property\": \"Focus\",\n \"checkbox\": {\n \"equals\": true\n }\n },\n {\n \"property\": \"Due\",\n \"date\": {\n \"is_not_empty\": true\n }\n }\n ]\n }\n ]\n}",
"filterType": "json"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "72c05668-ed66-4a17-abd1-d848890d23d2",
"name": "Compare Datasets",
"type": "n8n-nodes-base.compareDatasets",
"position": [
3420,
500
],
"parameters": {
"options": [],
"resolve": "mix",
"mergeByFields": {
"values": [
{
"field1": "id",
"field2": "id"
}
]
}
},
"typeVersion": 2.29999999999999982236431605997495353221893310546875
},
{
"id": "4fe3e94e-cd61-49e3-a37c-1a4a45ae990d",
"name": "Exists\/Completed in Notion",
"type": "n8n-nodes-base.if",
"position": [
3880,
1220
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "455d4c70-c774-44d4-b3ba-78754073c2db",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "e6917957-0511-489e-aef1-268d54d1ae1b",
"name": "Get Todoist Task",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"maxTries": 2,
"position": [
4540,
-280
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 2000
},
{
"id": "7ee99bf2-7316-4106-820b-71fb7dc62a69",
"name": "If Todoist ID exists",
"type": "n8n-nodes-base.if",
"position": [
4100,
-180
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "71e9af47-a275-4dd4-a984-0b7ba2cb8733",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "c54b47a5-8c6e-4656-841b-12e64d0dc3af",
"name": "Store Todoist ID",
"type": "n8n-nodes-base.notion",
"position": [
5200,
-180
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion').item.json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $json.id }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "29ccfeb0-f3c1-4141-a7ba-211ae6195e63",
"name": "Mark as Incomplete in Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
-480
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "reopen"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 5000
},
{
"id": "7322844b-0f2b-4f33-9aa6-117845bf90e5",
"name": "Mark as Completed in Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
1120
],
"parameters": {
"taskId": "={{ $('Todoist').item.json.id }}",
"operation": "close"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "44d117ae-f3a1-47ae-8e6d-c668e721add7",
"name": "Delete Task in Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
4980,
1320
],
"parameters": {
"taskId": "={{ $('Todoist').item.json.id }}",
"operation": "delete"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "57176531-6186-40d4-9306-65eea976583f",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
3660,
1320
],
"parameters": {
"options": []
},
"typeVersion": 3
},
{
"id": "25bc1156-cfa2-4633-a635-f7bb2f4f25f2",
"name": "Get Notion Task",
"type": "n8n-nodes-base.notion",
"position": [
3880,
1420
],
"parameters": {
"limit": 1,
"simple": false,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Todoist').item.json.id }}"
}
]
},
"options": [],
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"alwaysOutputData": true,
"waitBetweenTries": 5000
},
{
"id": "01ec4ae8-22df-45ef-8593-4f16e3571c2e",
"name": "Update Task in Todoist",
"type": "n8n-nodes-base.httpRequest",
"position": [
3880,
520
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks\/{{ $json.id }}",
"method": "POST",
"options": [],
"jsonQuery": "={{ $json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "4961331b-7cab-4c5e-b700-cb35e9258426",
"name": "Pick Todoist Fields",
"type": "n8n-nodes-base.set",
"position": [
3200,
600
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "18fbd156-06db-4991-bee7-14d8bde6bbf5",
"name": "id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "a0c01dca-6cfa-4461-8c93-fe3f2191d624",
"name": "content",
"type": "string",
"value": "={{ $json.content }}"
},
{
"id": "a9bae78e-1a70-4b8e-be0c-ce74e546b0de",
"name": "priority",
"type": "string",
"value": "={{ $json.priority }}"
},
{
"id": "6ed4364e-6f62-464d-a841-e520b5bf3d96",
"name": "due_datetime",
"type": "string",
"value": "={{ $if($json.due.datetime !== undefined, $json.due.datetime.toDateTime().toISO(), $json.due.date) }}"
},
{
"id": "23035cde-aea0-459d-9886-06cd7f20dd61",
"name": "description",
"type": "string",
"value": "={{ $json.description }}"
},
{
"id": "6795fa58-4d52-4e88-8a1c-10e574ed2c3d",
"name": "section_id",
"type": "string",
"value": "={{ $json.section_id }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "283370f2-93ff-4272-82bf-ad45c54e47f7",
"name": "Map Notion to Todoist",
"type": "n8n-nodes-base.code",
"position": [
3200,
400
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nconst properties = $json.properties;\n\nlet output = {};\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[\u2197 Open in Notion](\" + $json.url + \")\"\n\n\/\/ Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; \/\/ P4\n} else {\n output.priority = {\n \"do first\": \"4\", \/\/ P1\n \"urgent\": \"3\", \/\/ P2\n \"important\": \"2\" \/\/ P3\n }[properties['Priority'].select.name] ?? \"1\"; \/\/ P4\n}\n\n\/\/ Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n\/\/ Set UTC if time is set\noutput.due_datetime = null;\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start;\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "ca33c90f-3204-45e1-9d66-20e7e740a4d7",
"name": "Update task in Todoist before closing",
"type": "n8n-nodes-base.httpRequest",
"position": [
4540,
1120
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks\/{{ $json.id }}",
"method": "POST",
"options": [],
"jsonQuery": "={{ $json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "dd79e458-8c60-4de8-bdde-ec0009b2d4dc",
"name": "Map Notion to Todoist1",
"type": "n8n-nodes-base.code",
"position": [
4100,
1120
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nconst properties = $json.properties;\n\nlet output = {};\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[\u2197 Open in Notion](\" + $json.url + \")\"\n\n\/\/ Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; \/\/ P4\n} else {\n output.priority = {\n \"do first\": \"4\", \/\/ P1\n \"urgent\": \"3\", \/\/ P2\n \"important\": \"2\" \/\/ P3\n }[properties['Priority'].select.name] ?? \"1\"; \/\/ P4\n}\n\n\/\/ Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n\/\/ Set UTC if time is set\noutput.due_datetime = null;\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start;\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "7dec20dc-c1a9-4fd7-85b0-c3425a73ff66",
"name": "Handle empty dates",
"type": "n8n-nodes-base.code",
"position": [
3660,
520
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\/\/ API requires due_string instead of due_datetime when date\/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "b1bf65ad-b9b1-49ba-a892-678966c520a3",
"name": "Handle empty dates1",
"type": "n8n-nodes-base.code",
"position": [
4320,
1120
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\/\/ API requires due_string instead of due_datetime when date\/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "8dbd2213-4a7f-44ef-9276-67672c6bb1ec",
"name": "Handle empty dates2",
"type": "n8n-nodes-base.code",
"position": [
3660,
-180
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\/\/ API requires due_string instead of due_datetime when date\/time is removed\nif ($json.due_datetime === null) {\n delete $json.due_datetime;\n $input.item.json.due_string = \"no date\";\n}\n\nreturn $input.item;"
},
"typeVersion": 2
},
{
"id": "4a538bb6-80f6-4920-b96a-0756e0dc5449",
"name": "Create task in Todoist",
"type": "n8n-nodes-base.httpRequest",
"position": [
4980,
-280
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks",
"method": "POST",
"options": [],
"jsonQuery": "={{ $('If Todoist ID exists').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "afdad4ec-e8bc-48c0-b80d-e1f4066871c2",
"name": "Add project ID",
"type": "n8n-nodes-base.set",
"position": [
3880,
-180
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "38aef493-8125-46fd-82bc-93c6d7d6f66e",
"name": "project_id",
"type": "string",
"value": "={{ $('Globals').first().json.project_id }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "fb2f5730-5a69-4bed-983f-9597da7a0065",
"name": "Lock Todoist ID",
"type": "n8n-nodes-base.redis",
"position": [
5200,
-380
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "8f4e1ffd-a46c-4161-b6ed-89c0e78c47b1",
"name": "Generate UUID",
"type": "n8n-nodes-base.crypto",
"position": [
6080,
-80
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "28b4e10e-a9cb-40c0-9a2c-d45303f4b64e",
"name": "Update section (Sync API)",
"type": "n8n-nodes-base.httpRequest",
"position": [
6740,
-80
],
"parameters": {
"url": "https:\/\/api.todoist.com\/sync\/v9\/sync",
"method": "POST",
"options": [],
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "={{ $json.commands.toJsonString() }}"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "82cabb00-43bf-404a-9b3b-0a95bee0ba26",
"name": "Status changed",
"type": "n8n-nodes-base.filter",
"position": [
4100,
520
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "d4242f35-f49f-4b3e-a99d-dacc90ea912a",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
6520,
-80
],
"parameters": {
"options": [],
"aggregate": "aggregateAllItemData",
"destinationFieldName": "commands"
},
"typeVersion": 1
},
{
"id": "4f6d649b-94c7-4826-950c-1f1632037d56",
"name": "Prepare Batching Items",
"type": "n8n-nodes-base.set",
"position": [
6300,
-80
],
"parameters": {
"mode": "raw",
"options": [],
"jsonOutput": "={\n \"type\": \"item_move\",\n \"uuid\": \"{{ $json.uuid }}\",\n \"args\": {\n \"id\": \"{{ $('Merge').item.json.item.id }}\",\n \"section_id\": \"{{ $json.item.section_id }}\"\n }\n }\n"
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "4d5993d2-13af-47e3-b17a-9c19db7d0489",
"name": "Loop Over Items1",
"type": "n8n-nodes-base.splitInBatches",
"position": [
5860,
-180
],
"parameters": {
"options": [],
"batchSize": 100
},
"typeVersion": 3
},
{
"id": "5235aab4-70f2-4d21-b9d3-a11ab817dd13",
"name": "Status is Done",
"type": "n8n-nodes-base.if",
"position": [
4760,
1220
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a4a40d08-7929-46da-8369-c6d33b14e4f3",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Exists\/Completed in Notion').item.json.properties['Status'].status.name }}",
"rightValue": "Done"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "608efb15-5e9b-49c2-96ea-ad35445c82bd",
"name": "Lock Todoist ID1",
"type": "n8n-nodes-base.redis",
"position": [
4760,
1020
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "2d571e77-6162-4f5c-a147-99878d5b559a",
"name": "Lock Todoist ID2",
"type": "n8n-nodes-base.redis",
"position": [
4100,
320
],
"parameters": {
"key": "=lock_{{ $('Compare Datasets').item.json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "cee5f777-4763-490e-a89f-8ba715cfd5b8",
"name": "Update task in Todoist",
"type": "n8n-nodes-base.httpRequest",
"position": [
5200,
-580
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks\/{{ $('If Todoist ID exists').item.json.id }}",
"method": "POST",
"options": [],
"jsonQuery": "={{ $('If Todoist ID exists').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 5000
},
{
"id": "0b2c57af-c3b6-4bed-8e4c-ec2f84576a47",
"name": "Get mapped item",
"type": "n8n-nodes-base.set",
"position": [
5420,
-580
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "7419f1e6-2978-4e4d-9147-61dcb693260b",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "7684427f-3267-4092-a5d3-0d13fe4e2ebb",
"name": "action",
"type": "string",
"value": "reopened"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "e6d335b2-e848-4802-9809-15ea9eb3fd29",
"name": "Get mapped item1",
"type": "n8n-nodes-base.set",
"position": [
5000,
520
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "7679df9c-ccdb-425c-ad60-c8bd85379e87",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "d6bc106f-ed01-49be-9c27-13429684d573",
"name": "action",
"type": "string",
"value": "updated_status"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "ce49a060-0a2b-491f-b6f5-efa49b54e8d3",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2760,
500
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 6
}
]
}
},
"typeVersion": 1.1999999999999999555910790149937383830547332763671875
},
{
"id": "4402861c-7b7e-47c4-9e5c-91589262c556",
"name": "Prepare summary data",
"type": "n8n-nodes-base.set",
"position": [
4100,
720
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "2d823132-c41f-4685-9443-8ee143809ccb",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "45b155c2-4254-45b3-bdd4-127ee356f8d4",
"name": "action",
"type": "string",
"value": "updated"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "3a26cfc0-3e4e-4e14-80ef-2ad00236ae38",
"name": "Prepare summary data1",
"type": "n8n-nodes-base.set",
"position": [
5200,
1120
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "9614bc29-d8fa-4531-a8fc-5974f58e0f7a",
"name": "item",
"type": "object",
"value": "={{ $('Todoist').item.json }}"
},
{
"id": "6c0ffc23-a595-49ce-82b9-b02c3f9766d8",
"name": "action",
"type": "string",
"value": "completed"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "a3e4e83d-6789-4c55-9699-41da5eeac9e4",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
5640,
-180
],
"parameters": [],
"typeVersion": 3
},
{
"id": "9b93f9ff-0c11-49eb-8425-7d3dc71eaa0b",
"name": "Prepare summary data2",
"type": "n8n-nodes-base.set",
"position": [
5200,
1320
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "833becab-363d-48f1-8911-12abd34f16a0",
"name": "item",
"type": "object",
"value": "={{ $('Todoist').item.json }}"
},
{
"id": "306997a6-442e-44cc-ace1-c9bfe83ef28e",
"name": "action",
"type": "string",
"value": "deleted"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "f6ea7bc7-093b-47ea-8bb0-48a487566218",
"name": "Filter out status changes",
"type": "n8n-nodes-base.filter",
"position": [
6100,
720
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "db22023d-a3ff-4891-8c14-5316be7e5334",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.action }}",
"rightValue": "updated_status"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "63990a2a-7380-4799-9037-bd98ed779ddd",
"name": "Map Todoist to Notion",
"type": "n8n-nodes-base.code",
"position": [
6320,
720
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals').first().json;\nlet eventData = $json.item;\n\nlet output = {};\n\noutput.name = eventData.content\n\n\/\/ Map priority\noutput.priority = {\n \"4\": \"do first\", \/\/ P1\n \"3\": \"urgent\", \/\/ P2\n \"2\": \"important\" \/\/ P3\n}[eventData.priority] ?? \"\"; \/\/ P4\n\n\/\/ Map section\nconst sectionId = eventData.section_id;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.id, section.name])\n);\n\noutput.status = \"Backlog\";\nif (sectionId !== null) {\n if (!sectionMap.hasOwnProperty(sectionId)) {\n \/\/throw new Error(\"No status name found for section ID '\" + sectionId + \"'\");\n }\n output.status = sectionMap[sectionId];\n}\n\n\/\/ Since there is no Done section in Todoist, override status if completed\nif (eventData.is_completed) {\n output.status = \"Done\";\n}\n\nif ($json.action == \"deleted\") {\n output.status = \"Obsolete\";\n}\n\n\/\/ Format due date\noutput.due = eventData.due ? eventData.due.date : eventData.due_datetime || \"\"\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "a5f3a1ed-1a97-441e-afbc-604b5011928e",
"name": "Map summary fields",
"type": "n8n-nodes-base.set",
"position": [
6540,
720
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "e0fcdd39-1bd8-44e7-b84f-0b6ba834fb46",
"name": "name",
"type": "string",
"value": "=<a href=\"{{ $('Merge summary').item.json.item.description.extractUrl().replaceAll(')', '') }}\">{{ $json.name }}<\/a>"
},
{
"id": "f3bdfee8-1671-4e80-b8da-febe6f463756",
"name": "priority",
"type": "string",
"value": "={{ $json.priority }}"
},
{
"id": "060bfe55-0dfa-406b-99cc-ad8592a209dc",
"name": "status",
"type": "string",
"value": "={{ $json.status }}"
},
{
"id": "7b0ee321-0631-4cde-b1a7-b3547aff3561",
"name": "due",
"type": "string",
"value": "={{ $if(!$json.due, '', $if($json.due.length > 10, $json.due.toDateTime().toLocaleString({ dateStyle: 'medium', timeStyle: 'short' }), $json.due.toDateTime().toLocaleString({ dateStyle: 'medium' }))) }}"
},
{
"id": "11184572-1126-4b21-aa72-1540969200f3",
"name": "action",
"type": "string",
"value": "={{ $('Merge summary').item.json.action }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "50a844e8-5c5c-43e6-b774-993af828cb11",
"name": "Convert to HTML Table",
"type": "n8n-nodes-base.html",
"position": [
6760,
720
],
"parameters": {
"options": {
"capitalize": true
},
"operation": "convertToHtmlTable"
},
"typeVersion": 1.1999999999999999555910790149937383830547332763671875
},
{
"id": "53e56e25-4931-4a1a-b70f-1eda8520c649",
"name": "Generate email body",
"type": "n8n-nodes-base.html",
"position": [
6980,
720
],
"parameters": {
"html": "<!DOCTYPE html>\n<html>\n<head>\n <style>\n body {\n font-family: Arial, sans-serif;\n line-height: 1.6;\n color: #333;\n margin: 0;\n padding: 20px;\n }\n .container {\n max-width: 800px;\n margin: 0 auto;\n background-color: #ffffff;\n padding: 20px;\n border-radius: 5px;\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n h1 {\n color: #2c3e50;\n text-align: center;\n margin-bottom: 30px;\n padding-bottom: 10px;\n border-bottom: 2px solid #3498db;\n }\n table {\n width: 100%;\n border-collapse: collapse;\n margin-top: 20px;\n }\n th {\n background-color: #3498db;\n color: white;\n padding: 12px;\n text-align: left;\n }\n td {\n padding: 10px;\n border-bottom: 1px solid #ddd;\n }\n tr:nth-child(even) {\n background-color: #f9f9f9;\n }\n tr:hover {\n background-color: #f5f5f5;\n }\n a {\n color: #333;\n text-decoration: underline;\n }\n <\/style>\n<\/head>\n<body>\n <div class=\"container\">\n <h1>Notion-Todoist Sync Report<\/h1>\n <p>The following changes have been applied to Todoist:<\/p>\n {{ $json.table }}\n <\/div>\n<\/body>\n<\/html>\n"
},
"typeVersion": 1.1999999999999999555910790149937383830547332763671875
},
{
"id": "aa0507f8-e5b4-45b1-89bb-6ced3dd9b27e",
"name": "Get mapped item2",
"type": "n8n-nodes-base.set",
"position": [
5420,
-180
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "7679df9c-ccdb-425c-ad60-c8bd85379e87",
"name": "item",
"type": "object",
"value": "={{ $('Map Notion to Todoist').item.json }}"
},
{
"id": "d6bc106f-ed01-49be-9c27-13429684d573",
"name": "action",
"type": "string",
"value": "created"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "60826717-078e-4509-9dbd-8b3896078a9c",
"name": "Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
7200,
720
],
"webhookId": "5af8a60e-a295-40c1-8f03-0a920ba1de17",
"parameters": {
"message": "={{ $json.html }}",
"options": {
"appendAttribution": false
},
"subject": "Notion-Todoist Sync Report"
},
"credentials": {
"gmailOAuth2": {
"id": "TWM2BLjDHQtGAFQn",
"name": "Gmail (octionicsolutions)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "29ea508e-eba9-40a3-9840-d70d4ec51ddb",
"name": "Merge summary",
"type": "n8n-nodes-base.merge",
"position": [
5880,
680
],
"parameters": {
"numberInputs": 5
},
"typeVersion": 3
},
{
"id": "9773c5d4-a910-4b26-858d-2abf30c9602f",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
260
],
"parameters": {
"color": 7,
"width": 220,
"height": 300,
"content": "## Keep in sync\nif you update the mapping, make sure to change it in the other node as well!"
},
"typeVersion": 1
},
{
"id": "e4224f99-4063-49a2-a5cd-c2515b117bc5",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
4040,
980
],
"parameters": {
"color": 7,
"width": 220,
"height": 300,
"content": "## Keep in sync\nif you update the mapping, make sure to change it in the other node as well!"
},
"typeVersion": 1
},
{
"id": "3d5662ad-81a4-4a02-abe3-38d59a08bf9f",
"name": "Sticky Note10",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
20
],
"parameters": {
"width": 220,
"height": 300,
"content": "## Set Globals\nUse Sync Setup Helper Workflow to generate the JSON and paste it in every Globals Nodes"
},
"typeVersion": 1
},
{
"id": "e78779d9-32f9-467f-ae2c-ad0cec69f506",
"name": "Globals",
"type": "n8n-nodes-base.set",
"position": [
2980,
160
],
"parameters": {
"mode": "raw",
"options": [],
"jsonOutput": "{\n \"database_id\": \"165beaa8f4448063be17d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": false,
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "f01855a1-e9bf-469a-ad68-bef8d0286e3a",
"name": "Sticky Note14",
"type": "n8n-nodes-base.stickyNote",
"position": [
5800,
-260
],
"parameters": {
"color": 7,
"width": 1100,
"height": 380,
"content": "Update section for each task in Todoist using the Sync API in full sync mode. Use batching and split into multiple batches, if needed, since there is a request limit of 100 full syncs within a 15 minute period."
},
"typeVersion": 1
},
{
"id": "2ec59ee5-5ffb-4b57-9a2d-0f7d10a580ba",
"name": "Sticky Note15",
"type": "n8n-nodes-base.stickyNote",
"position": [
2640,
-740
],
"parameters": {
"color": 5,
"width": 4760,
"height": 2420,
"content": "# Unidirectional sync from Notion to Todoist"
},
"typeVersion": 1
},
{
"id": "a6f87f39-6885-4fff-99f0-1f29f0db25b7",
"name": "Sticky Note16",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
320
],
"parameters": {
"color": 7,
"width": 220,
"height": 440,
"content": "Get open\/incomplete tasks from both Notion and Todoist"
},
"typeVersion": 1
},
{
"id": "fcffe10e-4ab3-4172-8be3-d060a4070922",
"name": "Sticky Note17",
"type": "n8n-nodes-base.stickyNote",
"position": [
6040,
660
],
"parameters": {
"color": 7,
"width": 1100,
"height": 220,
"content": "Generate an email report containing all changes made by this workflow, since changes in Todoist are being overwritten."
},
"typeVersion": 1
},
{
"id": "af70e38e-eca7-4eba-9125-69a8ff222ac0",
"name": "Execute Workflow Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"disabled": true,
"position": [
2800,
3540
],
"parameters": [],
"typeVersion": 1
},
{
"id": "8e09225d-5d7b-468e-a8f6-c0836469799a",
"name": "Differences exist1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
2720
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "db8871d6-7f3f-468e-9a6e-3975c9f003f5",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.name }}",
"rightValue": "={{ $json.name }}"
},
{
"id": "df493beb-52a8-47f7-90c6-fb5786b1deed",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.priority }}",
"rightValue": "={{ $json.property_priority }}"
},
{
"id": "8942df51-6aa3-46d6-91f3-c6949bfd2361",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Todoist to Notion1').item.json.status }}",
"rightValue": "={{ $json.property_status }}"
},
{
"id": "5484e1f8-4fbc-4c63-829a-4b45b9090702",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $if($('Map Todoist to Notion1').item.json.due !== null, $('Map Todoist to Notion1').item.json.due.toDateTime().toISO(), null) }}",
"rightValue": "={{ $if($json.property_due !== null, $json.property_due.start.toDateTime().toISO(), null) }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "d30a9839-6ff4-49d3-8413-4c4786c47a0a",
"name": "Requires completion change1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
3100
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b9e134c4-66a7-4a83-8372-282c19fdc98e",
"operator": {
"type": "boolean",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Todoist Task1').item.json.is_completed }}",
"rightValue": "={{ $if($json.property_status == \"Done\", true, false) }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "38edc95d-ae92-46bb-9cc5-338588d720af",
"name": "Create task in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7420,
2220
],
"parameters": {
"title": "={{ $('Map Todoist to Notion1').item.json.name }}",
"options": [],
"resource": "databasePage",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}"
},
{
"key": "Priority|select",
"selectValue": "={{ $('Map Todoist to Notion1').item.json.priority }}"
},
{
"key": "Status|status",
"statusValue": "={{ $('Map Todoist to Notion1').item.json.status }}"
},
{
"key": "Due|date",
"date": "={{ $('Map Todoist to Notion1').item.json.due || '' }}",
"includeTime": "={{ $('Map Todoist to Notion1').item.json.due !== null && $('Map Todoist to Notion1').item.json.due.length > 10 }}"
},
{
"key": "Focus|checkbox",
"checkboxValue": true
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "fd60280f-aef7-43f6-835c-0086adad3952",
"name": "Update task in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7640,
2720
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Name|title",
"title": "={{ $('Map Todoist to Notion1').item.json.name }}"
},
{
"key": "Priority|select",
"selectValue": "={{ $('Map Todoist to Notion1').item.json.priority }}"
},
{
"key": "Status|status",
"statusValue": "={{ $('Map Todoist to Notion1').item.json.status }}"
},
{
"key": "Due|date",
"date": "={{ $ifEmpty($('Map Todoist to Notion1').item.json.due, \"\") }}",
"includeTime": "={{ $('Map Todoist to Notion1').item.json.due !== null && $('Map Todoist to Notion1').item.json.due.length > 10 }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "5aecc1ce-f83b-43a2-b2a3-44b00b59f26b",
"name": "Mark as Done in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3380
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "Done"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "fb2c809e-50ad-4627-ad7e-4919cf30b845",
"name": "Mark as Obsolete in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3700
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "Obsolete"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "6cb3caa1-45f5-4675-ae06-5c15163ccac1",
"name": "Mark as In Progress in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7640,
3180
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Status|status",
"statusValue": "In progress"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "7aa800dd-620a-40b3-86a3-9fc07aeb51fb",
"name": "Only continue if not locked",
"type": "n8n-nodes-base.filter",
"position": [
3860,
2900
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "ddf2093b-3528-4141-9694-0bc164851919",
"name": "Lock Notion ID",
"type": "n8n-nodes-base.redis",
"position": [
8300,
3040
],
"parameters": {
"key": "=lock_{{ $json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "05aac18c-e591-430e-9bc9-93a581cb0e89",
"name": "Check if Todoist ID is locked",
"type": "n8n-nodes-base.redis",
"position": [
3640,
2900
],
"parameters": {
"key": "=lock_{{ $json.body.event_data.id }}",
"keyType": "string",
"options": [],
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "4df2d7a9-ca8e-4b64-a797-1cf9ec4786cc",
"name": "Update Description in Todoist",
"type": "n8n-nodes-base.todoist",
"position": [
7860,
2220
],
"parameters": {
"taskId": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}",
"operation": "update",
"updateFields": {
"description": "={{ \"[\u2197 Open in Notion](\" + $('Create task in Notion').item.json.url + \")\" }}"
}
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "a2b07adf-6e01-4bbc-a852-8b59fb20046f",
"name": "Description has changed in Todoist",
"type": "n8n-nodes-base.filter",
"position": [
6980,
2920
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "db8871d6-7f3f-468e-9a6e-3975c9f003f5",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Todoist Task1').item.json.description }}",
"rightValue": "={{ \"[\u2197 Open in Notion](\" + $json.url + \")\" }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "5fe846b7-9869-4d1f-a6f0-6cd14e3127d4",
"name": "Update Description in Todoist1",
"type": "n8n-nodes-base.todoist",
"position": [
7200,
2920
],
"parameters": {
"taskId": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}",
"operation": "update",
"updateFields": {
"description": "={{ \"[\u2197 Open in Notion](\" + $json.url + \")\" }}"
}
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "879b47ad-5d32-4a40-ad61-690762e202b0",
"name": "Generate UUID1",
"type": "n8n-nodes-base.crypto",
"position": [
7420,
2440
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "106f7e35-0cc2-4054-817c-6c3d50407e88",
"name": "Update section (Sync API)1",
"type": "n8n-nodes-base.httpRequest",
"position": [
7640,
2440
],
"parameters": {
"url": "https:\/\/api.todoist.com\/sync\/v9\/sync",
"method": "POST",
"options": [],
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "=[ { \"type\": \"item_move\", \"uuid\": \"{{ $json.uuid }}\", \"args\": { \"id\": \"{{ $('Map Todoist to Notion1').item.json.todoist_id }}\", \"section_id\": \"{{ $json.section_id }}\" } }]"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.20000000000000017763568394002504646778106689453125
},
{
"id": "20d2cdc2-92ca-497d-a606-445ecf52e908",
"name": "Get Backlog Section ID",
"type": "n8n-nodes-base.code",
"position": [
7200,
2440
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals1').first().json;\n\nlet output = {};\n\nconst statusName = $('Map Todoist to Notion1').item.json.status;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n);\nif (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n}\noutput.section_id = sectionMap[statusName];\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "72819157-59db-4a4e-ad13-1bf73c583be7",
"name": "Notion Task not found",
"type": "n8n-nodes-base.if",
"position": [
6280,
2760
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "27ce7ba1-43e5-4534-a759-1bafb979c190",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "3b5d3da8-e8b6-46d0-8706-88a9d1a5e95a",
"name": "Check if creating flag exists1",
"type": "n8n-nodes-base.redis",
"position": [
6540,
2440
],
"parameters": {
"key": "=creating_for_{{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"keyType": "string",
"options": [],
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "1dc24cdd-63e7-4307-a945-6cc07babd535",
"name": "Only continue if flag does not exist1",
"type": "n8n-nodes-base.filter",
"position": [
6760,
2440
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "2e64fc80-428b-4f14-a7a1-db158bc58b06",
"name": "Set creating flag1",
"type": "n8n-nodes-base.redis",
"position": [
6980,
2440
],
"parameters": {
"key": "=creating_for_{{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"ttl": 10,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "4a2cb838-40d6-4e16-bb41-c04548d3394c",
"name": "Neither focussed nor planned1",
"type": "n8n-nodes-base.filter",
"position": [
7420,
3020
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "95c47aa4-249c-4235-89c2-44c16c5b65bb",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.property_focus }}",
"rightValue": ""
},
{
"id": "a611c1d2-d01a-4f5a-8ad8-cfaa65b11202",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.property_due }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "56df5752-6beb-4ebe-8b6c-1b5752633db3",
"name": "Mark as Focussed in Notion",
"type": "n8n-nodes-base.notion",
"position": [
7640,
2920
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Focus|checkbox",
"checkboxValue": true
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "e031cb0d-41bf-4273-a198-5485cdaa3758",
"name": "Todoist trigger reference",
"type": "n8n-nodes-base.noOp",
"position": [
3200,
2900
],
"parameters": [],
"typeVersion": 1
},
{
"id": "539bac50-20c8-4212-aaa5-a99b7f2c3cdb",
"name": "Get Notion Task2",
"type": "n8n-nodes-base.notion",
"position": [
6760,
3700
],
"parameters": {
"limit": 1,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Todoist trigger reference').item.json.body.event_data.id }}"
}
]
},
"options": [],
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "ObmaBA0dJss3JJPv",
"name": "Notion (Test)"
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"alwaysOutputData": true
},
{
"id": "1585da72-5578-456b-a092-21e66b033350",
"name": "Notion Task found1",
"type": "n8n-nodes-base.filter",
"position": [
6980,
3700
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d644079f-b140-42b3-85c5-f1283a6d21d8",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "c5df6b20-a7ed-4e57-9814-091f76ee677b",
"name": "Switch by Event",
"type": "n8n-nodes-base.switch",
"position": [
6760,
2920
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "item:updated",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4ffd3f5d-dc24-47cb-a4df-1a6f6a6dc2b9",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:updated"
}
]
},
"renameOutput": true
},
{
"outputKey": "item:completed",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d5290195-6595-4c91-af86-6df724bb0980",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:completed"
}
]
},
"renameOutput": true
},
{
"outputKey": "item:uncompleted",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e047ade5-ce68-4bc8-b799-7130bd0eb917",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:uncompleted"
}
]
},
"renameOutput": true
}
]
},
"options": []
},
"typeVersion": 3.20000000000000017763568394002504646778106689453125
},
{
"id": "74859244-46a3-45a2-8e90-923c6bdc0d40",
"name": "If event is not :deleted",
"type": "n8n-nodes-base.if",
"position": [
4080,
2900
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "4fb65ecf-dbb9-4bd1-a5a8-614441aa2963",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Todoist trigger reference').item.json.body.event_name }}",
"rightValue": "item:deleted"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "f3404606-51e8-4b53-b332-20f9cf26a969",
"name": "Execution Data",
"type": "n8n-nodes-base.executionData",
"position": [
3420,
2900
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "database_id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
{
"key": "project_id",
"value": "={{ $('Globals1').first().json.project_id }}"
},
{
"key": "todoist_id",
"value": "={{ $json.body.event_data.id }}"
},
{
"key": "source",
"value": "Todoist"
}
]
}
},
"typeVersion": 1
},
{
"id": "643b67db-43e2-47fb-8f7f-1c5ad7b780fc",
"name": "Execution Data1",
"type": "n8n-nodes-base.executionData",
"position": [
5840,
2760
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "task_name",
"value": "={{ $json.name }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "ad4ff5a0-ab39-48f9-82fd-9f920b8160d5",
"name": "Execution Data2",
"type": "n8n-nodes-base.executionData",
"position": [
7640,
2220
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "ee82b160-35ce-4943-b8f7-6297382f0656",
"name": "Execution Data3",
"type": "n8n-nodes-base.executionData",
"position": [
6540,
2920
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "591f2447-1a81-436c-b073-838cb3011f51",
"name": "Execution Data4",
"type": "n8n-nodes-base.executionData",
"position": [
7200,
3700
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "notion_id",
"value": "={{ $json.id }}"
},
{
"key": "task_name",
"value": "={{ $json.name }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "558f1a95-56ad-43c0-b924-b8a6ad50cf36",
"name": "Globals1",
"type": "n8n-nodes-base.set",
"position": [
3420,
2640
],
"parameters": {
"mode": "raw",
"options": [],
"jsonOutput": "{\n \"database_id\": \"165beaa8-f444-8063-be17-d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": true,
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "3f39ff17-8700-475e-9ae7-ef3dbef299af",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
3360,
2500
],
"parameters": {
"width": 220,
"height": 300,
"content": "## Set Globals\nUse Sync Setup Helper Workflow to generate the JSON and paste it in every Globals Nodes"
},
"typeVersion": 1
},
{
"id": "44393b98-739a-4478-a485-67b8bb59e901",
"name": "Map Todoist to Notion1",
"type": "n8n-nodes-base.code",
"position": [
5620,
2760
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals1').first().json;\nlet eventData = $json;\n\nlet output = {};\n\noutput.todoist_id = eventData.id;\n\noutput.name = eventData.content\n\n\/\/ Map priority\noutput.priority = {\n \"4\": \"do first\", \/\/ P1\n \"3\": \"urgent\", \/\/ P2\n \"2\": \"important\" \/\/ P3\n}[eventData.priority] ?? \"\"; \/\/ P4\n\n\/\/ Map section\nconst sectionId = eventData.section_id;\nconst sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.id, section.name])\n);\n\noutput.status = \"Backlog\";\nif (sectionId !== null) {\n if (!sectionMap.hasOwnProperty(sectionId)) {\n throw new Error(\"No status name found for section ID '\" + sectionId + \"'\");\n }\n output.status = sectionMap[sectionId];\n}\n\n\/\/ Since there is no Done section in Todoist, override status if completed\nif (eventData.is_completed) {\n output.status = \"Done\";\n}\n\n\/\/ Map date and time\noutput.due = null;\nif (eventData.due !== null) {\n output.due = eventData.due.date\n if (eventData.due.datetime !== undefined) {\n output.due = eventData.due.datetime\n }\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "9c9bf784-1318-4891-a6d8-3303aa09bd82",
"name": "Get Todoist Task1",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
4740,
2760
],
"parameters": {
"taskId": "={{ $('Todoist trigger reference').item.json.body.event_data.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 3000
},
{
"id": "0d64962d-079a-4991-9442-8cf1f18b4b4f",
"name": "Get Notion Task1",
"type": "n8n-nodes-base.notion",
"position": [
6060,
2760
],
"parameters": {
"limit": 1,
"filters": {
"conditions": [
{
"key": "Todoist ID|rich_text",
"condition": "equals",
"richTextValue": "={{ $('Map Todoist to Notion1').item.json.todoist_id }}"
}
]
},
"options": [],
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Globals1').first().json.database_id }}"
},
"filterType": "manual"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"alwaysOutputData": true
},
{
"id": "0380f6c3-a3e8-4dba-8e45-f98a53dfa60f",
"name": "Sticky Note23",
"type": "n8n-nodes-base.stickyNote",
"position": [
5780,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Store more information, which was not available earlier"
},
"typeVersion": 1
},
{
"id": "a36cf7d4-d467-428b-b6cc-2ff7c9ccd1ad",
"name": "Sticky Note26",
"type": "n8n-nodes-base.stickyNote",
"position": [
7360,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Set focussed flag, so it remains in Todoist even after updating in Notion"
},
"typeVersion": 1
},
{
"id": "79a16ed2-2cc9-4dc0-9efd-996ec3409b73",
"name": "Sticky Note27",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
1880
],
"parameters": {
"color": 7,
"width": 1800,
"height": 760,
"content": "**Create Notion page** if it does not exist"
},
"typeVersion": 1
},
{
"id": "124820ca-86d1-4b99-8437-2fcdc399cef7",
"name": "Sticky Note28",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
2660
],
"parameters": {
"color": 7,
"width": 1340,
"height": 900,
"content": "**Update Notion page** if it needs to be changed"
},
"typeVersion": 1
},
{
"id": "597e44ec-8c2c-4e2e-9d8c-e9fd8495dcd3",
"name": "Sticky Note29",
"type": "n8n-nodes-base.stickyNote",
"position": [
3580,
2800
],
"parameters": {
"color": 7,
"width": 440,
"height": 280,
"content": "Checking for a cached flag in Redis, to prevent overrides and endless loops."
},
"typeVersion": 1
},
{
"id": "88302c02-82d5-4e5d-9716-49e01d4d1b12",
"name": "Sticky Note31",
"type": "n8n-nodes-base.stickyNote",
"position": [
6000,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Retrieve Notion page, so it can be compared against"
},
"typeVersion": 1
},
{
"id": "31f3c572-4f38-4133-85ff-699ab6359d0e",
"name": "Sticky Note32",
"type": "n8n-nodes-base.stickyNote",
"position": [
6480,
2820
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Store more information, which was not available earlier"
},
"typeVersion": 1
},
{
"id": "f2f6d6b4-3c6e-46ef-906b-7293d16a0ce2",
"name": "Sticky Note33",
"type": "n8n-nodes-base.stickyNote",
"position": [
3360,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Store relevant information in highlighted execution data, in case of debugging"
},
"typeVersion": 1
},
{
"id": "3529a085-c1dc-4467-927f-cebbbc7ec128",
"name": "Sticky Note35",
"type": "n8n-nodes-base.stickyNote",
"position": [
2640,
1780
],
"parameters": {
"color": 5,
"width": 5880,
"height": 2160,
"content": "## Trigger based synchronization from Todoist to Notion (part of bi-directional sync)"
},
"typeVersion": 1
},
{
"id": "7bd78d2f-8452-48d0-8b85-c2ec9023336f",
"name": "Sticky Note36",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Following nodes reference to this node, so the trigger can easily be replaced"
},
"typeVersion": 1
},
{
"id": "e87ad29c-5eb8-4abb-86a2-d2dd937ab81d",
"name": "Sticky Note37",
"type": "n8n-nodes-base.stickyNote",
"position": [
6480,
2380
],
"parameters": {
"color": 7,
"width": 660,
"height": 240,
"content": "This prevents the creation of duplicates in Notion, by storing a special flag in Redis containing the Todoist ID"
},
"typeVersion": 1
},
{
"id": "e496f9f0-28be-4ae2-8a69-47805e858724",
"name": "Requires completion change",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2560
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b9e134c4-66a7-4a83-8372-282c19fdc98e",
"operator": {
"type": "boolean",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.is_completed }}",
"rightValue": "={{ $json.is_completed }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "6f0f1698-638a-49ec-af54-8a11ddfb4a3a",
"name": "Has been completed?",
"type": "n8n-nodes-base.if",
"position": [
1440,
2560
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a8595a9c-fc03-4bb1-b1fc-ecde223d19aa",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.is_completed }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "dab457b1-f419-4916-bc93-35029e8a8136",
"name": "Differences exist",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2260
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "3a400a40-a25b-4e88-81ba-1aa9ada9d3aa",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.content }}",
"rightValue": "={{ $json.content }}"
},
{
"id": "8a649292-a21e-4d3f-9e3c-0047b21a7487",
"operator": {
"type": "number",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.priority }}",
"rightValue": "={{ $json.priority }}"
},
{
"id": "65f02b94-18de-4883-b5b1-f58fe5459828",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.due_datetime || null }}",
"rightValue": "={{ $json.due === null ? null : $json.due.date}}"
},
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
},
{
"id": "0b24cd35-b8b0-4d21-943c-fad905c4542a",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.description }}",
"rightValue": "={{ $json.description }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "57489d22-0081-48f5-883d-d646cde35908",
"name": "Only continue if not locked1",
"type": "n8n-nodes-base.filter",
"position": [
-1680,
3040
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "6bbb506e-407b-4357-935e-692cdccac165",
"name": "Check if Notion ID is locked",
"type": "n8n-nodes-base.redis",
"position": [
-1900,
3040
],
"parameters": {
"key": "=lock_{{ $json.body.data.id }}",
"keyType": "string",
"options": [],
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "abd91f47-f21c-4805-ae8a-d52a6b576304",
"name": "Get Notion task",
"type": "n8n-nodes-base.notion",
"maxTries": 2,
"position": [
-1460,
3040
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion trigger reference').item.json.body.data.id }}"
},
"simple": false,
"resource": "databasePage",
"operation": "get"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "62a7503b-d605-484b-95c0-864ea698f31b",
"name": "Check if creating flag exists",
"type": "n8n-nodes-base.redis",
"position": [
1440,
3400
],
"parameters": {
"key": "=creating_for_{{ $('Notion trigger reference').item.json.body.data.id }}",
"keyType": "string",
"options": [],
"operation": "get",
"propertyName": "item"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "e7981df5-7d8e-4f2d-b25f-9b74b177c95e",
"name": "Only continue if flag does not exist",
"type": "n8n-nodes-base.filter",
"position": [
1660,
3400
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6b3359cb-c8fc-4663-b2a9-e367ad4fcba5",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $json.item }}",
"rightValue": "={{ null }}"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "22869aac-c36b-4aad-ab3b-00971cfe7c4e",
"name": "Set creating flag",
"type": "n8n-nodes-base.redis",
"position": [
1880,
3400
],
"parameters": {
"key": "=creating_for_{{ $('Notion trigger reference').item.json.body.data.id }}",
"ttl": 10,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "86e4a245-d8e3-4a2a-adab-57f908fadaca",
"name": "Todoist ID exists?",
"type": "n8n-nodes-base.if",
"position": [
-360,
3240
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8b074088-25e0-4975-9356-bd2d9c17c0d3",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "0f49f736-eeec-4159-bd12-056ef4270fb5",
"name": "Get todoist ID",
"type": "n8n-nodes-base.set",
"position": [
1880,
2560
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "be85c68d-e9a8-4a88-ae12-54ec9cc685e5",
"name": "id",
"type": "string",
"value": "={{ $('Get Todoist Task2').item.json.id }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "0f1eea07-0750-46a8-801b-76254461437f",
"name": "Todoist ID exists",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2000
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "d20ece89-96d3-4324-894f-860711138952",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "5fe0975a-9b10-4046-8037-54e20ebd8804",
"name": "Get todoist ID1",
"type": "n8n-nodes-base.set",
"position": [
1880,
2000
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "be85c68d-e9a8-4a88-ae12-54ec9cc685e5",
"name": "id",
"type": "string",
"value": "={{ $('Todoist ID exists').item.json.id }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "7025479a-b14e-471e-bc7d-211241b37d8f",
"name": "Status changed but not empty",
"type": "n8n-nodes-base.filter",
"position": [
1220,
2900
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": "={{ $json.section_id }}"
},
{
"id": "2ea2a63b-9968-4f95-af48-fc71fa210ffd",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $('Map Notion to Todoist2').item.json.section_id }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "e8b84dd0-9f5e-4670-a2ee-50e75448b611",
"name": "Status is Obsolete?",
"type": "n8n-nodes-base.if",
"position": [
-800,
3040
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "9118fe29-c592-430e-8877-57cb81e6bf75",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Get Notion task').item.json.properties['Status'].status.name }}",
"rightValue": "Obsolete"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "aba681f0-a079-442f-9538-fb81abe8a9b7",
"name": "Status is not Done",
"type": "n8n-nodes-base.filter",
"position": [
1220,
3240
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "e3225b20-c6b6-479b-a0b5-dc9725197521",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Get Notion task').item.json.properties['Status'].status.name }}",
"rightValue": "Done"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "8262fbbd-3fc4-478c-8ffc-3d8d6ece743c",
"name": "Neither focussed nor planned",
"type": "n8n-nodes-base.if",
"position": [
-580,
3140
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "990a9ac6-c358-4aae-8c4e-4c49f9830ef5",
"operator": {
"type": "object",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $('Get Notion task').item.json.properties.Due.date }}",
"rightValue": ""
},
{
"id": "93c8c7eb-1979-4eaf-90a6-be659290cded",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $('Get Notion task').item.json.properties.Focus.checkbox }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "d0ab88eb-deed-4a2f-85f9-7aa381659c55",
"name": "Notion trigger reference",
"type": "n8n-nodes-base.noOp",
"position": [
-2340,
3040
],
"parameters": [],
"typeVersion": 1
},
{
"id": "1895a6d2-71dc-4f58-a746-9b59c71bedcb",
"name": "Notion Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-3220,
3040
],
"webhookId": "7e1a4d8a-9cdc-4991-817c-1429ccfbece2",
"parameters": {
"path": "7e1a4d8a-9cdc-4991-817c-1429ccfbece2",
"options": [],
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "35178847-629c-4672-858a-9b7de7344ef7",
"name": "Store Todoist ID1",
"type": "n8n-nodes-base.notion",
"position": [
2320,
3500
],
"parameters": {
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Notion trigger reference').item.json.body.data.id }}"
},
"options": [],
"resource": "databasePage",
"operation": "update",
"propertiesUi": {
"propertyValues": [
{
"key": "Todoist ID|rich_text",
"textContent": "={{ $json.id }}"
}
]
}
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"retryOnFail": true,
"typeVersion": 2.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "1310034f-b035-43d0-9448-122bf0018e63",
"name": "Mark as Completed in Todoist1",
"type": "n8n-nodes-base.todoist",
"position": [
1660,
2460
],
"parameters": {
"taskId": "={{ $('Get Todoist Task2').item.json.id }}",
"operation": "close"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 3000
},
{
"id": "baa2d8ad-3eee-46ec-bd20-cea4615f5cec",
"name": "Update task in Todoist1",
"type": "n8n-nodes-base.httpRequest",
"position": [
1660,
2260
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks\/{{ $json.id }}",
"method": "POST",
"options": [],
"jsonQuery": "={{ $('Map Notion to Todoist2').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "ad9d9069-a09b-41d0-a4f5-e83ac28f04cf",
"name": "Globals2",
"type": "n8n-nodes-base.set",
"position": [
-2120,
2780
],
"parameters": {
"mode": "raw",
"options": [],
"jsonOutput": "{\n \"database_id\": \"165beaa8-f444-8063-be17-d105a9898961\",\n \"project_id\": \"2327925123\",\n \"sections\": [\n {\"id\": \"177308158\", \"name\": \"Backlog\"},\n {\"id\": \"175778849\", \"name\": \"In progress\"},\n {\"id\": \"175778857\", \"name\": \"On hold\"}\n ]\n}"
},
"executeOnce": true,
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "165db83b-47fd-4aa6-9d04-c7ce07e8f8bf",
"name": "Mark as Incomplete in Todoist1",
"type": "n8n-nodes-base.todoist",
"position": [
1660,
2660
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "reopen"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 3000
},
{
"id": "a5bca570-f213-4d40-ac5f-7f4bbd532432",
"name": "Generate UUID2",
"type": "n8n-nodes-base.crypto",
"position": [
1440,
2900
],
"parameters": {
"action": "generate",
"dataPropertyName": "uuid"
},
"typeVersion": 1
},
{
"id": "00cea352-9026-433b-b351-28720e0b6a40",
"name": "Update section (Sync API)2",
"type": "n8n-nodes-base.httpRequest",
"position": [
1660,
2900
],
"parameters": {
"url": "https:\/\/api.todoist.com\/sync\/v9\/sync",
"method": "POST",
"options": [],
"sendBody": true,
"contentType": "form-urlencoded",
"authentication": "predefinedCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "commands",
"value": "=[ { \"type\": \"item_move\", \"uuid\": \"{{ $json.uuid }}\", \"args\": { \"id\": \"{{ $('Get Todoist Task2').item.json.id }}\", \"section_id\": \"{{ $('Map Notion to Todoist2').item.json.section_id }}\" } }]"
}
]
},
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 4.20000000000000017763568394002504646778106689453125
},
{
"id": "1cd9bf69-0e3c-4ace-8e79-894fea1c0045",
"name": "Lock Todoist ID4",
"type": "n8n-nodes-base.redis",
"position": [
2320,
2580
],
"parameters": {
"key": "=lock_{{ $json.id }}",
"ttl": 15,
"value": "={{ $now.toISO() }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"credentials": {
"redis": {
"id": "t7IN9qSccRezBIzV",
"name": "Redis account"
}
},
"typeVersion": 1
},
{
"id": "dc21db22-41a0-4a3b-aeba-dc3c81fdf251",
"name": "Delete Task in Todoist2",
"type": "n8n-nodes-base.todoist",
"onError": "continueRegularOutput",
"position": [
1660,
2000
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "delete"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 2.100000000000000088817841970012523233890533447265625,
"waitBetweenTries": 3000
},
{
"id": "e90c2ef5-61f9-4e16-879f-14c9d7974f88",
"name": "Map Notion to Todoist2",
"type": "n8n-nodes-base.code",
"position": [
-1240,
3040
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const globals = $('Globals2').first().json;\nconst properties = $('Get Notion task').item.json.properties;\n\nlet output = {};\n\noutput.project_id = globals.project_id;\n\noutput.id = properties['Todoist ID'].rich_text.length > 0 ? \n properties['Todoist ID'].rich_text[0].text.content : \n null;\n\noutput.content = properties['Name'].title.length > 0 ? properties['Name'].title[0].text.content : '[empty]';\n\noutput.description = \"[\u2197 Open in Notion](\" + $('Get Notion task').item.json.url + \")\"\n\n\/\/ Map priority\nif (properties['Priority'].select === null) {\n output.priority = \"1\"; \/\/ P4\n} else {\n output.priority = {\n \"do first\": \"4\", \/\/ P1\n \"urgent\": \"3\", \/\/ P2\n \"important\": \"2\" \/\/ P3\n }[properties['Priority'].select.name] ?? \"1\"; \/\/ P4\n}\n\n\/\/ Map section\nconst statusName = properties['Status'].status.name;\noutput.section_id = null;\nif (!['Done', 'Obsolete'].includes(statusName)) {\n const sectionMap = Object.fromEntries(\n globals.sections.map(section => [section.name, section.id])\n );\n if (!sectionMap.hasOwnProperty(statusName)) {\n throw new Error(\"No Todoist section found for status '\" + statusName + \"'\");\n }\n output.section_id = sectionMap[statusName];\n}\n\n\/\/ Map completed by status \"Done\"\noutput.is_completed = false;\nif (properties['Status'].status.name == \"Done\") {\n output.is_completed = true;\n}\n\n\/\/ Set UTC if time is set\nif (properties['Due'].date !== null) {\n output.due_datetime = properties.Due.date.start\n if (properties.Due.date.start.length > 10) {\n output.due_datetime = new Date(properties.Due.date.start).toISOString();\n }\n} else {\n \/\/ API requires due_string instead of due_datetime when date\/time is removed\n output.due_string = \"no date\";\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "e5e12b26-6c45-4c63-9c7d-05ae88a457aa",
"name": "Get Todoist Task2",
"type": "n8n-nodes-base.todoist",
"onError": "continueErrorOutput",
"position": [
300,
3140
],
"parameters": {
"taskId": "={{ $json.id }}",
"operation": "get"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"typeVersion": 2.100000000000000088817841970012523233890533447265625
},
{
"id": "05441894-9904-44b3-94f7-bb600bcbe7ba",
"name": "Create task in Todoist1",
"type": "n8n-nodes-base.httpRequest",
"position": [
2100,
3400
],
"parameters": {
"url": "=https:\/\/api.todoist.com\/rest\/v2\/tasks",
"method": "POST",
"options": [],
"jsonQuery": "={{ $('Map Notion to Todoist2').item.json.toJsonString() }}",
"sendQuery": true,
"specifyQuery": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "todoistApi"
},
"credentials": {
"todoistApi": {
"id": "Dp3VoWGH5IhrK22k",
"name": "Todoist (mario.haarmann)"
}
},
"retryOnFail": true,
"typeVersion": 4.20000000000000017763568394002504646778106689453125,
"waitBetweenTries": 3000
},
{
"id": "354b6df0-b51e-476c-bb54-baaa1a6ca788",
"name": "Execution Data5",
"type": "n8n-nodes-base.executionData",
"position": [
-2120,
3040
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "database_id",
"value": "={{ $('Globals2').first().json.database_id }}"
},
{
"key": "project_id",
"value": "={{ $('Globals2').first().json.project_id }}"
},
{
"key": "notion_id",
"value": "={{ $json.body.data.id }}"
},
{
"key": "source",
"value": "Notion"
}
]
}
},
"typeVersion": 1
},
{
"id": "04fe1427-30ba-45ae-904a-05c7b4c2269f",
"name": "Execution Data6",
"type": "n8n-nodes-base.executionData",
"position": [
-1020,
3040
],
"parameters": {
"dataToSave": {
"values": [
{
"key": "todoist_id",
"value": "={{ $json.id }}"
},
{
"key": "task_name",
"value": "={{ $json.content }}"
}
]
}
},
"typeVersion": 1
},
{
"id": "2b8262d7-298b-4a70-a420-50d8d066cb40",
"name": "Sticky Note22",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
2840
],
"parameters": {
"color": 7,
"width": 440,
"height": 220,
"content": "Update section for in Todoist using the Sync API in full sync mode (**maximum of 100 requests within a 15 minute period!**)"
},
"typeVersion": 1
},
{
"id": "10571edb-ed79-43d3-bcb9-a64273c3eeb7",
"name": "Sticky Note24",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
1880
],
"parameters": {
"color": 7,
"width": 900,
"height": 300,
"content": "**Delete the task** if it does not fit into the filter anymore"
},
"typeVersion": 1
},
{
"id": "85124819-6f06-44e8-a5dd-0c31c1aec5e3",
"name": "Sticky Note25",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
2200
],
"parameters": {
"color": 7,
"width": 900,
"height": 880,
"content": "**Update the task** if it needs to be changed"
},
"typeVersion": 1
},
{
"id": "6a9d1e64-39d1-4361-aa60-2368ba36a2cd",
"name": "Sticky Note38",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1520,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Notion tries to deliver webhooks again. To ensure the data is up to date, the payload must be retrieved again."
},
"typeVersion": 1
},
{
"id": "4787a4de-f5d2-4983-a658-916c497ce683",
"name": "Sticky Note39",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2180,
2640
],
"parameters": {
"width": 220,
"height": 300,
"content": "## Set Globals\nUse Sync Setup Helper Workflow to generate the JSON and paste it in every Globals Nodes"
},
"typeVersion": 1
},
{
"id": "5afdea5e-e6ed-463a-99bb-35bda66c25e6",
"name": "Sticky Note41",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
1920
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Continues on error, so it is not necessary to retrieve task beforehand"
},
"typeVersion": 1
},
{
"id": "6353737c-ea2b-4907-94d1-d5b4c743ca29",
"name": "Sticky Note42",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2400,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Following nodes reference to this node, so the trigger can easily be replaced"
},
"typeVersion": 1
},
{
"id": "caa80f7d-1c1a-4bbf-a92d-4df2a65e6e2f",
"name": "Sticky Note43",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2180,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Store relevant information in highlighted execution data, in case of debugging"
},
"typeVersion": 1
},
{
"id": "58332f57-5bee-4559-922b-f2ec025af90d",
"name": "Sticky Note44",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1080,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Store more information, which was not available earlier"
},
"typeVersion": 1
},
{
"id": "785526ba-39e6-42aa-83f0-731a81e821ec",
"name": "Sticky Note45",
"type": "n8n-nodes-base.stickyNote",
"position": [
-860,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Instead of deleting pages in Notion, it is a safer practice to mark them as obsolete instead."
},
"typeVersion": 1
},
{
"id": "933c2fd0-c378-4984-b34a-6a1f89d61299",
"name": "Sticky Note46",
"type": "n8n-nodes-base.stickyNote",
"position": [
1380,
3320
],
"parameters": {
"color": 7,
"width": 660,
"height": 260,
"content": "In rare cases Notion fires multiple webhooks within a short period of time, when creating a new task. This prevents the creation of duplicates in Todoist."
},
"typeVersion": 1
},
{
"id": "14b97b13-0689-481b-8a21-61940e742b2a",
"name": "Sticky Note47",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1300,
2940
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "The mapping is more advanced and also includes fall-back values for edge-cases"
},
"typeVersion": 1
},
{
"id": "ce6ed70d-4fa0-4291-9ac8-13cd23e899cc",
"name": "Sticky Note48",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1960,
2940
],
"parameters": {
"color": 7,
"width": 440,
"height": 280,
"content": "Checking for a cached flag in Redis, to prevent overrides and endless loops."
},
"typeVersion": 1
},
{
"id": "21378455-34e5-42bf-857d-543a7a972c33",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3340,
1780
],
"parameters": {
"color": 5,
"width": 5880,
"height": 1960,
"content": "# Trigger based synchronization from Notion to Todoist (part of bi-directional sync)"
},
"typeVersion": 1
},
{
"id": "0688c57e-9de6-43b5-8b75-b214760a8445",
"name": "Sticky Note20",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3280,
2820
],
"parameters": {
"width": 220,
"height": 400,
"content": "## Configure Webhook\nGrab the webhook URL and use it in the Notion automation. If using the NotionWebhookEmulator instead, replace this node with the nodes shown below."
},
"typeVersion": 1
},
{
"id": "d3a90492-d62f-4b01-bf2b-1b82b74c04cc",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
3040
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Only sync focussed & planned tasks for efficiency reasons"
},
"typeVersion": 1
},
{
"id": "d0b03cb0-8c69-4a6a-854a-afdfc690219f",
"name": "Sticky Note19",
"type": "n8n-nodes-base.stickyNote",
"position": [
-420,
3120
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "The mapping between the Notion and Todoist tasks is stored in Notion"
},
"typeVersion": 1
},
{
"id": "c34e228b-eee5-43f4-9876-8ffcd3f64488",
"name": "Sticky Note11",
"type": "n8n-nodes-base.stickyNote",
"position": [
2260,
2460
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "The Todoist ID is being cached in Redis for 15 seconds, so the other Diff sync does not get triggered."
},
"typeVersion": 1
},
{
"id": "c78c9264-683f-4ee5-9cd2-086627ce4fd1",
"name": "Sticky Note13",
"type": "n8n-nodes-base.stickyNote",
"position": [
1140,
3100
],
"parameters": {
"color": 7,
"width": 1340,
"height": 580,
"content": "**Create task** if it does not exist (anymore)"
},
"typeVersion": 1
},
{
"id": "7940c709-6107-4efa-83dc-639b88286a84",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1160,
3160
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Edge case: the task has been moved from Obsolete to Done"
},
"typeVersion": 1
},
{
"id": "2d6b7423-0818-4a2d-b672-0d09f7eaab59",
"name": "Sticky Note49",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
3180
],
"parameters": {
"color": 7,
"width": 1020,
"height": 700,
"content": "## Todoist Webhook Router Example (optional)\nSince Todoist sends the updates of all projects to a single webhook, the requests need to be routed accordingly.\nTo do so, create a separate workflow, each containing the flow above, except replace the webhook and switch nodes with an \"Execute Workflow Trigger\".\nThen activate the nodes below and configure it, so it calls the sub-workflows accordingly."
},
"typeVersion": 1
},
{
"id": "b1b38cc8-3dda-43de-9fd3-8341b5fcd798",
"name": "Sticky Note51",
"type": "n8n-nodes-base.stickyNote",
"position": [
4020,
2800
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Using the event type from Todoist is the only option to actually recognize, if a task was deleted"
},
"typeVersion": 1
},
{
"id": "5eb3caf9-ae18-47ce-80c1-52b1029e627d",
"name": "Sticky Note52",
"type": "n8n-nodes-base.stickyNote",
"position": [
5560,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "The mapping is more advanced and also includes fall-back values for edge-cases"
},
"typeVersion": 1
},
{
"id": "d8935c51-34a4-4d69-890e-23a7194ba5ea",
"name": "Sticky Note53",
"type": "n8n-nodes-base.stickyNote",
"position": [
6220,
2660
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "Previous node always returns output data, since it uses a filter to retrieve a matching item"
},
"typeVersion": 1
},
{
"id": "d7f254d6-026a-48bb-b3db-1104181db443",
"name": "Sticky Note54",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
2380
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Reverse mapping the Section\/Status"
},
"typeVersion": 1
},
{
"id": "68536590-3bd2-443a-804a-e433910b0ccc",
"name": "Sticky Note55",
"type": "n8n-nodes-base.stickyNote",
"position": [
7360,
2380
],
"parameters": {
"color": 7,
"width": 440,
"height": 240,
"content": "Update section for in Todoist using the Sync API in full sync mode (**maximum of 100 requests within a 15 minute period!**)"
},
"typeVersion": 1
},
{
"id": "18013962-c950-4976-8639-b00b3c2c43ed",
"name": "Sticky Note56",
"type": "n8n-nodes-base.stickyNote",
"position": [
7580,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Store more information, which was not available earlier"
},
"typeVersion": 1
},
{
"id": "02b2cddc-565e-46b1-aaec-b3fc3db0611c",
"name": "Sticky Note57",
"type": "n8n-nodes-base.stickyNote",
"position": [
8240,
2920
],
"parameters": {
"color": 7,
"width": 220,
"height": 280,
"content": "The Notion ID is being cached in Redis for 15 seconds, so the other Diff sync does not get triggered."
},
"typeVersion": 1
},
{
"id": "9d1db117-f41a-4529-a2e8-503d65cff0ad",
"name": "Sticky Note58",
"type": "n8n-nodes-base.stickyNote",
"position": [
6460,
3580
],
"parameters": {
"color": 7,
"width": 1340,
"height": 320,
"content": "**Delete Notion page** if Todoist task has been deleted"
},
"typeVersion": 1
},
{
"id": "9de565e0-3edb-4ea0-bace-8c17b10d5aac",
"name": "Sticky Note59",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
3620
],
"parameters": {
"color": 7,
"width": 220,
"height": 260,
"content": "Store more information, which was not available earlier"
},
"typeVersion": 1
},
{
"id": "b8aeecf7-28fb-433e-b045-147e6080cc30",
"name": "Todoist Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
2760,
2900
],
"webhookId": "81107d16-d839-4507-a627-f607f1d53d4e",
"parameters": {
"path": "47b150f3-12a3-4fe9-97cc-5d139daa3b00",
"options": [],
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "d48d3e29-d1b1-4751-adad-692cddaf49e7",
"name": "Sticky Note60",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
-640
],
"parameters": {
"color": 7,
"width": 1980,
"height": 860,
"content": "Handle tasks **only found in Notion**. This can still mean, that the task exists in Todoist, but does not match the filter, since it has been completed. In that case the Todoist task is being retrieved. If it exist, the Todoist Task will be reopened, otherwise it will be created.\nThe Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered."
},
"typeVersion": 1
},
{
"id": "bfbf04c7-8e27-4afd-998c-1720eb7f05bd",
"name": "Sticky Note61",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
240
],
"parameters": {
"color": 7,
"width": 1760,
"height": 640,
"content": "Handle tasks which show **differences** between both systems. If there are differences, the Todoist task will be overwritten by all properties from Notion. Also the status is being updated in the following step accordingly.\nThe Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered."
},
"typeVersion": 1
},
{
"id": "8edde5d8-649a-42a3-bf81-83c36ac1ff03",
"name": "Sticky Note62",
"type": "n8n-nodes-base.stickyNote",
"position": [
3600,
900
],
"parameters": {
"color": 7,
"width": 1760,
"height": 720,
"content": "Handle tasks **only found in Todoist**. This can still mean, that the task exists in Notion, but does not match the filter, since it has been completed. In that case the Notion page is being retrieved. If it does not exist, because it was deleted, the Todoist Task will be deleted as well.\nThe Todoist ID is being cached in Redis for 15 seconds, so the Diff sync does not get triggered."
},
"typeVersion": 1
},
{
"id": "284199c4-40ba-4590-9eb8-1af4dedfedf4",
"name": "Sticky Note63",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
360
],
"parameters": {
"width": 220,
"height": 280,
"content": "## Set Schedule\nSet the preferred period. Usually once a day, likely outside working hours should be a solid option."
},
"typeVersion": 1
},
{
"id": "2cad2f03-b24d-4146-883d-78bdd11ac127",
"name": "Sticky Note64",
"type": "n8n-nodes-base.stickyNote",
"position": [
3140,
560
],
"parameters": {
"color": 7,
"width": 220,
"height": 200,
"content": "Preparing data for comparing"
},
"typeVersion": 1
},
{
"id": "453dd0b4-1163-46dd-849b-39179f8ddb7c",
"name": "Sticky Note65",
"type": "n8n-nodes-base.stickyNote",
"position": [
7140,
560
],
"parameters": {
"width": 220,
"height": 320,
"content": "## Set mail address\nProvide the email address where the report should be sent to. This node can also be replaced by an SMTP or similar node."
},
"typeVersion": 1
},
{
"id": "f77d8d57-0a5c-4b33-be07-9806c55330c3",
"name": "Execute Workflow",
"type": "n8n-nodes-base.executeWorkflow",
"disabled": true,
"position": [
3520,
3440
],
"parameters": {
"options": [],
"workflowId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "4bd15a48-39d1-41c3-8b15-7de6abf3d35d",
"name": "Extract IDs",
"type": "n8n-nodes-base.set",
"position": [
-2560,
2940
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "7f0c61dc-87ae-4fbe-9e40-d9d27a0d61a8",
"name": "body.data.id",
"type": "string",
"value": "={{ $json.data.id }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "6e61887e-8022-44ee-b9ab-94058b08a07b",
"name": "Split out Notion changes",
"type": "n8n-nodes-base.splitOut",
"position": [
-2780,
2940
],
"parameters": {
"options": [],
"fieldToSplitOut": "body"
},
"typeVersion": 1
},
{
"id": "5ed59854-c972-42bd-9b41-8e6c1d616c8f",
"name": "Body is array?",
"type": "n8n-nodes-base.if",
"position": [
-3000,
3040
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "0479a702-9ec0-4203-bcaa-e4bdbd872f4e",
"operator": {
"type": "object",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body[0] }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "a6003e6f-84a7-4c5e-99d7-47b31dee10d1",
"name": "Sticky Note66",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3060,
2880
],
"parameters": {
"color": 7,
"width": 660,
"height": 340,
"content": "If consuming data from the Notion Webhook Emulator instead of a regular Notion Webhook, the data is structured differently and can contain multiple items. This section makes the workflow compatible with both approaches."
},
"typeVersion": 1
},
{
"id": "07740ae3-3472-4125-bae9-d1c54e9584f1",
"name": "Switch by project",
"type": "n8n-nodes-base.switch",
"position": [
2980,
2900
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "[project name]",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": "2346249328"
}
]
},
"renameOutput": true
}
]
},
"options": [],
"looseTypeValidation": true
},
"typeVersion": 3.20000000000000017763568394002504646778106689453125
},
{
"id": "a76190bf-f467-4bbe-82f3-581c2ab27e2a",
"name": "Sticky Note21",
"type": "n8n-nodes-base.stickyNote",
"position": [
2700,
2680
],
"parameters": {
"width": 220,
"height": 400,
"content": "## Register Webhook in Todoist App\nGrab the URL from here and add it to the Todosit developer app as described in the setup instructions"
},
"typeVersion": 1
},
{
"id": "689992a3-c8bc-4d2e-856b-2c512afdb2da",
"name": "Sticky Note67",
"type": "n8n-nodes-base.stickyNote",
"position": [
2920,
2680
],
"parameters": {
"width": 220,
"height": 400,
"content": "## Set project ID and name\nThe ID can be copied from JSON in Globals Node.\nIf syncing multiple Todoist projects, use the router described below."
},
"typeVersion": 1
},
{
"id": "f2243125-18c3-4484-9a06-85c638b1895f",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"disabled": true,
"position": [
3080,
3540
],
"webhookId": "1d758ef5-a9b9-4313-b81e-1c7dfe230a33",
"parameters": {
"path": "1d758ef5-a9b9-4313-b81e-1c7dfe230a33",
"options": []
},
"typeVersion": 2
},
{
"id": "f875a8c7-2223-4296-bd55-8f42789d11b7",
"name": "Switch",
"type": "n8n-nodes-base.switch",
"disabled": true,
"position": [
3300,
3540
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "project1",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "project2",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "759db81d-4d1a-463f-9773-1db73b6a7829",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.body.event_data.project_id }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": []
},
"typeVersion": 3.20000000000000017763568394002504646778106689453125
},
{
"id": "e603a21c-238f-45a9-8d7d-d8ccabe25217",
"name": "Execute Workflow1",
"type": "n8n-nodes-base.executeWorkflow",
"disabled": true,
"position": [
3520,
3640
],
"parameters": {
"options": [],
"workflowId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "91fe0191-0c57-404b-95e8-c3257426ccde",
"name": "Sticky Note50",
"type": "n8n-nodes-base.stickyNote",
"position": [
2740,
3440
],
"parameters": {
"width": 220,
"height": 280,
"content": "### Replace trigger\nUse this trigger in the sub-workflows instead"
},
"typeVersion": 1
},
{
"id": "98bbef00-86b5-46b2-8242-45f8eec47de4",
"name": "Sticky Note68",
"type": "n8n-nodes-base.stickyNote",
"position": [
3020,
3420
],
"parameters": {
"width": 220,
"height": 300,
"content": "### Register Webhook\nConfigure this webhook in the Todoist App as mentioned above"
},
"typeVersion": 1
},
{
"id": "650d3da2-4dee-44ab-afcd-680fb0a0d74a",
"name": "Sticky Note69",
"type": "n8n-nodes-base.stickyNote",
"position": [
3240,
3380
],
"parameters": {
"width": 220,
"height": 340,
"content": "### Set project IDs and names\nCopy each project ID from the JSON of the different Globals Nodes and name the branches by the projects."
},
"typeVersion": 1
},
{
"id": "b9c01e3c-bb9c-4bfd-aa22-b1b4a7824e42",
"name": "Sticky Note70",
"type": "n8n-nodes-base.stickyNote",
"position": [
3460,
3320
],
"parameters": {
"width": 220,
"height": 500,
"content": "### Select sub-workflows\nChoose the sub-workflows based on the branch of the Switch Node"
},
"typeVersion": 1
},
{
"id": "007599a2-8c60-423c-80a8-7599ba709db8",
"name": "Sticky Note72",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
-580
],
"parameters": {
"width": 980,
"height": 560,
"content": "# 0. Start here\n### Notion\n- A database must already exist (get a basic template [here](https:\/\/steadfast-banjo-d1f.notion.site\/17682b476c848086b002de766879aa71)) with the following properties (case matters!):\n - Text: \"Name\"\n - Status: \"Status\", containing at least the options \"Backlog\", \"In progress\", \"Done\", \"Obsolete\"\n - Select: \"Priority\", containing the options \"do first\", \"urgent\", \"important\"\n - Date: \"Due\"\n - Checkbox: \"Focus\"\n - Text: \"Todoist ID\"\n### Todoist\n- A project must already exist with the same sections like defined as Status in Notion (except Done and Obsolete)\n### Redis\n- Create a [Free Redis Cloud](https:\/\/redis.io\/try-free\/) instance or self-host\n### Setup credentials in n8n\n- Notion: https:\/\/docs.n8n.io\/integrations\/builtin\/credentials\/notion\/\n- Todoist: https:\/\/docs.n8n.io\/integrations\/builtin\/credentials\/todoist\/\n- Redis: https:\/\/docs.n8n.io\/integrations\/builtin\/credentials\/redis\/\n### Clone workflow\n- Clone this workflow into your n8n environment and map the credentials (beware, Todoist credentials are also used in HTTP request nodes)\n- Check for every yellow sticky notes. They show instructions which need to be done once"
},
"typeVersion": 1
},
{
"id": "a30814bb-205c-488b-aa0b-bf315f3e32b2",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
0
],
"parameters": {
"width": 2780,
"height": 460,
"content": "# 1. Generate config JSON for Globals Nodes\nThe sync workflows have a Node named \"Globals\", which contain mapping information for both Notion and Todoist. This flow helps to generate the config, using a multi-form.\n### How to use\n- Check if the credentials in the Notion and HTTP Request Nodes are set correctly\n- Activate this workflow\n- Open production URL of the form trigger and complete the form\n- Copy the resulting JSON code into all 3 \"Globals\" nodes"
},
"typeVersion": 1
},
{
"id": "1f273075-0648-44b1-a8b7-7e226671293a",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1920,
480
],
"parameters": {
"width": 800,
"height": 680,
"content": "# 2. Setup Todoist Webhook\n- In the Todoist App open Settings > Integrations > Developer > Build Integrations\n- Click \"Create a new app\"\n - App name: \"Notion-Todoist Sync\" \/ Name of your n8n instance\n - App service URL: (leave empty)\n- Click \"Create app\"\n- Update settings of the app:\n - OAuth redirect URL: Grab the production URL of the \"OAuth redirect\" Node (from step 3 on the right)\n- Click on \"Save settings\", leave tab open\n\n- Activate this workflow\n- Open the Production URL of the \"Todoist Webhook Setup Helper\" Node (from step 3 on the right)\n- Copy the Client ID and secret from the App settings into the form and submit it\n- When prompted for permissions, click on \"Agree\"\n\n- Go back to App settings tab\n- Scroll further down to \"Webhook\"\n- Update settings of the app:\n - Webhook callback URL: Grab the production URL of the \"Todoist Webhook\" Node (Todoist to Notion Diff sync)\n - Select these options:\n - item:added\n - item:updated\n - item:completed\n - item:uncompleted\n - item:deleted\n- Click \"Save webhook configuration\"\n- Click \"Activate webhook\"\n- Settings can now be closed"
},
"typeVersion": 1
},
{
"id": "4915c92e-aca2-409a-909f-7b2efa440bed",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
480
],
"parameters": {
"width": 800,
"height": 680,
"content": "# 4. Setup Notion Webhook\nYou can choose from two options and also choose different options for different workspaces, e.g. optionA for a business related workspace and option B for a private workspace. Both options work similarly, option A is a bit easier to setup, yet it is more expensive.\n\n## Option A: Notion Webhook via Automation (paid plan required)\n- Create a new [automation](https:\/\/www.notion.com\/help\/database-automations) on the database which should be synced\n- Rename the automation to \"Notion-Todoist Sync\" or similar\n- Add trigger: \"Any property edited\"\n- Add action: \"Send webhook\"\n - URL: Grab the production URL from the \"Notion Webhook\" Node of this workflow (Notion to Todoist Diff sync)\n - Content: Select all existing properties\n- Click \"Save\"\n\n## Option B: Notion Webhook Emulator (no subscription required)\n- Follow the instructions here: https:\/\/n8n.io\/workflows\/2284-provide-real-time-updates-for-notion-databases-via-webhooks-with-supabase\/\n- Use the Webhook URL from the \"Notion Webhook\" Node of this workflow to push the updates to"
},
"typeVersion": 1
},
{
"id": "de16a551-7864-4758-9da0-8f25856ca33d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1100,
480
],
"parameters": {
"width": 1140,
"height": 680,
"content": "# 3. Activate Todoist Webhook\nWebhooks are a bit different in Todoist then usual. A Developer App needs to be created and once be connected to via OAuth, to activate the webhook functionality.\nThis flow only works when using the **production URL** of it's form trigger."
},
"typeVersion": 1
},
{
"id": "921e8fea-c860-4943-b9f8-9dee42a36688",
"name": "Due date empty",
"type": "n8n-nodes-base.filter",
"position": [
7420,
2820
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "9e948541-8c3b-4b99-99ab-0966319500ac",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
},
"leftValue": "={{ $if($('Map Todoist to Notion1').item.json.due !== null, $('Map Todoist to Notion1').item.json.due.toDateTime().toISO(), null) }}",
"rightValue": "null"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "6ae7b8f4-32b5-4a08-9061-07399fc87631",
"name": "Has not been completed?",
"type": "n8n-nodes-base.if",
"position": [
7200,
3100
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a8595a9c-fc03-4bb1-b1fc-ecde223d19aa",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $('Get Todoist Task1').item.json.is_completed }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "dae00353-4b24-4ab1-a2c3-86ffd6860ae3",
"name": "End here",
"type": "n8n-nodes-base.noOp",
"position": [
5180,
2860
],
"parameters": [],
"typeVersion": 1
},
{
"id": "11685bda-e714-4183-87b5-47dd1dd45123",
"name": "Retry limit reached",
"type": "n8n-nodes-base.stopAndError",
"position": [
4320,
20
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "05386c19-1529-48e6-b158-1eaca54ac19f",
"name": "Set tries",
"type": "n8n-nodes-base.set",
"position": [
4320,
-280
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "21d1ab05-c6fe-44c8-831c-095d24e7251f",
"name": "Update tries",
"type": "n8n-nodes-base.set",
"position": [
5200,
20
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "c67ba192-2207-47a3-a3c5-91d059bc4e91",
"name": "Wait",
"type": "n8n-nodes-base.wait",
"position": [
4980,
20
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": [],
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "ca20fd60-2b56-4590-ae22-a43c6dd05973",
"name": "Catch known error",
"type": "n8n-nodes-base.if",
"position": [
4760,
-180
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "feb6c1ec-2696-49df-94ed-e257361b92a0",
"name": "If tries left",
"type": "n8n-nodes-base.if",
"position": [
4100,
20
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "a7f467fb-4ef2-4251-aa0d-47b069fe1cd1",
"name": "Sticky Note18",
"type": "n8n-nodes-base.stickyNote",
"position": [
-200,
3060
],
"parameters": {
"color": 7,
"width": 1320,
"height": 480,
"content": "Retrieve Todoist task, which can be completed or deleted at this point.\nUsing advanced retry logic, since the \"Get many tasks\" endpoint does not return completed tasks."
},
"typeVersion": 1
},
{
"id": "6b1cc5de-e44e-4a8a-ba28-b05273abba18",
"name": "Retry limit reached1",
"type": "n8n-nodes-base.stopAndError",
"position": [
80,
3340
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "5e38207a-bf7c-4d0d-9e19-ba387a5c68a9",
"name": "Set tries1",
"type": "n8n-nodes-base.set",
"position": [
80,
3140
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "1ba45fd2-c7e3-40de-a341-27a342892765",
"name": "Update tries1",
"type": "n8n-nodes-base.set",
"position": [
960,
3340
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries1').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "a9696b6e-f134-46ba-9b25-862ae73df812",
"name": "Wait1",
"type": "n8n-nodes-base.wait",
"position": [
740,
3340
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": [],
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "84e88ff7-bf50-4911-8f30-7027d68abe2a",
"name": "Catch known error1",
"type": "n8n-nodes-base.if",
"position": [
520,
3240
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "0482d2b4-4327-4a71-b473-9d12e07e24a7",
"name": "If tries left1",
"type": "n8n-nodes-base.if",
"position": [
-140,
3340
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "92ff51e9-b9c7-47b4-a0ed-a5a0fddd7ef7",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4240,
2660
],
"parameters": {
"color": 7,
"width": 1320,
"height": 600,
"content": "Todoist webhooks can arrive late. To ensure the data is up to date, the payload must be retrieved again. Triggers on deletion can overlap, thus try 3 times, then just stop.\nUsing advanced retry logic, since the \"Get many tasks\" endpoint does not return completed tasks.\nNo more retries needed, if Task does not exist."
},
"typeVersion": 1
},
{
"id": "a611b837-4c77-4092-a0ee-db5ed7eee17c",
"name": "Set tries2",
"type": "n8n-nodes-base.set",
"position": [
4520,
2760
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "cd93a7f6-4c06-4e8a-8d0d-e812c5ec4bc5",
"name": "tries",
"type": "number",
"value": "={{ $json.tries || 0 }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "ac1787c5-a25e-4916-a537-7e9a0854fdc9",
"name": "Update tries2",
"type": "n8n-nodes-base.set",
"position": [
5400,
3060
],
"parameters": {
"options": [],
"assignments": {
"assignments": [
{
"id": "df3c9b29-afa6-4e08-868d-5b7e8202eefa",
"name": "tries",
"type": "number",
"value": "={{ $('Set tries2').item.json.tries + 1 }}"
}
]
}
},
"typeVersion": 3.399999999999999911182158029987476766109466552734375
},
{
"id": "614e1fa0-aac4-425f-87e0-85ba084319cb",
"name": "Wait2",
"type": "n8n-nodes-base.wait",
"position": [
5180,
3060
],
"webhookId": "9d3b561f-4afd-478c-8f6e-60641d4f1d0b",
"parameters": [],
"typeVersion": 1.100000000000000088817841970012523233890533447265625
},
{
"id": "5c205ac1-0c4c-4441-b49f-6e0fd12ead4a",
"name": "Catch known error2",
"type": "n8n-nodes-base.if",
"position": [
4960,
2860
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6a379b06-0b04-4ae4-9bf9-394bd40744b7",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json.error }}",
"rightValue": "could not be found"
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "e00655f6-fe5d-4d16-ac8f-303687aa17a9",
"name": "If tries left2",
"type": "n8n-nodes-base.if",
"position": [
4300,
3060
],
"parameters": {
"options": [],
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b18f784a-4386-4ced-a9e1-ce5a21ad036e",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.tries }}",
"rightValue": 3
}
]
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "a78d5103-9c36-4660-8823-6dbc99a90b3b",
"name": "Retry limit reached2",
"type": "n8n-nodes-base.stopAndError",
"position": [
4520,
3060
],
"parameters": {
"errorMessage": "Retry limit reached"
},
"typeVersion": 1
},
{
"id": "a760a6a7-f93c-4674-a21e-d10867c523de",
"name": "Sticky Note30",
"type": "n8n-nodes-base.stickyNote",
"position": [
7800,
2140
],
"parameters": {
"color": 7,
"width": 220,
"height": 240,
"content": "Overwrite description with Notion URL"
},
"typeVersion": 1
},
{
"id": "5a7190f7-78e7-4362-8129-72e461abb099",
"name": "Turn Markdown into Notion Blocks",
"type": "n8n-nodes-base.code",
"position": [
7640,
1960
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "\/\/ Get the markdown from the input\nconst markdown = $('Get Todoist Task').item.json.description || '';\n\n\/\/ Split the markdown into lines and remove empty lines\nconst lines = markdown.split('\\n').filter(line => line.trim());\n\n\/\/ Initialize result array\nconst result = [];\n\n\/\/ Helper function to split long text into chunks\nfunction splitIntoChunks(text, maxLength) {\n const chunks = [];\n let remaining = text;\n \n while (remaining.length > maxLength) {\n \/\/ Find the last space before maxLength\n let splitIndex = remaining.lastIndexOf(' ', maxLength);\n \/\/ If no space found, force split at maxLength\n if (splitIndex === -1) splitIndex = maxLength;\n \n chunks.push(remaining.substring(0, splitIndex).trim());\n remaining = remaining.substring(splitIndex).trim();\n }\n \n if (remaining.length > 0) {\n chunks.push(remaining);\n }\n \n return chunks;\n}\n\nfor (const line of lines) {\n const trimmedLine = line.trim();\n \n \/\/ Skip if empty line\n if (!trimmedLine) continue;\n \n \/\/ Check for different types of markdown elements\n if (trimmedLine.startsWith('# ')) {\n result.push({\n type: 'heading_1',\n content: trimmedLine.substring(2).trim()\n });\n }\n else if (trimmedLine.startsWith('## ')) {\n result.push({\n type: 'heading_2',\n content: trimmedLine.substring(3).trim()\n });\n }\n else if (trimmedLine.startsWith('### ')) {\n result.push({\n type: 'heading_3',\n content: trimmedLine.substring(4).trim()\n });\n }\n else if (trimmedLine.match(\/^\\d+\\.\/)) {\n result.push({\n type: 'numbered_list_item',\n content: trimmedLine.replace(\/^\\d+\\.\\s*\/, '').trim()\n });\n }\n else if (trimmedLine.startsWith('- ')) {\n result.push({\n type: 'bulleted_list_item',\n content: trimmedLine.substring(2).trim()\n });\n }\n else {\n \/\/ For paragraphs, check if they need to be split\n if (trimmedLine.length > 2000) {\n const chunks = splitIntoChunks(trimmedLine, 2000);\n for (const chunk of chunks) {\n result.push({\n type: 'paragraph',\n content: chunk\n });\n }\n } else {\n result.push({\n type: 'paragraph',\n content: trimmedLine\n });\n }\n }\n}\n\n\/\/ Return the result in the format n8n expects\nreturn { blocks: result };\n"
},
"typeVersion": 2
},
{
"id": "edb96637-26f5-49e7-ad33-a9be019f874c",
"name": "Handle each block separately",
"type": "n8n-nodes-base.splitOut",
"position": [
7860,
1960
],
"parameters": {
"options": [],
"fieldToSplitOut": "blocks"
},
"typeVersion": 1
},
{
"id": "8c4722b7-87ae-42e3-a97e-16f4bb112b74",
"name": "Append Notion Block",
"type": "n8n-nodes-base.notion",
"position": [
8080,
1960
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create task in Notion').item.json.id }}"
},
"blockUi": {
"blockValues": [
{
"type": "={{ $json.type }}",
"textContent": "={{ $json.content }}"
}
]
},
"resource": "block"
},
"credentials": {
"notionApi": {
"id": "5AJPpSLgA5nVypUf",
"name": "Notion (octionic)"
}
},
"typeVersion": 2.20000000000000017763568394002504646778106689453125
},
{
"id": "8ba90a10-f79d-4bac-af19-f61e7bbad979",
"name": "Sticky Note34",
"type": "n8n-nodes-base.stickyNote",
"position": [
7580,
1900
],
"parameters": {
"color": 7,
"width": 660,
"height": 240,
"content": "Copy task description initially to Notion, before it get's replaced in Todoist by the Notion URL"
},
"typeVersion": 1
},
{
"id": "888f3904-cd9a-4b7b-ab7e-af2eda62aa68",
"name": "Sticky Note40",
"type": "n8n-nodes-base.stickyNote",
"position": [
60,
1180
],
"parameters": {
"width": 800,
"height": 380,
"content": "# 5. Finishing Touches\n\n### Cleanup\nNow technically everything is ready, but it is missing better usability and maintainability. So start by moving (cut & paste) the 3 big flows (outlined in blue) to separate n8n workflows. You can name them as follows:\n- Notion-Todoist Full Sync (<workspace-name>)\n- Notion-Todoist Diff Sync (<workspace-name>)\n- Todoist-Notion Diff Sync (<workspace-name>)\n \nMake sure to **activate every workflow** which requires it.\nAlso save this workflow to avoid duplicates.\n\n### First run\n- Execute the **Notion-Todoist Full Sync** workflow manually first\n- Now that the initial sync is done and you can start using the realtime sync"
},
"typeVersion": 1
}
],
"active": false,
"pinData": [],
"settings": {
"executionOrder": "v1"
},
"versionId": "a3febb48-a786-4168-8e4f-61821e15858e",
"connections": {
"Wait": {
"main": [
[
{
"node": "Update tries",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Loop Over Items1",
"type": "main",
"index": 0
},
{
"node": "Merge summary",
"type": "main",
"index": 0
}
]
]
},
"Wait1": {
"main": [
[
{
"node": "Update tries1",
"type": "main",
"index": 0
}
]
]
},
"Wait2": {
"main": [
[
{
"node": "Update tries2",
"type": "main",
"index": 0
}
]
]
},
"Notion": {
"main": [
[
{
"node": "Map Notion to Todoist",
"type": "main",
"index": 0
}
]
]
},
"Switch": {
"main": [
[
{
"node": "Execute Workflow",
"type": "main",
"index": 0
}
],
[
{
"node": "Execute Workflow1",
"type": "main",
"index": 0
}
]
]
},
"Todoist": {
"main": [
[
{
"node": "Pick Todoist Fields",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Update section (Sync API)",
"type": "main",
"index": 0
}
]
]
},
"Set tries": {
"main": [
[
{
"node": "Get Todoist Task",
"type": "main",
"index": 0
}
]
]
},
"Set tries1": {
"main": [
[
{
"node": "Get Todoist Task2",
"type": "main",
"index": 0
}
]
]
},
"Set tries2": {
"main": [
[
{
"node": "Get Todoist Task1",
"type": "main",
"index": 0
}
]
]
},
"Extract IDs": {
"main": [
[
{
"node": "Notion trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Get projects": {
"main": [
[
{
"node": "Prep Dropdown1",
"type": "main",
"index": 0
}
]
]
},
"Get sections": {
"main": [
[
{
"node": "Generate config",
"type": "main",
"index": 0
}
]
]
},
"Update tries": {
"main": [
[
{
"node": "If tries left",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID": {
"main": [
[
{
"node": "Prepare Batching Items",
"type": "main",
"index": 0
}
]
]
},
"Get variables": {
"main": [
[
{
"node": "Verify security token",
"type": "main",
"index": 0
}
]
]
},
"If tries left": {
"main": [
[
{
"node": "Set tries",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached",
"type": "main",
"index": 0
}
]
]
},
"Merge summary": {
"main": [
[
{
"node": "Filter out status changes",
"type": "main",
"index": 0
}
]
]
},
"Prep Dropdown": {
"main": [
[
{
"node": "Choose Notion Database",
"type": "main",
"index": 0
}
]
]
},
"Update tries1": {
"main": [
[
{
"node": "If tries left1",
"type": "main",
"index": 0
}
]
]
},
"Update tries2": {
"main": [
[
{
"node": "If tries left2",
"type": "main",
"index": 0
}
]
]
},
"Add project ID": {
"main": [
[
{
"node": "If Todoist ID exists",
"type": "main",
"index": 0
}
]
]
},
"Body is array?": {
"main": [
[
{
"node": "Split out Notion changes",
"type": "main",
"index": 0
}
],
[
{
"node": "Notion trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Due date empty": {
"main": [
[
{
"node": "Mark as Focussed in Notion",
"type": "main",
"index": 0
}
]
]
},
"Execution Data": {
"main": [
[
{
"node": "Check if Todoist ID is locked",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID1": {
"main": [
[
{
"node": "Update section (Sync API)1",
"type": "main",
"index": 0
}
]
]
},
"Generate UUID2": {
"main": [
[
{
"node": "Update section (Sync API)2",
"type": "main",
"index": 0
}
]
]
},
"Get todoist ID": {
"main": [
[
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"If tries left1": {
"main": [
[
{
"node": "Set tries1",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached1",
"type": "main",
"index": 0
}
]
]
},
"If tries left2": {
"main": [
[
{
"node": "Set tries2",
"type": "main",
"index": 0
}
],
[
{
"node": "Retry limit reached2",
"type": "main",
"index": 0
}
]
]
},
"Notion Webhook": {
"main": [
[
{
"node": "Body is array?",
"type": "main",
"index": 0
}
]
]
},
"OAuth redirect": {
"main": [
[
{
"node": "Get variables",
"type": "main",
"index": 0
}
]
]
},
"Prep Dropdown1": {
"main": [
[
{
"node": "Choose Todoist Project",
"type": "main",
"index": 0
}
]
]
},
"Status changed": {
"main": [
[
{
"node": "Get mapped item1",
"type": "main",
"index": 0
}
]
]
},
"Status is Done": {
"main": [
[
{
"node": "Mark as Completed in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Exchange Tokens": {
"main": [
[
{
"node": "Respond with success",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Execution Data1": {
"main": [
[
{
"node": "Get Notion Task1",
"type": "main",
"index": 0
}
]
]
},
"Execution Data2": {
"main": [
[
{
"node": "Update Description in Todoist",
"type": "main",
"index": 0
},
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Execution Data3": {
"main": [
[
{
"node": "Switch by Event",
"type": "main",
"index": 0
}
]
]
},
"Execution Data4": {
"main": [
[
{
"node": "Mark as Obsolete in Notion",
"type": "main",
"index": 0
}
]
]
},
"Execution Data5": {
"main": [
[
{
"node": "Check if Notion ID is locked",
"type": "main",
"index": 0
}
]
]
},
"Execution Data6": {
"main": [
[
{
"node": "Status is Obsolete?",
"type": "main",
"index": 0
}
]
]
},
"Generate config": {
"main": [
[
{
"node": "Return config JSON",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get Notion task": {
"main": [
[
{
"node": "Map Notion to Todoist2",
"type": "main",
"index": 0
}
]
]
},
"Get mapped item": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Get todoist ID1": {
"main": [
[
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Exists\/Completed in Notion",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Notion Task",
"type": "main",
"index": 0
}
]
]
},
"Store variables": {
"main": [
[
{
"node": "Redirect to Auth Page",
"type": "main",
"index": 0
}
]
]
},
"Switch by Event": {
"main": [
[
{
"node": "Differences exist1",
"type": "main",
"index": 0
},
{
"node": "Description has changed in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Requires completion change1",
"type": "main",
"index": 0
}
],
[
{
"node": "Requires completion change1",
"type": "main",
"index": 0
}
]
]
},
"Todoist Webhook": {
"main": [
[
{
"node": "Switch by project",
"type": "main",
"index": 0
}
]
]
},
"Compare Datasets": {
"main": [
[
{
"node": "Handle empty dates2",
"type": "main",
"index": 0
}
],
[],
[
{
"node": "Handle empty dates",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task1": {
"main": [
[
{
"node": "Notion Task not found",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Task2": {
"main": [
[
{
"node": "Notion Task found1",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task": {
"main": [
[
{
"node": "Mark as Incomplete in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error",
"type": "main",
"index": 0
}
]
]
},
"Get mapped item1": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Get mapped item2": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 1
}
]
]
},
"Loop Over Items1": {
"main": [
[],
[
{
"node": "Generate UUID",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Notion",
"type": "main",
"index": 0
},
{
"node": "Todoist",
"type": "main",
"index": 0
},
{
"node": "Globals",
"type": "main",
"index": 0
}
]
]
},
"Store Todoist ID": {
"main": [
[
{
"node": "Get mapped item2",
"type": "main",
"index": 0
}
]
]
},
"Catch known error": {
"main": [
[
{
"node": "Create task in Todoist",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"Differences exist": {
"main": [
[
{
"node": "Update task in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task1": {
"main": [
[
{
"node": "Map Todoist to Notion1",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error2",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Task2": {
"main": [
[
{
"node": "Differences exist",
"type": "main",
"index": 0
},
{
"node": "Requires completion change",
"type": "main",
"index": 0
},
{
"node": "Status changed but not empty",
"type": "main",
"index": 0
}
],
[
{
"node": "Catch known error1",
"type": "main",
"index": 0
}
]
]
},
"Set creating flag": {
"main": [
[
{
"node": "Create task in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Switch by project": {
"main": [
[
{
"node": "Todoist trigger reference",
"type": "main",
"index": 0
}
]
]
},
"Todoist ID exists": {
"main": [
[
{
"node": "Delete Task in Todoist2",
"type": "main",
"index": 0
}
]
]
},
"Catch known error1": {
"main": [
[
{
"node": "Status is not Done",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait1",
"type": "main",
"index": 0
}
]
]
},
"Catch known error2": {
"main": [
[
{
"node": "End here",
"type": "main",
"index": 0
}
],
[
{
"node": "Wait2",
"type": "main",
"index": 0
}
]
]
},
"Differences exist1": {
"main": [
[
{
"node": "Update task in Notion",
"type": "main",
"index": 0
},
{
"node": "Due date empty",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates": {
"main": [
[
{
"node": "Update Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Map summary fields": {
"main": [
[
{
"node": "Convert to HTML Table",
"type": "main",
"index": 0
}
]
]
},
"Notion Task found1": {
"main": [
[
{
"node": "Execution Data4",
"type": "main",
"index": 0
}
]
]
},
"Set creating flag1": {
"main": [
[
{
"node": "Get Backlog Section ID",
"type": "main",
"index": 0
}
]
]
},
"Status is not Done": {
"main": [
[
{
"node": "Check if creating flag exists",
"type": "main",
"index": 0
}
]
]
},
"Todoist ID exists?": {
"main": [
[
{
"node": "Set tries1",
"type": "main",
"index": 0
}
],
[
{
"node": "Check if creating flag exists",
"type": "main",
"index": 0
}
]
]
},
"Append Notion Block": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Generate email body": {
"main": [
[
{
"node": "Gmail",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates1": {
"main": [
[
{
"node": "Update task in Todoist before closing",
"type": "main",
"index": 0
}
]
]
},
"Handle empty dates2": {
"main": [
[
{
"node": "Add project ID",
"type": "main",
"index": 0
}
]
]
},
"Has been completed?": {
"main": [
[
{
"node": "Mark as Completed in Todoist1",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark as Incomplete in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Pick Todoist Fields": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 1
}
]
]
},
"Status is Obsolete?": {
"main": [
[
{
"node": "Todoist ID exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Neither focussed nor planned",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Databases": {
"main": [
[
{
"node": "Prep Dropdown",
"type": "main",
"index": 0
}
]
]
},
"If Todoist ID exists": {
"main": [
[
{
"node": "Set tries",
"type": "main",
"index": 0
}
],
[
{
"node": "Create task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Prepare summary data": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 2
}
]
]
},
"Convert to HTML Table": {
"main": [
[
{
"node": "Generate email body",
"type": "main",
"index": 0
}
]
]
},
"Create task in Notion": {
"main": [
[
{
"node": "Execution Data2",
"type": "main",
"index": 0
},
{
"node": "Turn Markdown into Notion Blocks",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist": {
"main": [
[
{
"node": "Compare Datasets",
"type": "main",
"index": 0
}
]
]
},
"Map Todoist to Notion": {
"main": [
[
{
"node": "Map summary fields",
"type": "main",
"index": 0
}
]
]
},
"Notion Task not found": {
"main": [
[
{
"node": "Check if creating flag exists1",
"type": "main",
"index": 0
}
],
[
{
"node": "Execution Data3",
"type": "main",
"index": 0
}
]
]
},
"Prepare summary data1": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 3
}
]
]
},
"Prepare summary data2": {
"main": [
[
{
"node": "Merge summary",
"type": "main",
"index": 4
}
]
]
},
"Update task in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Verify security token": {
"main": [
[
{
"node": "Exchange Tokens",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond with error",
"type": "main",
"index": 0
}
]
]
},
"Choose Notion Database": {
"main": [
[
{
"node": "Get Notion Database ID",
"type": "main",
"index": 0
}
]
]
},
"Choose Todoist Project": {
"main": [
[
{
"node": "Get Todoist Project ID",
"type": "main",
"index": 0
}
]
]
},
"Create task in Todoist": {
"main": [
[
{
"node": "Store Todoist ID",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Delete Task in Todoist": {
"main": [
[
{
"node": "Prepare summary data2",
"type": "main",
"index": 0
}
]
]
},
"Get Backlog Section ID": {
"main": [
[
{
"node": "Generate UUID1",
"type": "main",
"index": 0
},
{
"node": "Create task in Notion",
"type": "main",
"index": 0
}
]
]
},
"Get Notion Database ID": {
"main": [
[
{
"node": "Get projects",
"type": "main",
"index": 0
}
]
]
},
"Get Todoist Project ID": {
"main": [
[
{
"node": "Get sections",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist1": {
"main": [
[
{
"node": "Handle empty dates1",
"type": "main",
"index": 0
}
]
]
},
"Map Notion to Todoist2": {
"main": [
[
{
"node": "Execution Data6",
"type": "main",
"index": 0
}
]
]
},
"Map Todoist to Notion1": {
"main": [
[
{
"node": "Execution Data1",
"type": "main",
"index": 0
}
]
]
},
"Mark as Done in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Prepare Batching Items": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Update Task in Todoist": {
"main": [
[
{
"node": "Status changed",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID2",
"type": "main",
"index": 0
},
{
"node": "Prepare summary data",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist": {
"main": [
[
{
"node": "Get mapped item",
"type": "main",
"index": 0
}
]
]
},
"Create task in Todoist1": {
"main": [
[
{
"node": "Store Todoist ID1",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID4",
"type": "main",
"index": 0
}
]
]
},
"Delete Task in Todoist2": {
"main": [
[
{
"node": "Get todoist ID1",
"type": "main",
"index": 0
}
]
]
},
"Generate security token": {
"main": [
[
{
"node": "Store variables",
"type": "main",
"index": 0
}
]
]
},
"Has not been completed?": {
"main": [
[
{
"node": "Mark as In Progress in Notion",
"type": "main",
"index": 0
},
{
"node": "Neither focussed nor planned1",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark as Done in Notion",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Execute Workflow Trigger": {
"main": [
[]
]
},
"If event is not :deleted": {
"main": [
[
{
"node": "Set tries2",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Notion Task2",
"type": "main",
"index": 0
}
]
]
},
"Notion trigger reference": {
"main": [
[
{
"node": "Globals2",
"type": "main",
"index": 0
},
{
"node": "Execution Data5",
"type": "main",
"index": 0
}
]
]
},
"Split out Notion changes": {
"main": [
[
{
"node": "Extract IDs",
"type": "main",
"index": 0
}
]
]
},
"Filter out status changes": {
"main": [
[
{
"node": "Map Todoist to Notion",
"type": "main",
"index": 0
}
]
]
},
"Todoist trigger reference": {
"main": [
[
{
"node": "Execution Data",
"type": "main",
"index": 0
},
{
"node": "Globals1",
"type": "main",
"index": 0
}
]
]
},
"Update section (Sync API)": {
"main": [
[
{
"node": "Loop Over Items1",
"type": "main",
"index": 0
}
]
]
},
"Exists\/Completed in Notion": {
"main": [
[
{
"node": "Map Notion to Todoist1",
"type": "main",
"index": 0
}
],
[
{
"node": "Delete Task in Todoist",
"type": "main",
"index": 0
}
]
]
},
"Mark as Focussed in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as Obsolete in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Requires completion change": {
"main": [
[
{
"node": "Has been completed?",
"type": "main",
"index": 0
}
]
]
},
"Update section (Sync API)2": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Only continue if not locked": {
"main": [
[
{
"node": "If event is not :deleted",
"type": "main",
"index": 0
}
]
]
},
"Requires completion change1": {
"main": [
[
{
"node": "Has not been completed?",
"type": "main",
"index": 0
}
]
]
},
"Check if Notion ID is locked": {
"main": [
[
{
"node": "Only continue if not locked1",
"type": "main",
"index": 0
}
]
]
},
"Handle each block separately": {
"main": [
[
{
"node": "Append Notion Block",
"type": "main",
"index": 0
}
]
]
},
"Mark as Completed in Todoist": {
"main": [
[
{
"node": "Prepare summary data1",
"type": "main",
"index": 0
}
]
]
},
"Neither focussed nor planned": {
"main": [
[
{
"node": "Todoist ID exists",
"type": "main",
"index": 0
}
],
[
{
"node": "Todoist ID exists?",
"type": "main",
"index": 0
}
]
]
},
"Only continue if not locked1": {
"main": [
[
{
"node": "Get Notion task",
"type": "main",
"index": 0
}
]
]
},
"Status changed but not empty": {
"main": [
[
{
"node": "Generate UUID2",
"type": "main",
"index": 0
}
]
]
},
"Todoist Webhook Setup Helper": {
"main": [
[
{
"node": "Generate security token",
"type": "main",
"index": 0
}
]
]
},
"Check if Todoist ID is locked": {
"main": [
[
{
"node": "Only continue if not locked",
"type": "main",
"index": 0
}
]
]
},
"Check if creating flag exists": {
"main": [
[
{
"node": "Only continue if flag does not exist",
"type": "main",
"index": 0
}
]
]
},
"Mark as Completed in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as In Progress in Notion": {
"main": [
[
{
"node": "Lock Notion ID",
"type": "main",
"index": 0
}
]
]
},
"Mark as Incomplete in Todoist": {
"main": [
[
{
"node": "Update task in Todoist",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Neither focussed nor planned1": {
"main": [
[
{
"node": "Mark as Focussed in Notion",
"type": "main",
"index": 0
}
]
]
},
"Check if creating flag exists1": {
"main": [
[
{
"node": "Only continue if flag does not exist1",
"type": "main",
"index": 0
}
]
]
},
"Mark as Incomplete in Todoist1": {
"main": [
[
{
"node": "Get todoist ID",
"type": "main",
"index": 0
}
]
]
},
"Notion-Todoist Sync Setup Helper": {
"main": [
[
{
"node": "Get Notion Databases",
"type": "main",
"index": 0
}
]
]
},
"Turn Markdown into Notion Blocks": {
"main": [
[
{
"node": "Handle each block separately",
"type": "main",
"index": 0
}
]
]
},
"Description has changed in Todoist": {
"main": [
[
{
"node": "Update Description in Todoist1",
"type": "main",
"index": 0
}
]
]
},
"Only continue if flag does not exist": {
"main": [
[
{
"node": "Set creating flag",
"type": "main",
"index": 0
}
]
]
},
"Only continue if flag does not exist1": {
"main": [
[
{
"node": "Set creating flag1",
"type": "main",
"index": 0
}
]
]
},
"Update task in Todoist before closing": {
"main": [
[
{
"node": "Status is Done",
"type": "main",
"index": 0
},
{
"node": "Lock Todoist ID1",
"type": "main",
"index": 0
}
]
]
}
}
}