> Markdown version of [/magazine/752-generating-odf-files-in-javascript-without-libreoffice](https://www.wearedevelopers.com/magazine/752-generating-odf-files-in-javascript-without-libreoffice). Every page supports `.md` or `Accept: text/markdown`. Links point to the HTML versions so they work for humans too. Agent guide: [/agents.md](https://www.wearedevelopers.com/agents.md). --- # Generating ODF Files in JavaScript — Without LibreOffice **By:** Scott Wirth, [Chris Heilmann](https://www.wearedevelopers.com/@chris-heilmann) **Published:** August 14, 2026 European governments are mandating OpenDocument Format (ODF) as the standard for public sector documents. Germany’s recent Deutschland-Stack decision and the EU’s push for digital sovereignty mean JavaScript developers increasingly need to produce .odt and .ods files — but the go-to solution (spinning up LibreOffice headless) is heavy, slow, error-prone, and completely unsuitable for serverless or browser environments. You need to generate a `.odt` document from your Node.js app. Maybe it’s a report, a contract, an invoice. You search npm. You find docx libraries — wrong format. You find LibreOffice headless wrappers. You spin one up. It takes four seconds per document, requires a full LibreOffice installation on your server, crashes under load, and is completely useless in a browser or serverless function. You consider hand-rolling the XML yourself. You look at the ODF spec. You close the tab. This is the reality for most JavaScript developers who need to generate OpenDocument Format files. It shouldn’t be this hard. ## Why ODF and why now OpenDocument Format (ODF) is the ISO standard for office documents — the native format of LibreOffice, and the mandated format for public sector software in an increasing number of European countries. Germany’s recent Deutschland-Stack decision requires open standards across public administration. The EU’s Interoperable Europe Act pushes member states toward ODF for document exchange. If you’re building software for European governments, municipalities, or any organisation that cares about digital sovereignty, you will eventually need to produce `.odt` or `.ods` files. ## Why browser-side generation matters for security There’s a case for generating documents server-side — but there’s a stronger case for not doing it at all. Every time a user’s data travels to your server to be assembled into a document, you’re creating a privacy surface. For healthcare apps, legal tools, HR software, or anything touching GDPR-sensitive data, browser-side document generation is the more defensible architecture: the document is assembled locally, on the user’s machine, and never leaves it. No data in transit. No server logs. No breach surface. This is only possible if your document library runs in the browser — and most don’t. ## Why ODF is harder than it looks An `.odt` file is a ZIP archive containing several XML files: `content.xml` for the document body, `styles.xml` for page layout and named styles, `settings.xml` for view settings, `meta.xml` for metadata, and `META-INF/manifest.xml` declaring every file in the archive. Each XML file uses a precise set of namespaces. Miss one and LibreOffice silently ignores the entire section. Get the whitespace wrong in `settings.xml` and freeze panes stop working. Use the wrong anchor type on an image and it renders in the wrong position. “Opens in LibreOffice” is not the same as “valid ODF” — the OASIS ODF validator will find issues that LibreOffice happily ignores, and other applications won’t be so forgiving. Most JavaScript libraries that claim ODF support produce files that pass the smell test but fail on edge cases. The only way to know is to run every generated file through the official OASIS validator — which is what odf-kit does on every CI build. ## odf-kit in practice [odf-kit](https://github.com/GitHubNewbie0/odf-kit) is an Apache 2.0 TypeScript/JavaScript library for generating, filling, reading, and converting ODF files. It has one runtime dependency (fflate, for ZIP), runs in Node.js and browsers, and every output file is validated against the OASIS ODF spec in CI. **Generating a report:** import { OdtDocument } from "odf-kit"; const doc = new OdtDocument(); doc.addHeading("Q4 Results", 1); doc.addParagraph("Revenue exceeded expectations across all regions."); doc.addTable( [ ["Region", "Revenue", "Growth"], ["North", "$2.1M", "+12%"], ["South", "$1.8M", "+8%"], ], { border: "0.5pt solid #000" } ); const bytes = await doc.save(); // bytes is a Uint8Array — write to disk in Node.js or trigger a download in the browser **Converting HTML to ODT** — useful if your content already lives as HTML (rich text editors, CMS output, email bodies): import { htmlToOdt } from "odf-kit"; const bytes = await htmlToOdt(html, { pageFormat: "A4" }); **Filling a template** — design the document in LibreOffice, add `{{placeholders}}`, fill programmatically: import { fillTemplate } from "odf-kit"; const bytes = fillTemplate(templateBytes, { name: "Alice", date: "2026-04-22", amount: "€1,250.00", }); The same code works in a browser — swap `writeFileSync` for a blob download and you have client-side document generation with no server involved. ## The warts It’s not all smooth. A few things I learned the hard way: - **LibreOffice requires `xmlns:ooo` on `settings.xml`** — without it, view settings (zoom level, freeze panes, active sheet) are silently ignored. No error. Just ignored. - **Whitespace in settings XML matters** — text nodes between config elements cause LibreOffice to discard the entire settings block. You need to compact the XML before writing it. - **Image anchoring is fiddly** — inline images need `anchor: "as-character"`, paragraph-anchored images behave differently across applications. Test in both LibreOffice and OnlyOffice. - **The ODF validator is your friend** — run it in CI. It catches issues that LibreOffice ignores but other applications don’t. ## Where this fits odf-kit is particularly useful for Nextcloud app development (where ODT export is a long-standing open request), for web-based editors using Lexical, TipTap or ProseMirror, and for any European public sector software that needs to produce ODF output without a LibreOffice dependency. The broader point is simpler: open document standards deserve open source tooling that actually works. The JavaScript ecosystem has had excellent libraries for PDF, DOCX, and XLSX for years. ODF is catching up. ## About the Author Scott Wirth is a freelance developer and maintainer of [odf-kit](https://githubnewbie0.github.io/odf-kit/) ## Related Articles - [Dev Digest 131 - AI'm not sure about OSS](https://www.wearedevelopers.com/magazine/472-dev-digest-131-ai-m-not-sure-about-oss) - [Dev Digest 136 - No JS(on) of mine](https://www.wearedevelopers.com/magazine/482-dev-digest-136-no-js-on-of-mine) - [Dev Digest 101 - the XXX edition](https://www.wearedevelopers.com/magazine/382-dev-digest-101-the-xxx-edition) - [Dev Digest 128 - Do not Google Monopoly](https://www.wearedevelopers.com/magazine/465-dev-digest-128-do-not-google-monopoly) ## Related Videos - [Why Is Software Documentation Still Static – And Why Modern Browsers Deserve Better](https://www.wearedevelopers.com/videos/2054-why-is-software-documentation-still-static-and-why-modern-browsers-deserve-better) - [WeAreDevelopers LIVE - CSS or BS?](https://www.wearedevelopers.com/videos/1850-wearedevelopers-live-css-or-bs) - [New Browser APIs, End of React Create App, Smuggling Data in Emoji and more with Rowdy Rabouw](https://www.wearedevelopers.com/videos/1299-new-browser-apis-end-of-react-create-app-smuggling-data-in-emoji-and-more-with-rowdy-rabouw) - [NoLoJS - Avoiding JavaScript Cruft with HTML and CSS - Aaron T. Grogg](https://www.wearedevelopers.com/videos/1806-nolojs-avoiding-javascript-cruft-with-html-and-css-aaron-t-grogg) ## Related Jobs - [Senior Open Source Advisor](https://www.wearedevelopers.com/jobs/ext/1278113-senior-open-source-advisor) at **ZEISS Group** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/639235-remote-senior-full-stack-engineer) at **Edge Impulse** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/645320-remote-senior-full-stack-engineer) at **Edge Impulse** - [AI Software Engineer (Germany)](https://www.wearedevelopers.com/jobs/48317-ai-software-engineer-germany) at **Sunhat** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/646086-remote-senior-full-stack-engineer) at **Edge Impulse** - [Remote Senior Full-Stack Engineer](https://www.wearedevelopers.com/jobs/ext/682327-remote-senior-full-stack-engineer) at **Edge Impulse**