Guide · TypeScript + JavaScript

How to generate .odt files in Node.js

A step-by-step tutorial for creating OpenDocument text files programmatically in JavaScript or TypeScript using odf-kit. Works in Node.js and browsers. No LibreOffice or desktop software required.

What you'll build

By the end of this guide, you'll have a Node.js script that generates a formatted .odt document with headings, paragraphs, a table, and styled text. The same code works in browsers too — see the browser usage section below. The output opens in LibreOffice, Google Docs, Microsoft Word, and any application that supports OpenDocument Format.

Prerequisites

Step 1 — Install odf-kit

$ npm install odf-kit

odf-kit has a single runtime dependency (fflate for ZIP packaging) with zero transitive dependencies. Nothing else to install.

Step 2 — Create your first document

Create a file called generate.js and add the following:

import { OdtDocument } from "odf-kit";
import { writeFileSync } from "fs";

const doc = new OdtDocument();

doc.addHeading("My First ODF Document", 1);
doc.addParagraph("This file was generated by Node.js.");

const bytes = await doc.save();
writeFileSync("output.odt", bytes);
console.log("Created output.odt");

Run it with node generate.js. Open output.odt in LibreOffice Writer — you'll see a heading and a paragraph. That's the minimal case: three lines to create a valid .odt file.

Step 3 — Add a table

Tables accept a simple array of arrays. The first row typically contains headers:

doc.addTable([
  ["Product",  "Quantity",  "Price"],
  ["Widget",   "50",        "$4.99"],
  ["Gadget",   "25",        "$9.99"],
  ["Gizmo",    "100",       "$2.49"],
], { border: "0.5pt solid #000" });

For more control, use a builder callback to set column widths, cell backgrounds, and merged cells. See the full table documentation.

Step 4 — Format text

Plain strings create plain paragraphs. For formatting, use a builder callback:

doc.addParagraph((p) => {
  p.addText("Total revenue: ");
  p.addText("$1.2M", {
    bold: true,
    fontSize: 14,
    color: "#16a34a"
  });
});

Available formatting options include bold, italic, underline, strikethrough, superscript, subscript, font family, font size, text color, and highlight color. Both shorthand (bold: true) and explicit (fontWeight: "bold") forms work.

Step 5 — Add page layout, headers, and footers

doc.setMetadata({ title: "Quarterly Report" });
doc.setPageLayout({ orientation: "landscape" });
doc.setHeader("Acme Corp — Confidential");
doc.setFooter("Page ###");
// ### is replaced with the actual page number

Step 6 — Add images

import { readFileSync } from "fs";

const logo = readFileSync("logo.png");
doc.addImage(logo, "image/png", {
  width: "5cm",
  height: "2cm",
});

Supported formats: PNG, JPEG, GIF, SVG, WebP, BMP, and TIFF. Images are embedded in the .odt ZIP file — no external references.

Complete example

Here's a full script that combines everything above into a formatted report:

import { OdtDocument } from "odf-kit";
import { writeFileSync } from "fs";

const doc = new OdtDocument();
doc.setMetadata({ title: "Q4 Report" });
doc.setFooter("Page ###");

doc.addHeading("Q4 2025 Results", 1);
doc.addParagraph("Revenue exceeded expectations across all regions.");

doc.addTable([
  ["Region",  "Revenue",  "Growth"],
  ["North",   "$2.1M",    "+12%"],
  ["South",   "$1.8M",    "+8%"],
  ["East",    "$1.5M",    "+15%"],
  ["West",    "$2.3M",    "+10%"],
], { border: "0.5pt solid #000" });

doc.addHeading("Summary", 2);
doc.addParagraph((p) => {
  p.addText("Status: ");
  p.addText("Approved", { bold: true, color: "green" });
});

const bytes = await doc.save();
writeFileSync("report.odt", bytes);
console.log("Created report.odt");

What about templates?

If you already have a .odt file created in LibreOffice and want to fill it with data, odf-kit has a template engine too. See the Fill .odt Templates with JavaScript guide.

Browser usage

The same API works in the browser — no server required. Use any bundler (Vite, webpack, esbuild) and import as normal. The only difference is how you save the file:

import { OdtDocument } from "odf-kit";

const doc = new OdtDocument();
doc.addHeading("Generated in the Browser", 1);
doc.addParagraph("No server involved.");

const bytes = await doc.save();

// Trigger download
const blob = new Blob([bytes], {
  type: "application/vnd.oasis.opendocument.text",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "document.odt";
a.click();
URL.revokeObjectURL(url);

This is useful for privacy-sensitive applications, offline tools, and anywhere you want to generate documents without sending data to a server. odf-kit works in Chrome, Firefox, Safari, and Edge.

Why .odt instead of .docx?

.odt is the OpenDocument Format — an ISO international standard (ISO/IEC 26300) that's vendor-independent and open. It's the default format for LibreOffice, and it's required by many governments, the European Union, and public sector organizations. If your users work with LibreOffice, or your project needs to comply with open format mandates, .odt is the right choice. If you need .docx, the docx package is a good option.

Ready to generate .odt files?

$ npm install odf-kit