I Built an Automated Content Factory with n8n — One Post, Four Platforms, 5 Minutes
Manual cross-platform publishing ate 2 hours per post. I used n8n automation to build a content distribution pipeline that drops it to 5 minutes. Full workflow JSON, real debugging notes, and production-ready code included.
What You'll Learn
- ✓ How to build a multi-platform content automation workflow with n8n
- ✓ Practical usage of n8n's Webhook, HTTP Request, Code, Switch, and Execute Command nodes
- ✓ A content distribution automation pipeline covering four platforms (blog + Xiaohongshu + WeChat + Bilibili)
- ✓ Real production pitfalls and their solutions, from Docker permission issues to API CSRF failures
I Built an Automated Content Factory with n8n — One Post, Four Platforms, 5 Minutes
Every time I finish a technical blog post, I need to publish it to four places: my personal blog (Hugo), Xiaohongshu, WeChat Official Account, and Bilibili Columns. Doing this manually took about 2 hours per post. Now I fire a webhook and all four platforms are ready in 5 minutes.
This post documents the entire build process — the complete workflow JSON, node-by-node configuration details, and every pitfall I hit along the way. You can import the workflow and replicate it.
Why I Built This
The Pain
I publish one piece of content per workday. The old workflow looked like this:
- Write the Markdown source (~2,000 words)
- Manually convert to Xiaohongshu format: add emoji, keep paragraphs under 100 characters, select 9 images
- Manually convert to WeChat format: paste into editor, tweak layout, upload images to media library
- Manually convert to Bilibili column format: add summary, pick tags, write intro
- Push the Markdown file to the Hugo repo, trigger deployment
Five steps, four platforms. Every single day, five days a week. That’s 10 hours a week spent on content shuffling.
And of those 10 hours, the only valuable work is “adapting content for each platform.” Most of the time vanishes into opening tabs, logging in, copy-pasting, and waiting for uploads.
Who Else Has This Problem
Not just me. A quick survey:
- Tech bloggers: 90% maintain 2+ platforms simultaneously
- Content creators: average 3.2 platforms per person (Xinbang, 2025 data)
- Educational YouTubers: 80% produce written content as a supplement
If you publish across multiple platforms, you face a distribution efficiency problem. The only difference is how much it hurts.
Why n8n
I compared three approaches:
| Option | Pros | Cons |
|---|---|---|
| Zapier/Make | Quick setup | Expensive, limited executions, poor support for Chinese platforms |
| Custom scripts | Full control | High development cost, maintenance burden |
| n8n | Free self-host, rich nodes, visual editor | Steeper learning curve than Zapier |
n8n won on two points: free self-hosting and native HTTP Request support. Chinese platforms rarely have public APIs, but they all have web interfaces. n8n’s HTTP Request node combined with cookies handles most scenarios.
Build Log: From Zero to Full Workflow
I built this in three phases:
- Day 1: Minimum viable flow — webhook trigger + content format conversion + Hugo push
- Day 2: Add Xiaohongshu and WeChat content generation
- Day 3: Add Bilibili + error handling + notifications
Day 1: Minimum Viable Flow
1.1 Install n8n
# One-command Docker deployment
docker run -d --name n8n \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
-e N8N_BASIC_AUTH_ACTIVE=true \
-e N8N_BASIC_AUTH_USER=admin \
-e N8N_BASIC_AUTH_PASSWORD=your-password \
n8nio/n8n
After launch, visit http://localhost:5678, create an account, and open the workflow editor.
1.2 Create the Webhook Trigger
First node: Webhook. This is the entry point for the entire workflow.
Node type: Webhook
HTTP Method: POST
Path: content-factory
Authentication: Header Auth (X-API-Key)
Response Mode: "Last Node" (wait for all nodes to finish before responding)
Test request:
curl -X POST http://localhost:5678/webhook/content-factory \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"title": "n8n Automation in Practice",
"content": "## What is n8n\nn8n is an open-source workflow automation tool...",
"tags": ["n8n", "automation"],
"images": ["https://example.com/img1.png", "https://example.com/img2.png"]
}'
The input data structure is the standard format that upstream systems (Notion, Obsidian plugins, or manual curl) push into. Defining this format is the key to the entire system — all downstream nodes depend on it.
My standard format:
{
"title": "Article Title",
"content": "Markdown body",
"tags": ["tag1", "tag2"],
"images": ["array of image URLs"],
"publish_date": "2026-03-25",
"meta": {
"description": "SEO description",
"category": "AI Automation"
}
}
1.3 Content Format Conversion — Code Node
After receiving the raw content, I need to convert it into platform-specific formats. I use a Code node:
Node name: Format Content
Language: JavaScript
const items = [];
// 1. Hugo blog format
items.push({
json: {
platform: "hugo",
filename: `content/posts/${$json.title.toLowerCase().replace(/\s+/g, '-')}.md`,
frontmatter: `---
title: "${$json.title}"
date: "${$json.publish_date}"
tags: [${$json.tags.map(t => `"${t}"`).join(', ')}]
description: "${$json.meta.description}"
---
`,
body: $json.content
}
});
// 2. Xiaohongshu format
const sections = $json.content
.split('\n\n')
.filter(s => s.trim())
.map(s => s.trim());
const xiaohongshuBody = sections
.map((section, i) => {
const emojis = ['📌', '💡', '🔥', '✅', '🎯', '⚡', '🚀'];
return `${emojis[i % emojis.length]} ${section}`;
})
.join('\n\n');
items.push({
json: {
platform: "xiaohongshu",
title: $json.title + ' | ' + $json.tags[0],
content: xiaohongshuBody.substring(0, 1000), // Xiaohongshu character limit
images: $json.images.slice(0, 9), // max 9 images
wordCount: xiaohongshuBody.substring(0, 1000).length
}
});
// 3. WeChat format
items.push({
json: {
platform: "wechat",
title: $json.title,
content: $json.content
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br/>'),
digest: $json.meta.description
}
});
// 4. Bilibili column format
items.push({
json: {
platform: "bilibili",
title: $json.title,
content: $json.content,
summary: $json.meta.description.substring(0, 200),
tags: $json.tags,
category_id: 122 // Tech > Digital > AI
}
});
return items;
This node turns one input into four outputs, one per platform. Each output’s fields match what that platform expects.
Verification: In n8n’s execution panel, I saw 1 item in, 4 items out, with platform fields of hugo, xiaohongshu, wechat, bilibili respectively. ✅
1.4 Routing with the Switch Node
The four items need different processing paths. I use a Switch node:
Node name: Route by Platform
Routing rules:
- Output 0: {{ $json.platform === 'hugo' }}
- Output 1: {{ $json.platform === 'xiaohongshu' }}
- Output 2: {{ $json.platform === 'wechat' }}
- Output 3: {{ $json.platform === 'bilibili' }}
- Default: unmatched items go to error path
1.5 Hugo Blog Publishing — Execute Command + Git Push
Hugo publishing requires two steps: write the file to the repo, then push to trigger deployment.
Node A: Write Hugo Post
I used n8n’s Execute Command node to operate directly on the local filesystem:
Node name: Write Hugo Post
Command: |
cat > /path/to/hugo-repo/content/posts/{{$json.filename}} << 'EOF'
{{$json.frontmatter}}{{$json.body}}
EOF
Node B: Git Commit and Push
Node name: Git Push Hugo
Command: |
cd /path/to/hugo-repo
git add .
git commit -m "auto: {{$json.title}}"
git push origin main
Note: n8n’s Docker container has an isolated filesystem. To let n8n operate on the host’s Git repo, you need to mount the directory:
docker run -v /path/to/hugo-repo:/data/hugo-repo ...
Day 1 verification:
- Sent a test article via curl
- 30 seconds later, new file appeared in Hugo repo
- 2 minutes later, Cloudflare Pages deployment complete, blog updated ✅
Minimum viable flow is working.
Day 2: Xiaohongshu and WeChat Content Generation
2.1 Xiaohongshu: Content Adaptation + Image Checklist
Xiaohongshu has no public API. My approach is semi-automated: n8n generates the adapted content and image list to a local file, then I manually copy it into the Xiaohongshu app.
Node: Xiaohongshu Content Output
Node name: Output Xiaohongshu
Type: Execute Command (Write File)
Path: /data/output/xiaohongshu/{{$json.title}}.txt
Content template: |
[Title] {{$json.title}}
[Body]
{{$json.content}}
[Image Checklist]
{{$json.images.map((img, i) => (i+1) + '. ' + img).join('\n')}}
[Word Count] {{$json.wordCount}} characters
Quick glance at the output:
[Title] n8n Automation in Practice | n8n
[Body]
📌 What is n8n
n8n is an open-source workflow automation tool...
💡 Core Advantages
Free self-hosting, rich node ecosystem...
[Image Checklist]
1. https://example.com/img1.png
2. https://example.com/img2.png
[Word Count] 856 characters
Copy title and body into Xiaohongshu, download images from the checklist. Time per post dropped from 30 minutes to 5 minutes. ✅
2.2 WeChat: HTML Formatting + Cover Image
WeChat Official Account also lacks a public API for individual accounts. Similar approach:
Node name: Output WeChat
Type: Execute Command (Write File)
Path: /data/output/wechat/{{$json.title}}.html
But WeChat needs better HTML. I added a styling layer in a Code node:
const styledHtml = `
<div style="max-width:677px;margin:0 auto;padding:20px;
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
font-size:16px;line-height:1.8;color:#333;">
<h1 style="font-size:22px;font-weight:bold;margin-bottom:24px;
color:#1a1a1a;border-bottom:2px solid #4a90d9;padding-bottom:12px;">
${$json.title}
</h1>
${$json.content}
<p style="margin-top:32px;padding-top:16px;border-top:1px solid #eee;
font-size:14px;color:#999;text-align:center;">
—— 鲲鹏AI探索局 ——
</p>
</div>
`;
Paste this HTML directly into WeChat’s editor in “source code” mode.
2.3 Bonus: AI-Assisted Content Rewriting
If you have the OpenAI API connected, you can add a node before format conversion to let AI rewrite content for different platforms:
Node name: AI Rewrite
Type: HTTP Request
URL: https://api.openai.com/v1/chat/completions
Method: POST
Headers:
Authorization: Bearer sk-xxx
Body:
{
"model": "gpt-4o-mini",
"messages": [
{"role": "system", "content": "You are a Xiaohongshu content expert. Rewrite the following in Xiaohongshu style: emoji-heavy, short paragraphs, conversational tone, interactive feel. Keep core information intact."},
{"role": "user", "content": "{{$json.content}}"}
],
"max_tokens": 1000,
"temperature": 0.7
}
I didn’t add this node. Reason: rewrite quality is inconsistent and occasionally drifts from the original meaning. For technical content, I prefer manual fine-tuning. But if you’re doing lifestyle content, give it a try.
Day 2 verification:
- Tested 3 articles, Xiaohongshu txt and WeChat html both generated correctly
- Manual publishing time dropped from 30 min to 5 min per post ✅
- Noticed Xiaohongshu emoji distribution was occasionally uneven — fixed in later optimization
Day 3: Bilibili + Error Handling + Notifications
3.1 Bilibili Column Publishing
Bilibili Columns have relatively complete APIs. You can publish via CSRF token + cookie using the HTTP Request node.
Step 1: Get CSRF Token
Node name: Get Bilibili CSRF
Type: HTTP Request
Method: GET
URL: https://api.bilibili.com/x/web-interface/nav
Headers:
Cookie: SESSDATA=your_sessdata; bili_jct=your_jct
The response contains data.csrf — save it to a variable.
Step 2: Create Draft
Node name: Create Bilibili Draft
Type: HTTP Request
Method: POST
URL: https://api.bilibili.com/x/article/creative/draft/addupdate
Headers:
Cookie: SESSDATA=your_sessdata; bili_jct=your_jct
Content-Type: application/x-www-form-urlencoded
Body (form-data):
title: {{$json.title}}
content: {{$json.content}}
summary: {{$json.summary}}
words: {{$json.summary.length}}
category: {{$json.category_id}}
list_id: 0
tid: 0
csrf: {{$node["Get Bilibili CSRF"].json.data.csrf}}
Getting a draft_id back means success.
Step 3: Manual Publish
After the draft is created, I manually review it in Bilibili’s backend before publishing. Fully automated publishing is too risky — if the formatting is broken, there’s no taking it back.
3.2 Error Handling
The workflow has multiple failure points. I use n8n’s Error Trigger combined with If nodes for monitoring:
Workflow name: Content Factory Error Handler
Trigger: Error Trigger
Subsequent nodes:
If: {{$json.execution.error.message.includes("401")}}
True → Send notification: "API authentication failed, check cookies"
If: {{$json.execution.error.message.includes("ECONNREFUSED")}}
True → Send notification: "Target service unreachable"
If: {{$json.execution.error.message.includes("timeout")}}
True → Send notification: "Request timed out, possible network issue"
Default → Send notification: "Unknown error: {{$json.execution.error.message}}"
Notifications go through Feishu (Lark) Webhook:
Node name: Send Feishu Alert
Type: HTTP Request
Method: POST
URL: https://open.feishu.cn/open-apis/bot/v2/hook/your-webhook-id
Body:
{
"msg_type": "interactive",
"card": {
"header": {
"title": {"tag": "plain_text", "content": "⚠️ Content Factory Alert"},
"template": "red"
},
"elements": [
{"tag": "div", "text": {"tag": "lark_md", "content": "**Error:** {{$json.error_msg}}\n**Platform:** {{$json.platform}}\n**Time:** {{$now.format('yyyy-MM-dd HH:mm')}}"}}
]
}
}
3.3 Success Notification
After each platform finishes processing, I aggregate results and send a summary:
Node name: Send Success Summary
Type: HTTP Request (Feishu Webhook)
Body:
{
"msg_type": "interactive",
"card": {
"header": {
"title": {"tag": "plain_text", "content": "✅ Content Distribution Complete"},
"template": "green"
},
"elements": [
{"tag": "div", "text": {"tag": "lark_md", "content": "**Title:** {{$json.title}}\n**Hugo:** ✅ Deployed\n**Xiaohongshu:** 📋 Ready for manual publish\n**WeChat:** 📋 Ready for manual publish\n**Bilibili:** 📝 Draft created"}}
]
}
}
Day 3 verification:
- Bilibili draft created successfully, received
draft_id✅ - Deliberately disconnected network to test error handling — Feishu alert received ✅
- Full workflow from trigger to notification: ~45 seconds (Hugo deployment excluded) ✅
Before vs. After: The Numbers
Real data from 30 published articles:
| Metric | Before | After | Change |
|---|---|---|---|
| Time per post | 120 min | 5 min | -95.8% |
| Weekly distribution time | 10 hours | 25 min | -95.8% |
| Formatting error rate | ~15% (missing tags, mismatched images) | ~2% (mainly Markdown special chars) | -86.7% |
| Platforms covered | 3–4 (often skipped to just 2) | Consistently 4 | +33% |
| Monthly output | 12 posts | 20 posts | +66.7% |
That last row was an unexpected bonus. Because distribution cost dropped from 2 hours to 5 minutes, the “psychological friction” of writing plummeted, and output naturally increased.
The time savings are the most tangible:
Before: Writing (3h) + Distribution (2h) = 5h/post
After: Writing (3h) + Distribution (5min) = 3h5min/post
Weekly savings: 9.5 hours ≈ 1.2 extra working days
Pitfall Log
Pitfall 1: n8n Code Nodes Don’t Support ES Modules
Symptom: Writing import fetch from 'node-fetch' in a Code node throws Cannot use import statement outside a module.
Cause: n8n’s Code node runs in a sandboxed environment that only supports CommonJS syntax. No import, no require(), no external npm packages.
Fix: Move all external HTTP requests to n8n’s HTTP Request node. Keep Code nodes for pure data transformation only. This is n8n’s design philosophy — Code nodes handle data orchestration, HTTP Request nodes handle network calls.
Pitfall 2: Xiaohongshu Content Truncation from Character Miscalculation
Symptom: First few test posts were fine, but a long article (3,000-word source) produced Xiaohongshu content that was cut off mid-sentence.
Cause: The Code node used .substring(0, 1000) for length limiting, but Xiaohongshu’s actual limit is 1,000 characters (not words). And Xiaohongshu’s backend counts characters differently — emoji take 2 character positions, URLs expand, etc.
Fix:
// Accurate Xiaohongshu character count
function xhsCharCount(str) {
let count = 0;
for (const char of str) {
if (/[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(char)) {
count += 4; // emoji surrogate pair
} else if (/[\u4e00-\u9fff]/.test(char)) {
count += 1;
} else {
count += 1;
}
}
return count;
}
// Truncate to 950 characters, leaving 50 as buffer
let xhsContent = xiaohongshuBody;
while (xhsCharCount(xhsContent) > 950) {
xhsContent = xhsContent.slice(0, -1);
}
Pitfall 3: Git Permission Issues Inside Docker
Symptom: git push fails with Permission denied (publickey).
Cause: n8n runs inside a Docker container without the host’s SSH keys. Even with the directory mounted, Git’s SSH authentication uses the container’s ~/.ssh, which is empty.
Fix: Two options:
Option A: Mount the host’s SSH key into the container
docker run \
-v ~/.ssh:/root/.ssh:ro \
-v /path/to/hugo-repo:/data/hugo-repo \
...
Option B (more secure): Use a GitHub Personal Access Token instead of SSH
# In the Execute Command node
git remote set-url origin https://ghp_xxx@github.com/user/repo.git
git push origin main
I went with Option B. A PAT can be scoped to read+push only, without exposing the full SSH key.
Pitfall 4: Bilibili API CSRF Validation Failures
Symptom: Calling the Bilibili column API returns {"code":-352,"message":"Permission check failed"}.
Cause: Bilibili’s API requires bili_jct (the CSRF token) in both the request Cookie header and the POST body. The values must match, and the token must be valid for the current session.
Fix:
- First call
/x/web-interface/navto get the latestbili_jct - Include this value in both the Cookie header and request body of subsequent calls
- SESSDATA expires (default: 30 days). You’ll need to re-login and refresh it when it does.
// Pre-process in a Code node
const csrfToken = $node["Get Bilibili CSRF"].json.data.bili_jct;
return {
cookie: `SESSDATA=${$env.BILIBILI_SESSDATA}; bili_jct=${csrfToken}`,
csrf: csrfToken
};
Complete Workflow JSON
Below is the full n8n workflow JSON, ready to import.
{
"name": "Content Factory - Multi-Platform Distribution",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "content-factory",
"responseMode": "lastNode",
"options": {}
},
"id": "webhook-1",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [0, 0],
"webhookId": "content-factory-trigger"
},
{
"parameters": {
"jsCode": "// Content Format Transformer\n// Input: standard content object\n// Output: 4 items (hugo, xiaohongshu, wechat, bilibili)\n\nconst items = [];\nconst input = $input.first().json;\n\n// 1. Hugo Blog\nitems.push({\n json: {\n platform: \"hugo\",\n filename: `content/posts/${input.title.toLowerCase().replace(/[^a-z0-9\\u4e00-\\u9fff]+/g, '-')}.md`,\n frontmatter: `---\ntitle: \"${input.title}\"\ndate: \"${input.publish_date}\"\ntags: [${input.tags.map(t => `\"${t}\"`).join(', ')}]\ndescription: \"${input.meta.description}\"\ncategory: \"${input.meta.category}\"\n---\n\n`,\n body: input.content,\n title: input.title\n }\n});\n\n// 2. Xiaohongshu\nconst sections = input.content.split('\\n\\n').filter(s => s.trim()).map(s => s.trim());\nconst emojis = ['📌', '💡', '🔥', '✅', '🎯', '⚡', '🚀', '📊', '🔑'];\nconst xhsBody = sections.map((s, i) => `${emojis[i % emojis.length]} ${s}`).join('\\n\\n');\nlet xhsContent = xhsBody;\nlet count = 0;\nfor (const ch of xhsContent) { if (count >= 950) { xhsContent = xhsContent.slice(0, count); break; } count++; }\n\nitems.push({\n json: {\n platform: \"xiaohongshu\",\n title: input.title + ' | ' + (input.tags[0] || ''),\n content: xhsContent,\n images: (input.images || []).slice(0, 9),\n wordCount: xhsContent.length,\n sourceTitle: input.title\n }\n});\n\n// 3. WeChat\nconst wechatHtml = input.content\n .replace(/^## (.+)$/gm, '<h2 style=\"font-size:18px;font-weight:bold;margin:24px 0 12px;\">$1</h2>')\n .replace(/^### (.+)$/gm, '<h3 style=\"font-size:16px;font-weight:bold;margin:20px 0 10px;\">$1</h3>')\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/`(.+?)`/g, '<code style=\"background:#f0f0f0;padding:2px 6px;border-radius:3px;\">$1</code>')\n .replace(/\\n/g, '<br/>');\n\nitems.push({\n json: {\n platform: \"wechat\",\n title: input.title,\n content: wechatHtml,\n digest: input.meta.description,\n sourceTitle: input.title\n }\n});\n\n// 4. Bilibili\nitems.push({\n json: {\n platform: \"bilibili\",\n title: input.title,\n content: input.content,\n summary: (input.meta.description || '').substring(0, 200),\n tags: input.tags,\n category_id: 122,\n sourceTitle: input.title\n }\n});\n\nreturn items;"
},
"id": "code-format",
"name": "Format Content",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [250, 0]
},
{
"parameters": {
"dataType": "string",
"value1": "={{ $json.platform }}",
"rules": {
"rules": [
{ "value2": "hugo", "output": 0 },
{ "value2": "xiaohongshu", "output": 1 },
{ "value2": "wechat", "output": 2 },
{ "value2": "bilibili", "output": 3 }
]
},
"fallbackOutput": 4
},
"id": "switch-platform",
"name": "Route by Platform",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [500, 0]
},
{
"parameters": {
"command": "mkdir -p /data/hugo-repo/content/posts && cat > '/data/hugo-repo/{{$json.filename}}' << 'EOFMARKER'\n{{$json.frontmatter}}{{$json.body}}\nEOFMARKER\ncd /data/hugo-repo && git add . && git commit -m 'auto: {{$json.title}}' && git push origin main"
},
"id": "hugo-deploy",
"name": "Hugo Deploy",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [750, -200]
},
{
"parameters": {
"command": "mkdir -p /data/output/xiaohongshu && cat > '/data/output/xiaohongshu/{{$json.sourceTitle}}.txt' << 'EOFMARKER'\n【标题】{{$json.title}}\n\n【正文】\n{{$json.content}}\n\n【配图清单】\n{{$json.images.map((img, i) => (i+1) + '. ' + img).join('\\n')}}\n\n【字数统计】{{$json.wordCount}}字\nEOFMARKER"
},
"id": "xhs-output",
"name": "Xiaohongshu Output",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [750, 0]
},
{
"parameters": {
"command": "mkdir -p /data/output/wechat && cat > '/data/output/wechat/{{$json.sourceTitle}}.html' << 'EOFMARKER'\n<div style=\"max-width:677px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:16px;line-height:1.8;color:#333;\">\n<h1 style=\"font-size:22px;font-weight:bold;margin-bottom:24px;color:#1a1a1a;border-bottom:2px solid #4a90d9;padding-bottom:12px;\">{{$json.title}}</h1>\n{{$json.content}}\n<p style=\"margin-top:32px;padding-top:16px;border-top:1px solid #eee;font-size:14px;color:#999;text-align:center;\">—— 鲲鹏AI探索局 ——</p>\n</div>\nEOFMARKER"
},
"id": "wechat-output",
"name": "WeChat Output",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [750, 200]
},
{
"parameters": {
"method": "GET",
"url": "https://api.bilibili.com/x/web-interface/nav",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Cookie", "value": "SESSDATA={{$env.BILIBILI_SESSDATA}}" }
]
},
"options": {}
},
"id": "bili-get-csrf",
"name": "Get Bilibili CSRF",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [750, 400]
},
{
"parameters": {
"method": "POST",
"url": "https://api.bilibili.com/x/article/creative/draft/addupdate",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Cookie", "value": "SESSDATA={{$env.BILIBILI_SESSDATA}}; bili_jct={{$json.data.bili_jct}}" },
{ "name": "Content-Type", "value": "application/x-www-form-urlencoded" }
]
},
"sendBody": true,
"bodyParameters": {
"parameters": [
{ "name": "title", "value": "={{$('Route by Platform').item.json.title}}" },
{ "name": "content", "value": "={{$('Route by Platform').item.json.content}}" },
{ "name": "summary", "value": "={{$('Route by Platform').item.json.summary}}" },
{ "name": "words", "value": "={{$('Route by Platform').item.json.summary.length}}" },
{ "name": "category", "value": "122" },
{ "name": "csrf", "value": "={{$json.data.bili_jct}}" }
]
},
"options": {}
},
"id": "bili-create-draft",
"name": "Create Bilibili Draft",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1000, 400]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[{ "node": "Format Content", "type": "main", "index": 0 }]
]
},
"Format Content": {
"main": [
[{ "node": "Route by Platform", "type": "main", "index": 0 }]
]
},
"Route by Platform": {
"main": [
[{ "node": "Hugo Deploy", "type": "main", "index": 0 }],
[{ "node": "Xiaohongshu Output", "type": "main", "index": 0 }],
[{ "node": "WeChat Output", "type": "main", "index": 0 }],
[{ "node": "Get Bilibili CSRF", "type": "main", "index": 0 }]
]
},
"Get Bilibili CSRF": {
"main": [
[{ "node": "Create Bilibili Draft", "type": "main", "index": 0 }]
]
}
}
}
Setup Checklist
After importing the workflow, you’ll need to configure the following:
| Config Item | How to Get It |
|---|---|
| n8n instance | Docker self-host (recommended) or n8n Cloud |
| Hugo Git repo | GitHub private repo with PAT configured |
| Xiaohongshu account | No API needed — manually copy from output files |
| WeChat Official Account | No API needed — manually copy from output files |
| Bilibili SESSDATA | Browser DevTools → Application → Cookies |
| Feishu Webhook URL | Feishu group settings → Bots → Add Bot |
| Optional: OpenAI API | For the AI content rewriting node |
Extension Ideas
This workflow is a starting point, not an endpoint. Here are directions to expand:
1. Upstream Integration with Notion/Obsidian Add a scheduled check node before the webhook, or use Notion’s database webhook to achieve “publish on write.”
2. Image Automation Use n8n’s HTTP Request node to call image compression APIs (like TinyPNG), or use Sharp for local processing. Xiaohongshu’s cover image ratio is 3:4 — you can auto-crop to that.
3. Analytics Feedback Loop 24 hours after distribution, scrape read counts from each platform and compile a weekly report. Use n8n’s Schedule Trigger to run daily.
4. Content Review Gate Add a “wait for approval” node after format conversion. n8n’s Wait node can pause the workflow until you reply “approved” in a Feishu group chat.
5. Multi-Language Distribution Plug in a translation API to auto-generate Chinese and English versions of the same content, distributed across different platform matrices.
I’ve been running this n8n content distribution workflow for two months, processing 50+ articles. Its value isn’t in replacing humans — it’s in compressing repetitive labor to near-zero, so you can focus your energy on the writing itself.
Automation isn’t the goal. More efficient creation is.
Key Takeaways
- • n8n is free to self-host and the best choice for content automation
- • One article automatically becomes four platform-specific outputs — from 2 hours to 5 minutes per post
- • The key is standardizing your input format so the workflow can batch-process everything
- • Ship a minimal viable flow first, then layer on features iteratively
FAQ
Is n8n free?
Yes — n8n has a fully-featured self-hosted version at no cost. The cloud tier starts at $20/month with 2,500 executions. Self-hosting is recommended for personal use.
Do I need programming skills?
n8n is a visual drag-and-drop tool, so no coding is required for basic workflows. However, configuring API integrations and debugging will require basic HTTP and JSON knowledge.
How do you automate posting to Xiaohongshu and WeChat?
Neither platform offers a public API for individual accounts. My approach is semi-automated: n8n generates platform-ready content to local files, then I manually copy-paste into each app. Browser automation with Playwright is another option.
Can I import the workflow JSON directly?
The full workflow JSON is at the bottom of this post. Import it into n8n and replace the API keys and account credentials with your own.
How does Bilibili video automation work?
Video automation is limited — mainly script generation and metadata can be automated. Editing and uploading still require manual intervention.
Subscribe to AI Insights
Weekly curated AI tools, tutorials, and insights delivered to your inbox.
支付宝扫码赞赏
感谢支持 ❤️