// 01 · CONTEXTJSON is a transport, not a presentation layer.
I've been running n8n in production for a year now — for editorial automation, internal ops, and the AI-assisted content pipelines I build for clients. About six months in, I hit the same problem on three different workflows: the output looked like garbage every time it left the platform.
JSON with literal \nsequences. Markdown that got mangled in Notion. Plain-text descriptions where every "smart quote" turned into ’. The fix turned out to be simpler than I expected, and it's been my default move ever since.
Most n8n workflows hand you data as JSON, which is great — until you need a human to read it. The default options are all bad in their own way:
- Plain text strips formatting and makes long descriptions a wall of run-on lines.
- Markdown works in some destinations (Notion, GitHub) and breaks in most (YouTube, Gmail, Slack rich-text).
- JSON-as-textis what you get if you don't transform anything — escape sequences and all.
JSON is a transport, not a presentation layer.// THE THESIS
What I actually want is rendered output. That's HTML's job.// THE CONCLUSION
One template, many destinations. Stop writing transforms.// THE PAYOFF
// 02 · THE FIXThe HTML node as a templating layer.
The HTML node in n8n is usually framed as "scrape a page" — but it's also a Handlebars-flavoured template renderer. You can pipe any upstream JSON into it and get formatted output the next node can consume. Here's the pattern I use:
// Handlebars template inside the HTML node {{#each items}} <h3>{{title}}</h3> <p>{{description}}</p> <a href="{{url}}">Read more →</a> {{/each}}
Set the node's output mode to HTML and the next node receives a clean, rendered string. From there I usually pipe it into one of four destinations:
// 03 · PROOFWhere it earns its keep.
I run a workflow that takes a video transcript, generates a structured description, and posts it to YouTube. Before the HTML node, I was getting blobs with literal escape sequences. After routing through it, the output renders cleanly in every destination I tested.
| // DESTINATION | NATIVE FORMAT | HTML NODE FIT ★ |
|---|---|---|
| YouTube | Plain text | STRONG |
| Gmail | HTML email | NATIVE |
| Notion | Markdown | SKIP |
| Slack | Block Kit | SKIP |
| WordPress | HTML | NATIVE |
// 04 · CAVEATSWhen not to use it.
// SKIP THIS WHEN
The destination accepts Markdown natively(Notion, Slack via Block Kit, GitHub issues). You're better off keeping the formatting language native to the destination.
Two other cases where the HTML node is overkill: when you need machine-readable output for the next step (keep it JSON), and when your template would be larger than your data (use String.template in a Code node instead).
// 05 · FAQQuick reference.
Q.01Does this work in self-hosted n8n and n8n Cloud?
Yes — the HTML node is a core node, available in both. No plugin or community node required.
Q.02Can I use it for emails with inline CSS?
Absolutely. Drop your inline-CSS email template in directly. Pair it with the Gmail or SMTP node and you've got a workflow-driven newsletter.
Q.03What about more complex logic — conditionals, loops?
Handlebars syntax handles {{#if}}, {{#each}}, and helpers. For anything beyond that, do the data work in a Code node first and keep the template dumb.