One Word template. JSON data. Finished docs.
Author a .docx in Word with {{placeholders}}, post it with your data, and get the filled document back. Add {{#if}} to show or hide sections and {{#each}} to repeat rows.
Download a working template
A one-page policy schedule using every feature below — nested fields, an {{#if}} clause, and an {{#each}}table. Open it in Word to see how it's authored, then post it to /v1/render.
Quick start
Send the template as base64 (template_bytes) plus a data object. The response body is the finished .docx.
curl -X POST https://mergedocx-production.up.railway.app/v1/render \
-H "Authorization: Bearer $MERGEDOCX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_bytes": "'"$(base64 < template-demo.docx)"'",
"data": {
"policy_no": "P-2026-0098",
"holder": { "name": "Acme Manufacturing Ltd" },
"flood": true,
"flood_limit": "$5,000,000",
"items": [
{ "desc": "Buildings", "sum": "$5,000,000" },
{ "desc": "Contents", "sum": "$1,200,000" }
],
"paid": false,
"balance": "$4,320"
}
}' --output policy.docxAlready uploaded the template once? Reference it by hash with template_hash instead of resending the bytes.
Syntax
| In your template | Kind | What it does |
|---|---|---|
{{key}} | Fill | Replaced with the value of key. Left as-is if the key is absent. |
{{holder.name}} | Nested | Dotted path into a nested JSON object. |
«KEY» / [[KEY]] | Aliases | Same as {{KEY}} — for templates authored in Word without curly braces. |
{{#if key}} … {{/if}} | Conditional | Keeps the block only when key is truthy. |
{{#unless key}} … {{/unless}} | Conditional | Keeps the block only when key is falsy or missing. |
{{#each items}} … {{/each}} | Loop | Repeats the block once per array item; {{field}} inside reads that item. |
Loops fill tables
Put {{#each items}} in the first cell of a table row and {{/each}} in the last. The whole row is cloned once per item — borders, fonts, and cell widths copied exactly. It works for plain paragraphs too: wrap a block of paragraphs and it repeats.
Row template Data Output
───────────────────── ─────────────────────────── ─────────────────
| {{#each items}} | items: [ | Buildings | $5M |
| {{desc}} | { desc: "Buildings", | Contents | $1M |
| {{sum}} | sum: "$5M" }, |
| {{/each}} | { desc: "Contents", | (one row per item,
sum: "$1M" } ] cloned in place)Three rules worth knowing
- 01.Missing keys are left untouched. A
{{key}}with no matching data stays in the document verbatim — never blanked, never an error. Fills always return200. - 02.One block marker per line. Put each
{{#if}},{{#each}}, and its closing tag on its own paragraph or table row — not inline mid-sentence. An opening and its closing tag must sit in the same table cell. - 03.Structural mistakes fail closed. An unbalanced tag, a loop over a non-array, or a marker that crosses a table boundary returns
400with a plain message — never a corrupted.docx.
Endpoints
| Endpoint | You send | You get |
|---|---|---|
POST /v1/render | template + one data object | One finished .docx. |
POST /v1/render_batch | template + records[] (+ merge) | A .zip of N docs, or one merged .docx when merge:true. |
POST /v1/template/inspect | template only | Lists every placeholder the template exposes. |
Need to merge several finished documents into one file instead? That's /v1/merge.
Make your first render
Grab an API key, download the sample template, and post it with your data.