Skip to main content
RapidDev - Software Development Agency
n8n-tutorial

How to Fix Broken Markdown Formatting in Claude Messages from n8n

Broken markdown in Claude responses within n8n happens because special characters like asterisks, backticks, and brackets get escaped or stripped during data transfer between nodes. Fix this by using a Code node to unescape double-escaped characters, configuring the Respond to Webhook node with the correct content type, and ensuring downstream nodes treat the response as raw text rather than HTML.

What you'll learn

  • How to identify where markdown corruption occurs in the n8n node pipeline
  • How to fix double-escaped special characters in Claude responses
  • How to configure Respond to Webhook for correct markdown output
  • How to preserve code block formatting and newlines through n8n data transformations
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced10 min read20-25 minutesn8n 1.20+ (self-hosted and Cloud)March 2026RapidDev Engineering Team
TL;DR

Broken markdown in Claude responses within n8n happens because special characters like asterisks, backticks, and brackets get escaped or stripped during data transfer between nodes. Fix this by using a Code node to unescape double-escaped characters, configuring the Respond to Webhook node with the correct content type, and ensuring downstream nodes treat the response as raw text rather than HTML.

Preserving Markdown Formatting in Claude Responses Within n8n Workflows

Claude's responses often include rich markdown formatting: headers, bold text, code blocks, and bullet lists. When these responses pass through n8n nodes, the markdown can break in several ways. Asterisks get doubled or stripped, backticks disappear, newlines collapse into single lines, and code blocks lose their indentation. This tutorial identifies where markdown corruption occurs in the n8n data pipeline and provides fixes for each scenario.

Prerequisites

  • An n8n workflow with an Anthropic Chat Model or AI Agent node connected to Claude
  • A downstream node that consumes the Claude response (Respond to Webhook, Email, Slack, etc.)
  • Basic understanding of markdown syntax and JSON string escaping
  • Familiarity with the n8n Code node

Step-by-step guide

1

Identify where markdown breaks by inspecting each node's output

Run your workflow manually and click on each node in sequence to inspect the output. Start from the Claude or AI Agent node and trace the response through every downstream node. In the output panel, switch between Table view and JSON view to see how the markdown text is stored. In JSON view, look for double-escaped characters: \\n instead of \n for newlines, \\* instead of * for asterisks, or missing backticks. The node where the formatting first breaks is where you need to add a fix. Common culprits include Set nodes that re-assign the text value, Function/Code nodes that call JSON.stringify without handling special characters, and HTTP Request or Webhook Response nodes that encode the output.

Expected result: You can identify the exact node where markdown formatting breaks in the pipeline.

2

Add a Code node to fix double-escaped characters

Insert a Code node immediately after the node where formatting breaks. This node unescapes double-escaped characters that commonly corrupt markdown. Set it to Run Once for All Items. The code processes the Claude response text and replaces double-escaped newlines, tabs, asterisks, and backticks with their single-escaped equivalents. It also normalizes line endings and preserves code block indentation. Place this node before any output node like Respond to Webhook, Slack, or Email.

