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": "Markdown \u2192 Confluence Pages (Create or Update, Multi-Page)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "markdown-to-confluence",
"responseMode": "responseNode",
"options": {}
},
"id": "k1000000-0000-0000-0000-000000000001",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
]
},
{
"parameters": {
"jsCode": "function mdToConfluenceHtml(md) {\n const lines = md.replace(/\\r/g, '').split('\\n');\n let html = '', inCodeBlock = false, codeContent = '', codeLang = 'text', inList = false;\n // Escape XML special chars in text BEFORE applying inline markdown, otherwise a bare\n // & / < / > in prose (e.g. \"Andrew Hunt & David Thomas\") produces invalid storage XML,\n // which Confluence rejects with the misleading \"Content contains unsupported extensions\".\n function fmt(t) { t = t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); return t.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>').replace(/\\*(.+?)\\*/g, '<em>$1</em>').replace(/`([^`]+)`/g, '<code>$1</code>').replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>'); }\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.startsWith('```') && !inCodeBlock) { if (inList) { html += '</ul>'; inList = false; } inCodeBlock = true; codeLang = line.slice(3).trim() || 'text'; codeContent = ''; continue; }\n if (line.startsWith('```') && inCodeBlock) { inCodeBlock = false; var __mm = (typeof $env !== 'undefined' && $env.CONFLUENCE_MERMAID_MACRO) ? $env.CONFLUENCE_MERMAID_MACRO : 'mermaid-cloud'; if (codeLang.toLowerCase() === 'mermaid') { html += '<ac:structured-macro ac:name=\"' + __mm + '\"><ac:plain-text-body><![CDATA[' + codeContent.trim() + ']]></ac:plain-text-body></ac:structured-macro>'; } else { html += '<ac:structured-macro ac:name=\"code\"><ac:parameter ac:name=\"language\">' + codeLang + '</ac:parameter><ac:plain-text-body><![CDATA[' + codeContent.trim() + ']]></ac:plain-text-body></ac:structured-macro>'; } continue; }\n if (inCodeBlock) { codeContent += line + '\\n'; continue; }\n if (line.match(/^\\|.+\\|$/) && i + 1 < lines.length && lines[i+1].match(/^\\|[-| :]+\\|$/)) {\n if (inList) { html += '</ul>'; inList = false; }\n const headers = line.split('|').filter(c => c.trim()).map(c => '<th>' + fmt(c.trim()) + '</th>').join('');\n html += '<table><thead><tr>' + headers + '</tr></thead><tbody>'; i++;\n while (i + 1 < lines.length && lines[i+1].match(/^\\|.+\\|$/)) { i++; html += '<tr>' + lines[i].split('|').filter(c => c.trim()).map(c => '<td>' + fmt(c.trim()) + '</td>').join('') + '</tr>'; }\n html += '</tbody></table>'; continue;\n }\n const hm = line.match(/^(#{1,6}) (.+)$/);\n if (hm) { if (inList) { html += '</ul>'; inList = false; } html += '<h' + hm[1].length + '>' + fmt(hm[2]) + '</h' + hm[1].length + '>'; continue; }\n if (line.match(/^---+$/)) { if (inList) { html += '</ul>'; inList = false; } html += '<hr/>'; continue; }\n if (line.startsWith('> ')) { if (inList) { html += '</ul>'; inList = false; } html += '<blockquote><p>' + fmt(line.slice(2)) + '</p></blockquote>'; continue; }\n if (line.match(/^- \\[([ xX])\\] /)) {\n if (inList) { html += '</ul>'; inList = false; }\n html += '<ac:task-list>';\n while (i < lines.length && lines[i].match(/^- \\[([ xX])\\] /)) { const cm = lines[i].match(/^- \\[([ xX])\\] (.+)/); html += '<ac:task><ac:task-status>' + ((cm[1]==='x'||cm[1]==='X') ? 'complete' : 'incomplete') + '</ac:task-status><ac:task-body>' + fmt(cm[2]) + '</ac:task-body></ac:task>'; i++; }\n html += '</ac:task-list>'; continue;\n }\n if (line.match(/^[-*] /)) { if (!inList) { html += '<ul>'; inList = true; } html += '<li>' + fmt(line.replace(/^[-*] /, '')) + '</li>'; continue; }\n if (line.trim() === '') { if (inList) { html += '</ul>'; inList = false; } continue; }\n if (inList) { html += '</ul>'; inList = false; }\n html += '<p>' + fmt(line) + '</p>';\n }\n if (inList) html += '</ul>';\n return html;\n}\n\nfunction extractPageId(val) { if (!val) return ''; val = val.trim(); const m = val.match(/\\/pages\\/(\\d+)/); if (m) return m[1]; return val.match(/^\\d+$/) ? val : val; }\n\nconst body = $input.first().json.body;\nconst pages = body.pages || [];\nconst labelsStr = body.labels || '';\nlet parentPageId = extractPageId(body.parentPageId || '') || ($env.CONFLUENCE_PARENT_PAGE_ID || '');\nconst date = new Date().toISOString().split('T')[0];\nconst labels = ['n8n-pipeline-generated'];\nif (labelsStr) labels.push(...labelsStr.split(',').map(l => l.trim()).filter(Boolean));\nconst labelsJson = labels.map(l => ({ prefix: 'global', name: l }));\n\nif (pages.length === 0 && body.pageMarkdown) {\n pages.push({ markdown: body.pageMarkdown, pageId: body.pageId || '', title: body.title || '' });\n const sms = body.sectionMarkdowns || []; for (const s of sms) { if (s.trim()) pages[0].markdown += '\\n\\n---\\n\\n' + s; }\n}\nif (pages.length === 0) return [{ json: { error: 'No pages provided' } }];\n\n// Process ALL pages sequentially in this single Code node.\n// For creates: build POST body. For updates: fetch version via API then build PUT body.\nconst confBase = $env.CONFLUENCE_BASE_URL;\nconst spaceKey = $env.CONFLUENCE_SPACE_KEY;\nconst results = [];\n\nfor (let idx = 0; idx < pages.length; idx++) {\n const pg = pages[idx];\n const md = pg.markdown || '';\n const pageId = extractPageId(pg.pageId || '');\n let title = pg.title || '';\n if (!title) { const tm = md.replace(/\\r/g, '').match(/^# (.+)$/m); title = tm ? tm[1] : 'Page ' + (idx + 1); }\n let html = mdToConfluenceHtml(md);\n html += '<hr/><p><em>Published from markdown by <a href=\"https://github.com/openmindednewby/ai-confluence-pipeline\">ai-confluence-pipeline</a> on ' + date + '</em></p>';\n\n if (pageId) {\n // Update: output GET request first, then PUT\n results.push({ json: { step: 'get-version', pageId, title, html, pageIndex: idx, method: 'GET', url: confBase + '/wiki/rest/api/content/' + pageId + '?expand=version', apiBody: null, isUpdate: true } });\n } else {\n // Create: output POST body\n const confBody = { type: 'page', title, space: { key: spaceKey }, body: { storage: { value: html, representation: 'storage' } }, metadata: { labels: labelsJson } };\n if (parentPageId) confBody.ancestors = [{ id: parentPageId }];\n results.push({ json: { step: 'create', pageId: '', title, html, pageIndex: idx, method: 'POST', url: confBase + '/wiki/rest/api/content', apiBody: confBody, isUpdate: false } });\n }\n}\n\nreturn results;"
},
"id": "k1000000-0000-0000-0000-000000000002",
"name": "Prepare Pages",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "is-get",
"leftValue": "={{ $json.step }}",
"rightValue": "get-version",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"id": "k1000000-0000-0000-0000-000000000031",
"name": "Needs Version?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
680,
300
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $json.url }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"options": {
"timeout": 15000
}
},
"id": "k1000000-0000-0000-0000-000000000011",
"name": "Get Page Version",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
900,
150
],
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// For each update item: get the version from HTTP response, get our data from the matching Prepare Pages item\nconst versionResponses = $input.all();\nconst prepItems = $('Needs Version?').all();\n\nreturn versionResponses.map((vr, i) => {\n const currentVersion = vr.json.version?.number || 1;\n const prep = prepItems[i]?.json || {};\n const updateBody = {\n type: 'page',\n title: prep.title,\n space: { key: $env.CONFLUENCE_SPACE_KEY },\n body: { storage: { value: prep.html, representation: 'storage' } },\n version: { number: currentVersion + 1 }\n };\n return { json: { method: 'PUT', url: $env.CONFLUENCE_BASE_URL + '/wiki/rest/api/content/' + prep.pageId, apiBody: updateBody, title: prep.title, pageId: prep.pageId, isUpdate: true, pageIndex: prep.pageIndex } };\n});"
},
"id": "k1000000-0000-0000-0000-000000000012",
"name": "Build Update Bodies",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
150
]
},
{
"parameters": {
"method": "={{ $json.method }}",
"url": "={{ $json.url }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify($json.apiBody) }}",
"options": {
"timeout": 30000
}
},
"id": "k1000000-0000-0000-0000-000000000004",
"name": "Create or Update Page",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1340,
300
],
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const allPrepped = $('Prepare Pages').all();\nconst allResults = $input.all();\n\nconst pages = allPrepped.map((prep, i) => {\n const result = allResults[i]?.json || {};\n const url = (result._links?.base || '') + (result._links?.webui || '');\n return {\n id: result.id || prep.json.pageId,\n title: prep.json.title,\n url: url,\n action: prep.json.isUpdate ? 'updated' : 'created'\n };\n});\n\nreturn [{ json: { success: true, pages, pageCount: pages.length } }];"
},
"id": "k1000000-0000-0000-0000-000000000005",
"name": "Aggregate Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"id": "k1000000-0000-0000-0000-000000000006",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
1780,
300
]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Prepare Pages",
"type": "main",
"index": 0
}
]
]
},
"Prepare Pages": {
"main": [
[
{
"node": "Needs Version?",
"type": "main",
"index": 0
}
]
]
},
"Needs Version?": {
"main": [
[
{
"node": "Get Page Version",
"type": "main",
"index": 0
}
],
[
{
"node": "Create or Update Page",
"type": "main",
"index": 0
}
]
]
},
"Get Page Version": {
"main": [
[
{
"node": "Build Update Bodies",
"type": "main",
"index": 0
}
]
]
},
"Build Update Bodies": {
"main": [
[
{
"node": "Create or Update Page",
"type": "main",
"index": 0
}
]
]
},
"Create or Update Page": {
"main": [
[
{
"node": "Aggregate Results",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Results": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "ai-pipeline"
},
{
"name": "confluence"
},
{
"name": "markdown"
}
]
}
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.
httpBasicAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Markdown → Confluence Pages (Create or Update, Multi-Page). Uses httpRequest. Webhook trigger; 8 nodes.
Source: https://github.com/openmindednewby/ai-confluence-pipeline/blob/master/workflows/markdown-to-confluence-pipeline.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.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.
This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c