The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "WF-04: Timeline Engine",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "wf04-timeline",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Manual Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
],
"notes": "Trigger with: POST https://YOUR-N8N-INSTANCE.onrender.com/webhook/wf04-timeline \u2014 or add a Schedule node for automatic runs."
},
{
"parameters": {
"operation": "getAll",
"tableId": "metadata",
"returnAll": true
},
"id": "fetch-all-entries",
"name": "Fetch All Entries",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
450,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"notes": "Fetches all rows from the metadata table. For large tables (>10K rows), add pagination."
},
{
"parameters": {
"functionCode": "// Temporal clustering logic\n// Assigns each entry to a timeline_group based on date_estimated\n//\n// Customize the date ranges and group names for your project.\n// Current groups match a 3-era narrative structure:\n// 1974-1990 \u2014 Origin/founding period\n// 2019-2023 \u2014 Exploration/growth period\n// 2024-2026 \u2014 Current/integration period\n\nconst entries = $input.all();\nconst clustered = [];\n\nfor (const item of entries) {\n const entry = item.json;\n let timeline_group = '2024-2026'; // default to current period\n \n // Parse date_estimated field\n const dateStr = entry.date_estimated || '';\n \n if (dateStr) {\n let year = null;\n \n // Handle YYYY format\n if (/^\\d{4}$/.test(dateStr)) {\n year = parseInt(dateStr);\n }\n // Handle YYYY-MM-DD format\n else if (/^\\d{4}-\\d{2}-\\d{2}$/.test(dateStr)) {\n year = parseInt(dateStr.split('-')[0]);\n }\n // Handle any other format \u2014 extract 4-digit year\n else {\n const match = dateStr.match(/\\d{4}/);\n if (match) year = parseInt(match[0]);\n }\n \n // Map year to timeline_group\n // CUSTOMIZE: Change these ranges to match your project's history\n if (year) {\n if (year >= 1974 && year <= 1990) {\n timeline_group = '1974-1990';\n } else if (year >= 2019 && year <= 2023) {\n timeline_group = '2019-2023';\n } else if (year >= 2024 && year <= 2026) {\n timeline_group = '2024-2026';\n }\n }\n }\n \n clustered.push({\n json: {\n id: entry.id,\n file_name: entry.file_name,\n timeline_group: timeline_group,\n date_estimated: entry.date_estimated,\n narrative_role: entry.narrative_role\n }\n });\n}\n\nreturn clustered;"
},
"id": "temporal-clustering",
"name": "Temporal Clustering",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
650,
300
]
},
{
"parameters": {
"functionCode": "// Narrative arc detection\n// Groups entries by timeline period and identifies dominant narrative roles\n//\n// narrative_role values follow a story arc:\n// setup \u2192 origin \u2192 exploration \u2192 conflict \u2192 realisation \u2192 integration\n//\n// CUSTOMIZE: Change the expected narrative roles to match your taxonomy.\n\nconst items = $input.all();\n\n// Group by timeline_group\nconst groups = {\n '1974-1990': [],\n '2019-2023': [],\n '2024-2026': []\n};\n\nfor (const item of items) {\n const group = item.json.timeline_group;\n if (groups[group]) {\n groups[group].push(item.json);\n }\n}\n\n// Analyze narrative arc for each period\nconst analysis = {\n '1974-1990': {\n count: groups['1974-1990'].length,\n roles: groups['1974-1990'].map(e => e.narrative_role).filter(Boolean),\n dominant: 'origin'\n },\n '2019-2023': {\n count: groups['2019-2023'].length,\n roles: groups['2019-2023'].map(e => e.narrative_role).filter(Boolean),\n dominant: 'exploration'\n },\n '2024-2026': {\n count: groups['2024-2026'].length,\n roles: groups['2024-2026'].map(e => e.narrative_role).filter(Boolean),\n dominant: 'integration'\n }\n};\n\nreturn [{\"json\": {\"analysis\": analysis, \"total_entries\": items.length, \"entries_to_update\": items}}];"
},
"id": "narrative-analysis",
"name": "Narrative Arc Detection",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
850,
300
]
},
{
"parameters": {
"batchSize": 1,
"options": {}
},
"id": "split-batches",
"name": "Split in Batches",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 1,
"position": [
1050,
300
],
"notes": "Processes one entry at a time. Increase batchSize if your Supabase plan supports higher concurrency."
},
{
"parameters": {
"functionCode": "const batch = $input.item.json;\nconst entries = batch.entries_to_update || [];\n\nif (entries.length > 0) {\n const entry = entries[$node['Split in Batches'].context.currentRunIndex];\n return [{json: entry}];\n}\n\nreturn [];"
},
"id": "extract-entry",
"name": "Extract Entry",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
1250,
300
]
},
{
"parameters": {
"operation": "update",
"tableId": "metadata",
"filterType": "string",
"filterString": "id=eq.{{$json.id}}",
"columns": {
"mappingMode": "defineBelow",
"value": {
"timeline_group": "={{$json.timeline_group}}"
}
}
},
"id": "update-timeline-group",
"name": "Update Timeline Group",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
1450,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\"status\": \"complete\", \"message\": \"Timeline clustering complete\", \"analysis\": {{$node['Narrative Arc Detection'].json.analysis}}, \"entries_updated\": {{$node['Narrative Arc Detection'].json.total_entries}}}"
},
"id": "respond-summary",
"name": "Respond Summary",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1650,
300
]
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Fetch All Entries",
"type": "main",
"index": 0
}
]
]
},
"Fetch All Entries": {
"main": [
[
{
"node": "Temporal Clustering",
"type": "main",
"index": 0
}
]
]
},
"Temporal Clustering": {
"main": [
[
{
"node": "Narrative Arc Detection",
"type": "main",
"index": 0
}
]
]
},
"Narrative Arc Detection": {
"main": [
[
{
"node": "Split in Batches",
"type": "main",
"index": 0
}
]
]
},
"Split in Batches": {
"main": [
[
{
"node": "Extract Entry",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond Summary",
"type": "main",
"index": 0
}
]
]
},
"Extract Entry": {
"main": [
[
{
"node": "Update Timeline Group",
"type": "main",
"index": 0
}
]
]
},
"Update Timeline Group": {
"main": [
[
{
"node": "Split in Batches",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
supabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
WF-04: Timeline Engine. Uses supabase. Webhook trigger; 8 nodes.
Source: https://github.com/batistai4724/zero-cost-ops/blob/main/workflows/WF-04-timeline-engine.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
webhook - simulador PDV (fluxo). Uses supabase. Webhook trigger; 55 nodes.
This workflow automates raw materials inventory management for businesses, eliminating manual stock updates, delayed material issue approvals, and missed low stock alerts. It ensures real-time stock t
booking no duplicate. Uses httpRequest, supabase. Webhook trigger; 38 nodes.
Backend Erick. Uses supabase, n8n-nodes-evolution-api. Webhook trigger; 36 nodes.
01-intake-storage. Uses airtable, supabase, stopAndError, httpRequest. Webhook trigger; 33 nodes.