How it works
You design your document in LibreOffice Writer, typing {placeholders} where
you want data inserted. odf-kit's fillTemplate() function reads the .odt
file, replaces all placeholders with your data, and returns a new .odt file. The
formatting, fonts, images, and layout from your template are preserved — only the
placeholder text changes.
The syntax follows conventions established by Mustache and docxtemplater, so it'll feel familiar if you've used either. odf-kit's template engine is a clean-room implementation purpose-built for OpenDocument Format.
Template syntax
| Syntax | Description | Example data |
|---|---|---|
| {tag} | Simple replacement | { name: "Alice" } |
| {object.property} | Dot notation | { user: { name: "Alice" } } |
| {#items}...{/items} | Loop (array) | { items: [{}, {}] } |
| {#flag}...{/flag} | Conditional (truthy/falsy) | { flag: true } |
Step 1 — Create a template in LibreOffice
Open LibreOffice Writer and create a document. Type {placeholders} directly
in the text wherever you want data inserted. For example, an invoice template might look
like:
Invoice #{invoiceNumber}
Date: {date}
Bill to: {customer}
{company.address}
Save it as invoice-template.odt. You can format the template however you want
— fonts, colors, tables, images, headers, footers. Everything is preserved when the
template is filled.
Step 2 — Install odf-kit and fill the template
import { fillTemplate } from "odf-kit"; import { readFileSync, writeFileSync } from "fs"; const template = readFileSync("invoice-template.odt"); const result = fillTemplate(template, { invoiceNumber: "2026-042", date: "February 23, 2026", customer: "Acme Corp", company: { name: "Acme Corp", address: "123 Main St, Springfield", }, }); writeFileSync("invoice-001.odt", result);
That's it. Every {placeholder} in the template is replaced with the
corresponding value from your data object. Values are automatically XML-escaped — no risk
of breaking the document with special characters like < or
&.
Browser usage: The same fillTemplate() function works
client-side. Get template bytes from a <input type="file"> or
fetch(), and trigger a download with Blob +
URL.createObjectURL(). No server required — user data never leaves the
browser.
Loops — repeating content
Use {#arrayName}...{/arrayName} to repeat content for each item in an array.
Inside the loop, use {property} to access each item's fields.
In your template, you might have a table where one row contains loop markers:
{#items}
{product} — Qty: {qty} — {price}
{/items}
const result = fillTemplate(template, { items: [ { product: "Widget", qty: 5, price: "$125" }, { product: "Gadget", qty: 3, price: "$120" }, { product: "Gizmo", qty: 10, price: "$99" }, ], });
The content between {#items} and {/items} is repeated once for
each object in the array. Inside the loop, fields from the current item
({product}, {qty}, {price}) are resolved first,
then fields from the parent data object are checked — so you can reference top-level data
inside loops.
Conditionals — optional sections
The same syntax works for conditionals. If the value is a boolean, a non-empty string, or any truthy value (but not an array), the content is included or removed:
// Template: {#showNotes}Notes: {notes}{/showNotes} fillTemplate(template, { showNotes: true, notes: "Net 30 payment terms", }); // → "Notes: Net 30 payment terms" fillTemplate(template, { showNotes: false, }); // → "" (section removed entirely)
How odf-kit handles LibreOffice's XML fragmentation
When you type {customer} in LibreOffice, it often saves the XML as something
like
<text:span>{cust</text:span><text:span>omer}</text:span>
— the placeholder is split across multiple XML elements due to editing history, spell
checking, or formatting changes. Other template engines break on this.
odf-kit includes a dedicated placeholder healer that automatically
detects and reassembles fragmented placeholders before doing any replacements. It also
cleans up editing artifacts like <text:s/> and
<text:bookmark/> elements that LibreOffice inserts inside placeholders.
You don't need to do anything — it just works.
Headers and footers
Placeholders in document headers and footers are processed alongside the body. If your
template has {company.name} in the header, it gets replaced with the same
data.
What about building documents from scratch?
If you don't have an existing template and want to create .odt files programmatically with code, odf-kit does that too. See the Generate .odt Files in Node.js guide.