Template engine

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.

Start here

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.docx

Already uploaded the template once? Reference it by hash with template_hash instead of resending the bytes.

Syntax

In your templateKindWhat it does
{{key}}FillReplaced with the value of key. Left as-is if the key is absent.
{{holder.name}}NestedDotted path into a nested JSON object.
«KEY» / [[KEY]]AliasesSame as {{KEY}} — for templates authored in Word without curly braces.
{{#if key}} … {{/if}}ConditionalKeeps the block only when key is truthy.
{{#unless key}} … {{/unless}}ConditionalKeeps the block only when key is falsy or missing.
{{#each items}} … {{/each}}LoopRepeats 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

  1. 01.Missing keys are left untouched. A {{key}} with no matching data stays in the document verbatim — never blanked, never an error. Fills always return 200.
  2. 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.
  3. 03.Structural mistakes fail closed. An unbalanced tag, a loop over a non-array, or a marker that crosses a table boundary returns 400 with a plain message — never a corrupted .docx.

Endpoints

EndpointYou sendYou get
POST /v1/rendertemplate + one data objectOne finished .docx.
POST /v1/render_batchtemplate + records[] (+ merge)A .zip of N docs, or one merged .docx when merge:true.
POST /v1/template/inspecttemplate onlyLists 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.