Message Template: Render JSON, XML, CSV and HL7 in n8n Without a Code Node

Table of contents
Message Template is an open-source n8n community node that renders a message from an expression template and outputs it as text, JSON, XML or a file. You write the message the way it should look, drop in {{ $json.field }} expressions, and pick whether to render it once per item or combine all items into one document. It replaces the Code node I used to reach for whenever an API wanted a body that was not plain JSON.
It is on npm as n8n-nodes-message-template, MIT licensed, with the source on GitHub. The current version is 0.1.0, so test it against your own payloads first.
What Message Template builds
The node is for anything where the output is shaped like text:
- API request bodies, as JSON you control character by character
- SOAP and other XML documents
- emails and notifications
- CSV exports
- line-based formats such as HL7
If your job is to reshape structured data from one schema to another, that is a different tool. I built Data Mapper for that. A rule of thumb that has held up: when you can describe the work as “this field goes there”, use Data Mapper. When you can describe it as “the output should look exactly like this”, use Message Template.
Two modes, four outputs
flowchart LR
accTitle: Message Template modes and output formats
accDescr: Input items go into the node. In Run Once for Each Item mode, the template is rendered for each item and produces one output per item. In Combine All Items mode, every item is rendered and joined with a header, separator and footer into a single output. The rendered string is then output as text in a field, as parsed JSON, as parsed structured XML, or as a binary file.
IN[Input items]
EACH["Run Once for Each Item<br/>one output per item"]
COMB["Combine All Items<br/>header + items + footer"]
TXT[Text in a field]
JSN[Parsed JSON]
XML[Structured XML]
FILE[Binary file]
IN --> EACH
IN --> COMB
EACH --> TXT
EACH --> JSN
EACH --> XML
EACH --> FILE
COMB --> TXT
COMB --> JSN
COMB --> XML
COMB --> FILE
| Mode | What it does |
|---|---|
| Run Once for Each Item | Renders the template for every input item and outputs one item each. |
| Combine All Items | Renders it for every item and joins the results into one output item, with an optional Header, Separator and Footer. |
Combine mode is how you build “one document from many items”. Set the header to [, the separator to , and the footer to ], and a template of {{ JSON.stringify({ id: $json.id, total: $json.total }) }} becomes a single JSON array. With the header <orders> and the footer </orders>, the same idea builds one XML document.
What each output format gives you
Text writes the rendered string to one field, message by default.
JSON parses the rendered string and uses it as the output item. A JSON array produces one output item per element, and a bare value is wrapped as { "data": value }. I ran two orders through a combine-mode array template, and got back two separate items, one per order.
XML parses the rendered string into structured data you can browse and drag fields from in the output panel, using the same $ (attributes) and _ (text) keys as n8n’s built-in XML node. Two orders rendered inside <orders> come out as:
{
"orders": {
"order": [
{ "$": { "id": "A1" }, "total": "10.5" },
{ "$": { "id": "B2" }, "total": "7" }
]
}
}
Later nodes then use {{ $json.orders.order[0].total }} like any other field. All values are strings, as with the XML node. Turn on Add Formatted XML Field and the node also stores the pretty-printed XML text, ready to send in an HTTP Request body.
File stores the rendered string as a binary file with the file name and MIME type you choose, ready for Write Files to Disk, email attachments, FTP or S3.
Why a template instead of a Code node
You can read it. A Code node that builds XML out of string concatenation hides the shape of the output inside the code. A template shows the document, with the dynamic parts marked:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetCustomer><Id>{{ $json.id }}</Id></GetCustomer>
</soap:Body>
</soap:Envelope>
Anyone who knows the target format can review that without knowing JavaScript. It also uses n8n expressions you already know: $json, $itemIndex, $now, $('Other node'). An empty template outputs an empty message, and an expression that returns an object is converted to JSON text for you.
Escaping is your job
The node does not escape values for the format you are building. It cannot know whether {{ $json.name }} is going into JSON, XML or a CSV cell. That puts three small habits on you:
- JSON: wrap values in
{{ JSON.stringify($json.field) }}, which adds the quotes and escapes special characters. - XML: replace
&and<, for example{{ $json.name.replaceAll('&', '&').replaceAll('<', '<') }}. - CSV: wrap text in double quotes and double any quote inside it. Do not use
JSON.stringifyhere. It escapes an inner quote as\", while CSV expects"", so a name likeBo "the" Builderbreaks the file.
When the XML or JSON does not parse, the error says what went wrong. XML errors include the line and column, and JSON errors suggest the escaping fix.
Line endings for formats that care
Under Options, Line Endings converts every line break to Unix (LF), Windows (CRLF) or carriage return only (CR). That last one is not an oddity. HL7 messages separate segments with a carriage return, so a template written with normal line breaks can be converted on output. Nothing else is touched. I checked that a backslash in an HL7 header like MSH|^~\&| survives unchanged.
Install it
In n8n, open Settings → Community Nodes, choose Install, enter n8n-nodes-message-template, and accept the risk notice. The node appears as Message Template. For a self-hosted setup that uses the custom folder, the package has no runtime dependencies:
mkdir -p custom && cd custom
npm pack n8n-nodes-message-template
tar -xzf n8n-nodes-message-template-*.tgz && mv package n8n-nodes-message-template
Restart n8n afterwards. It targets n8n 1.x and 2.x. Turn on Settings → On Error → Continue if you want failed items to come out as { "error": "…" } instead of stopping the workflow.
Limits to know about
- It is a renderer, not a templating language. There are no loops or conditionals of its own. Use combine mode for repetition and a JavaScript expression inside
{{ }}for small decisions. - Header, separator and footer come from the first item in combine mode, along with the options.
- XML values are always strings, so convert numbers in a later node when you need them.
- It is version 0.1.0. The behavior described here is covered by the repo’s tests, but your real payloads are the test that matters.
FAQ
Is the Message Template node free?
Yes. It is open source under the MIT license, published on npm as n8n-nodes-message-template, with the code on GitHub.
How is Message Template different from the Set node or a Code node?
The Set node builds structured fields. A Code node builds anything, but the output format is hidden inside code. Message Template lets you write the output itself, whether that is XML, CSV or a line-based format, and parses it back into n8n data when you want it as JSON or XML.
Can it build one file from many items?
Yes. In Combine All Items mode, every item is rendered and joined using the header, separator and footer you set, which gives you a single CSV, JSON array or XML document.
Does it escape special characters for me?
No. You escape values for the format you are building, using JSON.stringify for JSON, replacing & and < for XML, and doubling quotes for CSV. Invalid output fails with a clear error instead of passing along broken data.
Can it send HL7 messages?
It can build them. Write one segment per line in the template and set Line Endings to Carriage Return Only. Delivering the message is the job of whichever node you connect after it.
