This workflow follows the Agent → Ollama Chat 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 →
{
"name": "Exemplar Moderation",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "moderation",
"responseMode": "responseNode",
"authentication": "headerAuth",
"options": {}
},
"id": "a1b2c3d4-0001-4000-8000-000000000001",
"name": "Moderation Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
-1040,
0
],
"notes": "Receives POST from the bot's AI-moderation orchestrator. Header Auth: X-API-Key must match N8N_API_KEY.",
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Flatten the bot payload. n8n's Webhook node nests the POST body under\n// `body`; fall back to the root for manual test executions.\nconst b = $input.first().json.body ?? $input.first().json;\n\nreturn [\n {\n json: {\n userId: b.userId ?? '',\n userName: b.userName ?? '',\n message: b.message ?? '',\n channelId: b.channelId ?? '',\n channelName: b.channelName ?? '',\n serverId: b.serverId ?? '',\n recentWarnings: Array.isArray(b.recentWarnings) ? b.recentWarnings : [],\n serverRules: b.serverRules ?? '',\n mode: b.mode ?? 'moderate',\n timestamp: b.timestamp ?? new Date().toISOString(),\n },\n },\n];"
},
"id": "a1b2c3d4-0002-4000-8000-000000000002",
"name": "Extract payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-820,
0
],
"notes": "Unwrap the webhook body and normalise defaults."
},
{
"parameters": {
"promptType": "define",
"text": "=Channel: {{ $json.channelName }}\nUser: {{ $json.userName }}\nServer rules: {{ $json.serverRules }}\nRecent warnings: {{ JSON.stringify($json.recentWarnings) }}\nMessage: {{ $json.message }}",
"options": {
"systemMessage": "/no_think\nYou are a Discord content moderator for a single Discord server. For each incoming message decide exactly one of four actions:\n\n- \"allow\" \u2014 the message is fine.\n- \"warn\" \u2014 the message breaks community norms (light insult, mild spam, off-topic noise). The user receives a tracked warning.\n- \"timeout\" \u2014 the message is bad enough to silence the user temporarily (harassment, repeated rule-breaking, NSFW where forbidden). You MUST include a duration: \"30s\", \"5m\", \"1h\", \"1d\". Cap your suggestion at \"1d\" unless extreme.\n- \"delete\" \u2014 the message itself must be removed but the user is otherwise fine (spam links, leaked secrets, accidental personal info).\n\nContext the bot gives you in each request:\n- \"Server rules\": the operator's server-specific rules. If present, treat these as the primary authority. If empty, fall back to the generic baseline below.\n- \"Recent warnings\": the user's last 5 warnings (any age) with reason and ISO timestamp. Use them to judge \"repeated\" rule-breaking objectively: a clean user gets the benefit of the doubt; a user with several recent warns for similar behaviour should escalate toward \"timeout\".\n\nGeneric community baseline (apply when Server rules is empty):\n- No slurs, harassment, or targeted hate.\n- No spam, mass-mentions, or link-flooding.\n- No NSFW content outside channels explicitly designated for it.\n- No doxxing or leaking of personal information.\n- No illegal content.\n\nReturn ONLY valid JSON, no markdown fences, no commentary. Shape:\n {\"action\": \"<action>\", \"reason\": \"<short Polish-language reason>\", \"duration\": \"<only if action is timeout>\"}\n\nBe conservative: prefer \"allow\" over false positives. The bot fails open on malformed JSON, so a partial output is worse than \"allow\"."
}
},
"id": "a1b2c3d4-0003-4000-8000-000000000003",
"name": "Moderator",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
-560,
0
],
"notes": "Classifies the message. Output lands in $json.output for the parser."
},
{
"parameters": {
"model": "qwen3:8b",
"options": {
"format": "json",
"temperature": 0.1,
"numCtx": 4096,
"keepAlive": "30m"
}
},
"id": "a1b2c3d4-0004-4000-8000-000000000004",
"name": "Qwen3 8B (moderation)",
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"typeVersion": 1,
"position": [
-560,
220
],
"credentials": {
"ollamaApi": {
"name": "<your credential>"
}
},
"notes": "format:json forces valid JSON. Swap the model for qwen3.5:9b or qwen2.5:7b-instruct as you prefer."
},
{
"parameters": {
"jsCode": "// Parse + validate the LLM verdict and shape it into the exact response the\n// bot reads (result.data.verdict). Fail open to `allow` on any problem.\nconst raw = $input.first().json;\n\n// The Agent node returns its text in `output`; the other keys cover plain\n// chat-model / chain setups so this parser is drop-in either way.\nconst candidates = [\n raw.output,\n raw.text,\n raw.message?.content,\n raw.content,\n raw.output_text,\n raw.choices?.[0]?.message?.content,\n];\nlet text = candidates.find((c) => typeof c === 'string' && c.length > 0) ?? '';\n\n// Strip ```json fences and any qwen <think> block if thinking wasn't disabled.\ntext = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\ntext = text.replace(/^```(?:json)?\\s*/i, '').replace(/```\\s*$/i, '').trim();\n\nconst ALLOWED = new Set(['allow', 'warn', 'timeout', 'delete']);\nconst DURATION_RE = /^\\d+[smhd]$/;\n\nfunction failOpen(reason) {\n return [{ json: { verdict: { action: 'allow', reason: `parse-fallback: ${reason}` } } }];\n}\n\nlet parsed;\ntry {\n parsed = JSON.parse(text);\n} catch (err) {\n return failOpen('invalid JSON');\n}\n\nif (!parsed || typeof parsed !== 'object') return failOpen('not an object');\nif (!ALLOWED.has(parsed.action)) return failOpen('unknown action');\nif (parsed.action === 'timeout') {\n if (typeof parsed.duration !== 'string' || !DURATION_RE.test(parsed.duration)) {\n return failOpen('timeout missing valid duration');\n }\n}\n\nconst verdict = { action: parsed.action };\nif (typeof parsed.reason === 'string') verdict.reason = parsed.reason;\nif (typeof parsed.duration === 'string') verdict.duration = parsed.duration;\n\nreturn [{ json: { verdict } }];"
},
"id": "a1b2c3d4-0005-4000-8000-000000000005",
"name": "Parse verdict",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-300,
0
],
"notes": "Validates action + duration; fails open to allow."
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"verdict\": $json.verdict } }}",
"options": {
"responseCode": 200
}
},
"id": "a1b2c3d4-0006-4000-8000-000000000006",
"name": "Respond to bot",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
-60,
0
],
"notes": "Returns { verdict: {...} } \u2014 the shape the bot reads as result.data.verdict."
}
],
"connections": {
"Moderation Webhook": {
"main": [
[
{
"node": "Extract payload",
"type": "main",
"index": 0
}
]
]
},
"Extract payload": {
"main": [
[
{
"node": "Moderator",
"type": "main",
"index": 0
}
]
]
},
"Qwen3 8B (moderation)": {
"ai_languageModel": [
[
{
"node": "Moderator",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Moderator": {
"main": [
[
{
"node": "Parse verdict",
"type": "main",
"index": 0
}
]
]
},
"Parse verdict": {
"main": [
[
{
"node": "Respond to bot",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"tags": []
}
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.
httpHeaderAuthollamaApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Exemplar Moderation. Uses agent, lmChatOllama. Webhook trigger; 6 nodes.
Source: https://github.com/whiteravens20/exemplar/blob/dev/docs/moderation-workflow.n8n.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 workflow is designed for organizations or services managing appointments, such as interview scheduling, class enrollments, or client meetings. It’s ideal for users who want to automate appointmen
This workflow implements a privacy-preserving AI document processing pipeline that detects, masks, and securely manages Personally Identifiable Information (PII) before any AI processing occurs.
FullProject. Uses postgres, ssh, httpRequest, agent. Webhook trigger; 34 nodes.
This automated n8n workflow enables an AI-powered movie recommendation system on WhatsApp. Users send messages like "I want to watch a horror movie" or "Where can I watch the Jumanji movie?" The workf
This n8n workflow enables an AI-powered symptom checker where users input symptoms via a form or chat, analyzes them using an AI model, matches possible conditions, and suggests relevant doctors with