typescript
1// Code node: Fix Markdown Escaping
2// Mode: Run Once for All Items
3
4const items = $input.all();
5
6for (const item of items) {
7 let text = item.json.output || item.json.text || item.json.message || '';
8
9 // Fix double-escaped newlines
10 text = text.replace(/\\n/g, '\n');
11
12 // Fix double-escaped tabs
13 text = text.replace(/\\t/g, '\t');
14
15 // Fix escaped asterisks (broken bold/italic)
16 text = text.replace(/\\\*/g, '*');
17
18 // Fix escaped backticks (broken code blocks)
19 text = text.replace(/\\`/g, '`');
20
21 // Fix escaped brackets (broken links)
22 text = text.replace(/\\\[/g, '[');
23 text = text.replace(/\\\]/g, ']');
24
25 // Normalize line endings
26 text = text.replace(/\r\n/g, '\n');
27
28 item.json.formattedOutput = text;
29}
30
31return items;

Expected result: The markdown text has proper single-escaped characters, with newlines, bold, italic, and code blocks rendering correctly.

3

Configure Respond to Webhook with the correct content type

If your workflow returns the Claude response via Respond to Webhook, incorrect content type settings cause markdown to render as plain text or get HTML-encoded. In the Respond to Webhook node, set the Response Content Type to text/plain if you want the raw markdown, or text/html if you want to render it. For API consumers that parse markdown (like chat UIs), use application/json and wrap the markdown in a JSON field. If using text/html, you need to convert markdown to HTML first using a Code node with a simple markdown-to-HTML converter. Do NOT use application/json content type if you are putting raw text directly in the response body, as this will add extra JSON escaping.

typescript
1// For Respond to Webhook sending raw markdown:
2// Response Content Type: text/plain
3// Response Body: {{ $json.formattedOutput }}
4
5// For API consumers expecting JSON:
6// Response Content Type: application/json
7// Response Body:
8{
9 "response": "{{ $json.formattedOutput }}",
10 "status": "success"
11}

Expected result: The webhook response contains properly formatted markdown text with the correct content type header.

4

Preserve code blocks through Set and Merge nodes

Set nodes and Merge nodes can strip whitespace from text values, destroying code block indentation. When you need to pass Claude's response through a Set node, use the expression {{ $json.output }} directly rather than assigning it to a new field and then referencing it. If you must use a Set node, set the value type to String and use the expression editor rather than the fixed value input. For Merge nodes, use the Combine mode and ensure the text field from the Claude branch is the one preserved, not overwritten by the other branch. Add a test Code node after the Merge that validates the code blocks still have their original indentation.

typescript
1// Code node: Validate code block preservation
2const items = $input.all();
3
4for (const item of items) {
5 const text = item.json.formattedOutput || item.json.output || '';
6
7 // Check if code blocks still have indentation
8 const codeBlockRegex = /```[\s\S]*?```/g;
9 const codeBlocks = text.match(codeBlockRegex) || [];
10
11 for (const block of codeBlocks) {
12 const lines = block.split('\n');
13 const hasIndentation = lines.some(line => line.startsWith(' ') || line.startsWith('\t'));
14 if (!hasIndentation && lines.length > 2) {
15 console.log('WARNING: Code block may have lost indentation');
16 }
17 }
18}
19
20return items;

Expected result: Code blocks retain their original indentation and formatting through Set and Merge nodes.

5

Handle markdown in Slack and Email output nodes

Slack and Email nodes have their own markdown interpretation. Slack uses mrkdwn format which differs from standard markdown: bold is *text* not **text**, and code blocks use triple backticks without a language specifier working differently. Email nodes may strip markdown entirely or render it as plain text. For Slack, add a Code node that converts standard markdown to Slack mrkdwn before the Slack node. For Email, convert markdown to HTML. If you need both, branch the workflow and apply different formatting transformations for each destination.

typescript
1// Code node: Convert standard markdown to Slack mrkdwn
2const items = $input.all();
3
4for (const item of items) {
5 let text = item.json.formattedOutput || '';
6
7 // Convert standard bold **text** to Slack bold *text*
8 text = text.replace(/\*\*(.+?)\*\*/g, '*$1*');
9
10 // Convert headers ## to bold
11 text = text.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
12
13 // Convert standard italic _text_ to Slack italic _text_ (same)
14 // Convert links [text](url) to Slack <url|text>
15 text = text.replace(/\[(.+?)\]\((.+?)\)/g, '<$2|$1>');
16
17 item.json.slackMessage = text;
18}
19
20return items;

Expected result: Claude's markdown response is correctly formatted for the specific output channel, whether Slack, Email, or webhook.

Complete working example

markdown-fixer.js
1// Code node: Comprehensive Markdown Fixer for Claude Responses
2// Mode: Run Once for All Items
3// Place between Claude/AI Agent output and your destination node
4
5const items = $input.all();
6
7function fixMarkdown(text) {
8 if (!text) return '';
9
10 // Step 1: Fix double-escaped characters
11 let fixed = text;
12 let prev = '';
13 while (fixed !== prev) {
14 prev = fixed;
15 fixed = fixed.replace(/\\n/g, '\n');
16 fixed = fixed.replace(/\\t/g, '\t');
17 fixed = fixed.replace(/\\\*/g, '*');
18 fixed = fixed.replace(/\\`/g, '`');
19 fixed = fixed.replace(/\\\[/g, '[');
20 fixed = fixed.replace(/\\\]/g, ']');
21 }
22
23 // Step 2: Normalize line endings
24 fixed = fixed.replace(/\r\n/g, '\n');
25 fixed = fixed.replace(/\r/g, '\n');
26
27 // Step 3: Ensure blank lines before headers
28 fixed = fixed.replace(/([^\n])\n(#{1,6}\s)/g, '$1\n\n$2');
29
30 // Step 4: Ensure blank lines around code blocks
31 fixed = fixed.replace(/([^\n])\n```/g, '$1\n\n```');
32 fixed = fixed.replace(/```\n([^\n])/g, '```\n\n$1');
33
34 return fixed;
35}
36
37function toSlackMrkdwn(text) {
38 let slack = text;
39 slack = slack.replace(/\*\*(.+?)\*\*/g, '*$1*');
40 slack = slack.replace(/^#{1,6}\s+(.+)$/gm, '*$1*');
41 slack = slack.replace(/\[(.+?)\]\((.+?)\)/g, '<$2|$1>');
42 return slack;
43}
44
45function toPlainHtml(text) {
46 let html = text;
47 html = html.replace(/&/g, '&amp;');
48 html = html.replace(/</g, '&lt;');
49 html = html.replace(/>/g, '&gt;');
50 html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
51 html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
52 html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
53 html = html.replace(/^#{3}\s+(.+)$/gm, '<h3>$1</h3>');
54 html = html.replace(/^#{2}\s+(.+)$/gm, '<h2>$1</h2>');
55 html = html.replace(/^#{1}\s+(.+)$/gm, '<h1>$1</h1>');
56 html = html.replace(/\n/g, '<br>');
57 return html;
58}
59
60const results = [];
61
62for (const item of items) {
63 const raw = item.json.output || item.json.text || '';
64 const fixed = fixMarkdown(raw);
65
66 results.push({
67 json: {
68 ...item.json,
69 markdown: fixed,
70 slackMrkdwn: toSlackMrkdwn(fixed),
71 html: toPlainHtml(fixed)
72 }
73 });
74}
75
76return results;

Common mistakes when fixing Broken Markdown Formatting in Claude Messages from n8n

Why it's a problem: Using JSON.stringify on the Claude response in a Code node, which double-escapes all special characters

How to avoid: Pass the text directly as a string property on the JSON object. Do not wrap it in JSON.stringify unless you are building a JSON response body.

Why it's a problem: Setting Respond to Webhook content type to application/json when sending raw markdown text

How to avoid: Use text/plain for raw markdown or properly structure the response as a JSON object with the markdown in a named field.

Why it's a problem: Using Slack's message field with standard markdown syntax instead of Slack's mrkdwn format

How to avoid: Add a conversion Code node before the Slack node that transforms **bold** to *bold* and converts links to Slack's <url|text> format.

Why it's a problem: Assuming Table view in n8n shows the exact text that will be sent downstream

How to avoid: Always check JSON view for the actual string content. Table view may render or truncate markdown differently.

Why it's a problem: Not testing with code blocks that contain special characters like backticks inside backticks

How to avoid: Test with edge cases including nested code blocks, inline code with special chars, and multi-language code blocks.

Best practices

  • Always inspect node output in JSON view, not Table view, to see the actual character escaping
  • Minimize the number of nodes between Claude output and the final destination to reduce formatting corruption opportunities
  • Use text/plain content type for webhook responses that return raw markdown
  • Convert markdown to the target format (Slack mrkdwn, HTML) as the last step before the output node
  • Test with responses that contain all markdown elements: headers, bold, italic, code blocks, lists, and links
  • Avoid JSON.stringify on text that contains markdown unless you intend to double-escape it
  • Use the expression editor's preview to verify markdown characters are preserved before running the workflow
  • Store Claude's raw response in a separate field and transform copies for different output channels

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

My n8n workflow sends Claude responses to Slack but the markdown formatting is broken. Bold text shows as **text** instead of rendering, and code blocks lose their formatting. How do I convert standard markdown to Slack's mrkdwn format in a Code node?

n8n Prompt

Add a Code node after my AI Agent node that fixes double-escaped markdown characters in Claude's response and outputs three versions: raw markdown, Slack mrkdwn, and HTML. The response needs to preserve code block indentation.

Frequently asked questions

Why does Claude's markdown break when passing through n8n nodes?

n8n stores data as JSON objects. When text containing markdown special characters (*, `, [, ]) is serialized to JSON and deserialized between nodes, these characters can be double-escaped. Each node transition is a potential point where escaping can add extra backslashes.

How do I send Claude's markdown response as formatted HTML in an email?

Add a Code node before the Email node that converts markdown to HTML. Replace **bold** with <strong> tags, # headers with <h> tags, and newlines with <br> tags. Set the Email node to send HTML content, not plain text.

Does n8n have a built-in markdown renderer?

No, n8n does not include a built-in markdown-to-HTML converter. You need to handle the conversion in a Code node. For complex markdown, consider calling a markdown parsing library through an external API.

Why do code blocks in Claude responses lose their indentation?

Set nodes and some transformation operations trim or normalize whitespace. Preserve code blocks by avoiding Set nodes between the Claude output and your destination, or by extracting and reattaching code blocks after other transformations.

Can I preserve markdown formatting when storing Claude responses in a database?

Yes, store the response in a TEXT or JSONB column. Use a Code node to ensure the text is not double-escaped before inserting. When reading it back, the text should retain its original formatting.

How do I handle markdown in Claude responses sent to a React frontend?

Return the raw markdown via the Respond to Webhook node with text/plain content type. In your React frontend, use a library like react-markdown to render the markdown as HTML components.

Why does my Slack message show raw asterisks instead of bold text?

Slack uses mrkdwn format, not standard markdown. Standard markdown bold is **text** but Slack bold is *text*. Add a Code node before the Slack node that converts **text** to *text* and adjusts other syntax differences.

Can RapidDev help build n8n workflows with proper formatting pipelines?

Yes, RapidDev specializes in n8n workflow development and can build formatting pipelines that correctly transform LLM responses for any output channel including Slack, Email, webhooks, and databases.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.