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.