This workflow corresponds to n8n.io template #16658 — we link there as the canonical source.
This workflow follows the Chainllm → Form Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "rhJSbsxsjPsr4VJ6",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "n8n Workflow Auditor & Notion Doc Generator",
"tags": [],
"nodes": [
{
"id": "ddbc08fd-cfd0-43b7-8714-dac5a2c87bbe",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
23184,
-2096
],
"parameters": {
"width": 480,
"height": 896,
"content": "## n8n Workflow Auditor & Notion Doc Generator\n\n### How it works\n\nThis workflow accepts an n8n workflow submission through a form, validates and audits the workflow JSON, then builds a prompt for an AI documentation generator. Claude Sonnet generates structured documentation, which is parsed into Notion-compatible blocks and published to a newly created Notion page. The workflow appends content in batches with a wait step to respect Notion API limits, and a separate error path captures failed executions.\n\n### Setup steps\n\n- Configure the form trigger fields so users can submit the workflow JSON and any required metadata.\n- Add Anthropic credentials for the Claude Sonnet chat model used by the LLM chain.\n- Connect Notion credentials and configure the target Notion database or parent page for newly generated documentation pages.\n- Review the custom code nodes for expected input field names, validation rules, prompt format, and Notion block output structure.\n- Set an appropriate batch size and wait duration for the Notion append loop to match Notion API rate limits.\n\n### Customization\n\nYou can customize the audit criteria, documentation prompt, Claude model settings, Notion page properties, block formatting, batch size, and rate-limit delay to match your documentation standards."
},
"typeVersion": 1
},
{
"id": "2185c9d5-6797-445e-8877-d8dac7f7014e",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
23712,
-1952
],
"parameters": {
"color": 7,
"width": 416,
"height": 368,
"content": "## Capture workflow input\n\nStarts the main path with a submitted workflow and performs initial parsing and validation before deeper analysis."
},
"typeVersion": 1
},
{
"id": "c92841e7-3c5e-4c4d-9641-92af934ea350",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
24160,
-1952
],
"parameters": {
"color": 7,
"width": 416,
"height": 368,
"content": "## Audit and prompt\n\nAnalyzes the validated workflow and converts the audit results into a prompt for AI-generated documentation."
},
"typeVersion": 1
},
{
"id": "8ca22035-82e1-4511-9572-ef0b7e19c389",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
24608,
-1952
],
"parameters": {
"color": 7,
"width": 544,
"height": 576,
"content": "## Generate AI documentation\n\nRuns the LLM documentation step with Claude Sonnet and parses the model output into a structured response for downstream formatting."
},
"typeVersion": 1
},
{
"id": "d6977413-49fb-4860-b2bc-d17291d3d9bd",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
25184,
-1952
],
"parameters": {
"color": 7,
"width": 416,
"height": 384,
"content": "## Prepare Notion page\n\nTransforms parsed documentation into Notion block payloads and creates the destination Notion page that will receive the content."
},
"typeVersion": 1
},
{
"id": "a055357f-f68a-4865-a729-f7680034ef94",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
25632,
-2096
],
"parameters": {
"color": 7,
"width": 1088,
"height": 528,
"content": "## Append blocks in batches\n\nLoops through generated Notion blocks, appends them to the page, waits between batches for rate limiting, and exits through the completion node when all batches are processed."
},
"typeVersion": 1
},
{
"id": "d31fe588-f631-4d3e-9408-576edb772146",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
23712,
-1536
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Handle execution errors\n\nA separate lower canvas cluster catches failed workflow executions and stops with a logged error for troubleshooting."
},
"typeVersion": 1
},
{
"id": "692c5ff9-1303-4415-bbdd-907ab77c44a9",
"name": "When Form Submitted",
"type": "n8n-nodes-base.formTrigger",
"position": [
23760,
-1760
],
"parameters": {
"options": {},
"formTitle": "Workflow Documentation Generator",
"formFields": {
"values": [
{
"fieldType": "file",
"fieldLabel": "Workflow JSON",
"multipleFiles": false,
"requiredField": true,
"acceptFileTypes": ".json"
},
{
"fieldLabel": "Client Name",
"placeholder": "e.g. Acme Corp",
"requiredField": true
},
{
"fieldLabel": "Notion Parent Page ID",
"placeholder": "32-character page ID (no dashes)",
"requiredField": true
}
]
},
"formDescription": "Upload your exported n8n workflow JSON. A Notion page with a business summary and technical audit will be created automatically."
},
"typeVersion": 2.2
},
{
"id": "9008dfbd-52d2-46ab-bd11-7f24b17d5044",
"name": "Validate Form Data",
"type": "n8n-nodes-base.code",
"position": [
23984,
-1760
],
"parameters": {
"jsCode": "const item = $input.first();\nconst form = item.json;\n\n// Read uploaded JSON file\nlet raw = '';\nif (item.binary?.['Workflow JSON']) {\n const buf = await this.helpers.getBinaryDataBuffer(0, 'Workflow JSON');\n raw = buf.toString('utf-8');\n} else {\n throw new Error('No workflow JSON file uploaded.');\n}\n\nconst wf = JSON.parse(raw);\nif (!Array.isArray(wf.nodes)) throw new Error('Invalid workflow: nodes must be an array.');\nif (typeof wf.connections !== 'object') throw new Error('Invalid workflow: connections must be an object.');\n\nconst notionParentId = (form['Notion Parent Page ID'] || '').trim();\nif (!notionParentId) throw new Error('Notion Parent Page ID is required.');\n\nconst clientName = (form['Client Name'] || 'Client').trim();\nconst workflowName = (wf.name || 'Unnamed Workflow')\n .replace(/[\\x00-\\x1F\\x7F]/g, '').replace(/[`*_#\\[\\]<>|]/g, ' ').trim()\n || 'Unnamed Workflow';\n\nreturn [{ json: { wf, clientName, notionParentId, workflowName } }];"
},
"typeVersion": 2
},
{
"id": "3a14f08c-abd9-4ef8-99ed-9b34a5fa5e0d",
"name": "Review Workflow Content",
"type": "n8n-nodes-base.code",
"position": [
24208,
-1760
],
"parameters": {
"jsCode": "const { wf, clientName, notionParentId, workflowName } = $input.first().json;\nconst { nodes, connections } = wf;\n\n// Build node maps\nconst byId = Object.fromEntries(nodes.map(n => [n.id, n]));\nconst nameId = Object.fromEntries(nodes.map(n => [n.name, n.id]));\nconst adj = Object.fromEntries(nodes.map(n => [n.id, []]));\nconst indeg = Object.fromEntries(nodes.map(n => [n.id, 0]));\n\nfor (const [src, outs] of Object.entries(connections)) {\n const sid = nameId[src]; if (!sid) continue;\n for (const branches of Object.values(outs)) {\n if (!Array.isArray(branches)) continue;\n for (const branch of branches) {\n if (!Array.isArray(branch)) continue;\n for (const { node } of branch) {\n const tid = nameId[node]; if (!tid) continue;\n adj[sid].push(tid);\n indeg[tid]++;\n }\n }\n }\n}\n\n// Kahn's topological sort\nconst queue = nodes.filter(n => indeg[n.id] === 0).map(n => n.id);\nconst sorted = [];\nconst seen = new Set();\nwhile (queue.length) {\n const id = queue.shift();\n if (seen.has(id)) continue;\n seen.add(id); sorted.push(id);\n for (const nid of adj[id]) { if (--indeg[nid] === 0) queue.push(nid); }\n}\n\nconst TRIGGERS = new Set(['n8n-nodes-base.webhook','n8n-nodes-base.scheduleTrigger',\n 'n8n-nodes-base.manualTrigger','n8n-nodes-base.formTrigger',\n 'n8n-nodes-base.emailReadImap','@n8n/n8n-nodes-langchain.chatTrigger']);\n\nconst cycleNodes = nodes.filter(n => !seen.has(n.id)).map(n => n.name);\nconst deadNodes = nodes.filter(n => {\n return !connections[n.name] && !nodes.some(o => adj[o.id]?.includes(n.id))\n && !TRIGGERS.has(n.type);\n}).map(n => n.name);\n\nconst ordered = [...sorted, ...cycleNodes.map(nm => nameId[nm]).filter(Boolean)]\n .map(id => byId[id]).filter(Boolean);\n\n// Static audit\nconst findings = [];\nconst SECRETS = [\n [/sk-[a-zA-Z0-9]{20,}/, 'OpenAI API key'],\n [/xoxb-[a-zA-Z0-9-]+/, 'Slack bot token'],\n [/ghp_[a-zA-Z0-9]{36}/, 'GitHub PAT'],\n [/Bearer\\s+[a-zA-Z0-9\\-_.]{20,}/, 'hardcoded Bearer YOUR_TOKEN_HERE'],\n];\n\nfor (const n of nodes) {\n const p = JSON.stringify(n.parameters || {});\n if (n.type === 'n8n-nodes-base.httpRequest' && !n.parameters?.options?.timeout)\n findings.push({ sev:'HIGH', node:n.name, issue:'HTTP Request has no timeout set.', fix:'Set Options \u203a Timeout to 15000\u201330000 ms.' });\n for (const [re, label] of SECRETS)\n if (re.test(p)) findings.push({ sev:'CRITICAL', node:n.name, issue:`Possible hardcoded ${label}.`, fix:'Move to n8n Credentials.' });\n if (n.type === 'n8n-nodes-base.webhook' && (!n.parameters?.authentication || n.parameters.authentication === 'none'))\n findings.push({ sev:'HIGH', node:n.name, issue:'Webhook has no authentication.', fix:'Enable Header Auth or Basic Auth.' });\n if (n.type === '@n8n/n8n-nodes-langchain.agent' && !nodes.some(o => /approval|confirm|human/i.test(o.name)))\n findings.push({ sev:'MEDIUM', node:n.name, issue:'AI Agent has no human-in-the-loop step.', fix:'Add a Wait or approval node before destructive actions.' });\n if (['n8n-nodes-base.httpRequest','n8n-nodes-base.executeWorkflow'].includes(n.type)) {\n const hasErrNode = adj[n.id]?.some(id => byId[id]?.name?.toLowerCase().includes('error'));\n if (!hasErrNode) findings.push({ sev:'MEDIUM', node:n.name, issue:'No explicit error-handling node downstream.', fix:'Add an error branch or configure \"On Error: Continue\" with conditional logic.' });\n }\n}\nfor (const nm of deadNodes) findings.push({ sev:'LOW', node:nm, issue:'Node is unreachable.', fix:'Remove or reconnect it.' });\nfor (const nm of cycleNodes) findings.push({ sev:'MEDIUM', node:nm, issue:'Node is in a cycle \u2014 sort order unresolved.', fix:'Ensure loops have an exit condition.' });\n\nconst score = Math.max(0, 100\n - findings.filter(f=>f.sev==='CRITICAL').length * 25\n - findings.filter(f=>f.sev==='HIGH').length * 10\n - findings.filter(f=>f.sev==='MEDIUM').length * 5\n - findings.filter(f=>f.sev==='LOW').length * 2);\n\nconst nodeList = ordered.map((n,i) => {\n const ds = (adj[n.id]||[]).map(id=>byId[id]?.name).filter(Boolean);\n return `${i+1}. [${n.type}] \"${n.name}\"${ds.length ? ' \u2192 '+ds.join(', ') : ' (terminal)'}${cycleNodes.includes(n.name)?' \u26a0 CYCLE':''}`;\n}).join('\\n');\n\nconst findingsSummary = findings.length\n ? findings.map(f=>`[${f.sev}] ${f.node}: ${f.issue} Fix: ${f.fix}`).join('\\n')\n : 'No issues found.';\n\nreturn [{ json: {\n workflowName, clientName, notionParentId,\n nodeCount: nodes.length, score,\n criticalCount: findings.filter(f=>f.sev==='CRITICAL').length,\n highCount: findings.filter(f=>f.sev==='HIGH').length,\n mediumCount: findings.filter(f=>f.sev==='MEDIUM').length,\n lowCount: findings.filter(f=>f.sev==='LOW').length,\n findings, cycleNodes, deadNodes, hasCycles: cycleNodes.length > 0,\n nodeList, findingsSummary\n} }];"
},
"typeVersion": 2
},
{
"id": "a3934f6e-7dd1-4b75-a0f5-bd3c2ae0dccc",
"name": "Construct AI Prompt",
"type": "n8n-nodes-base.code",
"position": [
24432,
-1760
],
"parameters": {
"jsCode": "const d = $input.first().json;\n\nconst system = `You are an automation consultant writing client documentation.\n\nReturn ONLY a raw JSON object with two keys \u2014 no markdown fences, no preamble:\n{\n \"businessLogic\": \"<150-300 word plain-English summary of what this workflow does, when it runs, and what outcomes it produces. No technical node names.>\",\n \"auditReport\": \"<Developer-facing audit findings in Markdown. Bold each severity label. End with an overall assessment paragraph. If no findings, say so.>\"\n}`;\n\nconst user =\n`Workflow: ${d.workflowName}\nClient: ${d.clientName}\nNodes: ${d.nodeCount} | Audit Score: ${d.score}/100\n${d.hasCycles ? 'WARNING: cycles detected in ' + d.cycleNodes.join(', ') : ''}\n\nEXECUTION ORDER:\n${d.nodeList}\n\nAUDIT FINDINGS:\n${d.findingsSummary}`;\n\nreturn [{ json: { ...d, system, user } }];"
},
"typeVersion": 2
},
{
"id": "c34d4be6-afa4-450a-9ae3-a37acee29ace",
"name": "Generate Documentation",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
24656,
-1760
],
"parameters": {
"messages": {
"messageValues": [
{
"message": "={{ $json.system }}"
}
]
}
},
"typeVersion": 1.4
},
{
"id": "7e96ba02-6f50-419e-9da7-3c31fd1c5641",
"name": "Claude Model Interaction",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
24736,
-1536
],
"parameters": {
"model": "claude-sonnet-4-6",
"options": {}
},
"typeVersion": 1.3
},
{
"id": "f693461e-63c9-406c-bb8b-133af29fbbcd",
"name": "Interpret AI Response",
"type": "n8n-nodes-base.code",
"position": [
25008,
-1760
],
"parameters": {
"jsCode": "const text = ($input.first().json.text || '').trim()\n .replace(/^```(?:json)?\\n?/, '').replace(/\\n?```$/, '');\n\nif (!text) throw new Error('Model returned empty output.');\n\nlet parsed;\ntry { parsed = JSON.parse(text); }\ncatch { throw new Error('Model did not return valid JSON: ' + text.substring(0, 200)); }\n\nif (!parsed.businessLogic) throw new Error('Missing businessLogic in model response.');\nif (!parsed.auditReport) throw new Error('Missing auditReport in model response.');\n\nconst ctx = $('Construct AI Prompt').first().json;\n\nreturn [{ json: {\n ...ctx,\n businessLogicMd: parsed.businessLogic,\n auditReportMd: parsed.auditReport\n} }];"
},
"typeVersion": 2
},
{
"id": "1c79db3d-ec34-47e7-8f9d-22be290ff83e",
"name": "Formulate Notion Blocks",
"type": "n8n-nodes-base.code",
"position": [
25232,
-1760
],
"parameters": {
"jsCode": "const { workflowName, clientName, notionParentId, score,\n criticalCount, highCount, mediumCount, lowCount,\n nodeCount, businessLogicMd, auditReportMd,\n findings, hasCycles, cycleNodes, deadNodes } = $input.first().json;\n\nconst MAX = 1990;\n\nfunction spans(text, ann={}) {\n const chunks = [];\n let s = String(text||'');\n while (s.length > MAX) {\n let i = s.lastIndexOf(' ', MAX); if (i < MAX*0.5) i = MAX;\n chunks.push(s.slice(0,i)); s = s.slice(i).trimStart();\n }\n if (s) chunks.push(s);\n return chunks.map(c => ({ type:'text', text:{content:c},\n annotations:{bold:false,italic:false,strikethrough:false,\n underline:false,code:false,color:'default',...ann} }));\n}\n\nfunction mdToBlocks(md) {\n const out = []; let inCode=false, codeLines=[], lang='';\n for (const line of (md||'').split('\\n')) {\n if (/^```/.test(line.trim())) {\n if (!inCode) { inCode=true; lang=line.trim().slice(3)||'plain text'; codeLines=[]; }\n else {\n inCode=false;\n const content=codeLines.join('\\n');\n for(let i=0;i<content.length;i+=MAX)\n out.push({object:'block',type:'code',code:{language:lang,rich_text:[{type:'text',text:{content:content.slice(i,i+MAX)}}]}});\n codeLines=[]; lang='';\n }\n continue;\n }\n if (inCode) { codeLines.push(line); continue; }\n const t = line.trimEnd();\n if (!t) continue;\n if (/^### /.test(t)) { out.push({object:'block',type:'heading_3',heading_3:{rich_text:spans(t.slice(4))}}); continue; }\n if (/^## /.test(t)) { out.push({object:'block',type:'heading_2',heading_2:{rich_text:spans(t.slice(3))}}); continue; }\n if (/^# /.test(t)) { out.push({object:'block',type:'heading_1',heading_1:{rich_text:spans(t.slice(2))}}); continue; }\n if (/^[-*] /.test(t)){ out.push({object:'block',type:'bulleted_list_item',bulleted_list_item:{rich_text:spans(t.slice(2))}}); continue; }\n if (/^\\d+\\. /.test(t)){ out.push({object:'block',type:'numbered_list_item',numbered_list_item:{rich_text:spans(t.replace(/^\\d+\\. /,''))}}); continue; }\n if (/^---+$/.test(t)) { out.push({object:'block',type:'divider',divider:{}}); continue; }\n // paragraph with inline bold\n const parts=[]; const re=/\\*\\*(.+?)\\*\\*/g; let idx=0,m;\n while((m=re.exec(t))!==null){if(m.index>idx)parts.push(...spans(t.slice(idx,m.index)));parts.push(...spans(m[1],{bold:true}));idx=m.index+m[0].length;}\n if(idx<t.length)parts.push(...spans(t.slice(idx)));\n out.push({object:'block',type:'paragraph',paragraph:{rich_text:(parts.length?parts:spans(t)).slice(0,100)}});\n }\n return out;\n}\n\nconst emoji = score>=80?'\u2705':score>=50?'\u26a0\ufe0f':'\ud83d\udd34';\nconst color = score>=80?'green_background':score>=50?'yellow_background':'red_background';\nconst today = new Date().toISOString().slice(0,10);\n\nconst blocks = [\n { object:'block',type:'callout',callout:{\n rich_text: spans(`${emoji} Audit Score: ${score}/100 \u00b7 Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount} Low: ${lowCount} \u00b7 ${clientName} \u00b7 ${today}`),\n color, icon:{type:'emoji',emoji}\n }},\n {object:'block',type:'divider',divider:{}},\n {object:'block',type:'heading_2',heading_2:{rich_text:spans('Business Logic Summary')}},\n ...mdToBlocks(businessLogicMd),\n {object:'block',type:'divider',divider:{}},\n {object:'block',type:'heading_2',heading_2:{rich_text:spans('Technical Audit Report')}},\n ...mdToBlocks(auditReportMd),\n];\n\nif (hasCycles || deadNodes?.length) {\n blocks.push({object:'block',type:'divider',divider:{}});\n blocks.push({object:'block',type:'heading_2',heading_2:{rich_text:spans('Graph Warnings')}});\n if (hasCycles) blocks.push({object:'block',type:'callout',callout:{rich_text:spans('Cycles: '+cycleNodes.join(', ')),color:'orange_background',icon:{type:'emoji',emoji:'\u26a0\ufe0f'}}});\n if (deadNodes?.length) blocks.push({object:'block',type:'callout',callout:{rich_text:spans('Unreachable: '+deadNodes.join(', ')),color:'yellow_background',icon:{type:'emoji',emoji:'\ud83d\udd38'}}});\n}\n\nblocks.push({object:'block',type:'divider',divider:{}});\nblocks.push({object:'block',type:'paragraph',paragraph:{rich_text:[\n ...spans('Generated by '), ...spans('AutomiQ',{bold:true}), ...spans(' \u00b7 automiq.fi \u00b7 '+today)\n]}});\n\nconst batches=[];\nfor(let i=0;i<blocks.length;i+=99) batches.push(blocks.slice(i,i+99));\n\nreturn batches.map((b,i)=>({json:{notionParentId,workflowName,blocks:b,batchIndex:i,totalBatches:batches.length}}));"
},
"typeVersion": 2
},
{
"id": "9c5f6bfa-966f-451f-9e0b-8bc13bdc5637",
"name": "Create Notion Document",
"type": "n8n-nodes-base.notion",
"position": [
25456,
-1760
],
"parameters": {
"title": "={{ $('Construct AI Prompt').first().json.workflowName }} \u2014 Docs",
"pageId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Construct AI Prompt').first().json.notionParentId }}"
},
"blockUi": {
"blockValues": []
},
"options": {}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "39f915b0-904a-480c-98c7-31b6e5fe762b",
"name": "Process Batch of 10",
"type": "n8n-nodes-base.splitInBatches",
"position": [
25936,
-1760
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "ebf15d41-ad3c-4c1c-8cde-7c1bd1d13e9a",
"name": "Append to Notion Document",
"type": "n8n-nodes-base.notion",
"position": [
26160,
-1952
],
"parameters": {
"blockId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Create Notion Document').first().json.id }}"
},
"blockUi": {
"blockValues": [
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
]
},
"resource": "block"
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "b35f8579-8350-4942-8a39-135d7ac15b7d",
"name": "Completion Marker",
"type": "n8n-nodes-base.noOp",
"position": [
26160,
-1760
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e5092f35-a8d5-428c-84bb-213dffd3b115",
"name": "Wait 350 Milliseconds",
"type": "n8n-nodes-base.wait",
"position": [
26384,
-1776
],
"parameters": {
"amount": 35
},
"typeVersion": 1.1
},
{
"id": "4c1783c2-888f-4434-bb30-18eb30e987d6",
"name": "On Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"position": [
23760,
-1360
],
"parameters": {},
"typeVersion": 1
},
{
"id": "ec4d4b78-2e05-4eb2-aa47-e75b00b2ef4e",
"name": "Record Error Details",
"type": "n8n-nodes-base.stopAndError",
"position": [
23984,
-1360
],
"parameters": {
"errorMessage": "={{ 'Workflow documentation failed\\n\\nNode: ' + $json.execution.lastNodeExecuted + '\\nError: ' + $json.error.message }}"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "c096ff74-2d7f-40c9-b1e5-8e59afbb0c81",
"nodeGroups": [],
"connections": {
"On Error Trigger": {
"main": [
[
{
"node": "Record Error Details",
"type": "main",
"index": 0
}
]
]
},
"Validate Form Data": {
"main": [
[
{
"node": "Review Workflow Content",
"type": "main",
"index": 0
}
]
]
},
"Construct AI Prompt": {
"main": [
[
{
"node": "Generate Documentation",
"type": "main",
"index": 0
}
]
]
},
"Process Batch of 10": {
"main": [
[
{
"node": "Append to Notion Document",
"type": "main",
"index": 0
}
],
[
{
"node": "Completion Marker",
"type": "main",
"index": 0
}
]
]
},
"When Form Submitted": {
"main": [
[
{
"node": "Validate Form Data",
"type": "main",
"index": 0
}
]
]
},
"Interpret AI Response": {
"main": [
[
{
"node": "Formulate Notion Blocks",
"type": "main",
"index": 0
}
]
]
},
"Wait 350 Milliseconds": {
"main": [
[
{
"node": "Process Batch of 10",
"type": "main",
"index": 0
}
]
]
},
"Create Notion Document": {
"main": [
[
{
"node": "Process Batch of 10",
"type": "main",
"index": 0
}
]
]
},
"Generate Documentation": {
"main": [
[
{
"node": "Interpret AI Response",
"type": "main",
"index": 0
}
]
]
},
"Formulate Notion Blocks": {
"main": [
[
{
"node": "Create Notion Document",
"type": "main",
"index": 0
}
]
]
},
"Review Workflow Content": {
"main": [
[
{
"node": "Construct AI Prompt",
"type": "main",
"index": 0
}
]
]
},
"Claude Model Interaction": {
"ai_languageModel": [
[
{
"node": "Generate Documentation",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Append to Notion Document": {
"main": [
[
{
"node": "Wait 350 Milliseconds",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
notionApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects an exported n8n workflow JSON via an n8n Form, audits it for common reliability and security issues, uses Anthropic Claude to draft a business summary and technical report, and then creates a formatted documentation page in Notion. Receives a form…
Source: https://n8n.io/workflows/16658/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow automates Invoice & Payment Tracking (with Approvals) across Notion and Slack. Ingest — You drop invoices/receipts (PDF/IMG/JSON) into the flow. Extract — OCR + parsing pulls out key fie
Episode 11: AI shorts factory app. Uses httpRequest, googleSheets, lmChatOpenAi, lmChatOllama. Event-driven trigger; 96 nodes.
This template attempts to replicate OpenAI's DeepResearch feature which, at time of writing, is only available to their pro subscribers.
My workflow 53. Uses formTrigger, httpRequest, lmChatOpenAi, form. Event-driven trigger; 74 nodes.
Submit a research topic through a form and receive a professionally styled executive report in your inbox — fully automated, with built-in scraping resilience.