Guide · odf-kit/docx

Convert DOCX to ODT in JavaScript

Convert Microsoft Word .docx files to OpenDocument .odt format in Node.js and browsers — pure ESM, zero new dependencies, no LibreOffice required. Spec-validated against ECMA-376 5th edition.

Install

docxToOdt() is built into odf-kit — no separate package needed:

$ npm install odf-kit

Basic usage

Import docxToOdt from odf-kit/docx and pass raw bytes. The function returns { bytes, warnings }bytes is the complete .odt file, warnings reports any content that could not be fully converted.

import { docxToOdt } from "odf-kit/docx";
import { readFileSync, writeFileSync } from "fs";

const { bytes, warnings } = await docxToOdt(readFileSync("report.docx"));
writeFileSync("report.odt", bytes);

if (warnings.length > 0) {
  console.warn("Conversion warnings:", warnings);
}
// → Valid .odt file, opens in LibreOffice, Google Docs, Word

Browser usage

odf-kit/docx is pure ESM with zero new dependencies — it works in any modern browser. The conversion runs entirely client-side; the file never leaves the user's device.

import { docxToOdt } from "https://esm.sh/odf-kit@0.10.0/docx";

// From a file input
const file = event.target.files[0];
const { bytes, warnings } = await docxToOdt(await file.arrayBuffer());

// 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 = file.name.replace(/\.docx$/i, ".odt");
a.click();
URL.revokeObjectURL(url);

Options

Pass a DocxToOdtOptions object as the second argument to control the output:

const { bytes } = await docxToOdt(readFileSync("report.docx"), {
  // Override or fallback page format (default: read from DOCX)
  pageFormat: "letter",        // "A4" | "letter" | "legal" | "A3" | "A5"

  // Override page orientation (default: read from DOCX)
  orientation: "portrait",     // "portrait" | "landscape"

  // Read page size and margins from DOCX (default: true)
  preservePageLayout: true,

  // Map custom Word style names to ODT heading levels
  styleMap: {
    "Section Title": 1,
    "Sub Title": 2,
    "Article Heading": 3,
  },

  // Override metadata (default: read from docProps/core.xml)
  metadata: {
    title: "My Report",
    creator: "Alice",
    description: "Q4 results",
  },
});

What is preserved

ContentPreserved
Paragraphs and headings Style name and outlineLvl detection
Bold, italic, underline, strikethrough
Superscript / subscript
Small caps, all caps
Font size Actual point values from w:sz
Text color Hex values from w:color
Highlight color
Font family
Text alignment left, center, right, justify
Paragraph spacing (before/after)
Line height
Indentation (left, right, first-line, hanging)
Bullet lists
Numbered lists (decimal, roman, alpha)
Nested lists
Tables Column widths, cell backgrounds, vertical alignment
Merged cells (colspan and rowspan)
Hyperlinks (external and internal anchors)
Bookmarks Two-pass cross-paragraph resolution
Images Actual EMU dimensions, not defaulted to 10cm
Page layout (size, margins, orientation) From w:sectPr
Headers and footers Default type
Footnotes and endnotes Appended as end section
Page breaks Including mid-paragraph breaks
Tabs
Tracked changes Final text — insertions included, deletions skipped
Document metadata From docProps/core.xml
Style inheritance (w:basedOn) Full chain resolution
Complex fields (HYPERLINK) w:fldChar and w:fldSimple both handled
Legacy VML images (w:pict)
Structured document tags (w:sdt) Unwrapped; checkboxes as ☐/☑
Text boxes, charts, SmartArt Warned and skipped
Math (OMML) Out of scope
Macros, OLE objects Out of scope

Why not use mammoth.js?

mammoth.js is a well-known DOCX reader, but it has important limitations for a DOCX→ODT use case:

odf-kit/docx is a native DOCX→ODT converter — pure ESM, browser-safe, validated against the ECMA-376 5th edition spec.

Why not shell out to LibreOffice?

LibreOffice headless (libreoffice --headless --convert-to odt) produces high-fidelity output but comes with real costs:

odf-kit/docx parses DOCX files directly in JavaScript. No subprocess, no installed software, no startup penalty. Works anywhere JavaScript runs — including browsers.

Comparison

OptionESM / BrowserFont sizes & colorsPage layoutImages (actual size)
odf-kit/docx
mammoth.js CommonJS Intentional 10cm default
LibreOffice headless Subprocess
officeParser CommonJSPartial

Handling warnings

The warnings array in the result reports content that could not be fully converted — unrecognised field instructions, missing image data, or mid-document section breaks. These are informational and do not cause the conversion to fail. Log them during development to understand what a specific document contains that is out of scope.

const { bytes, warnings } = await docxToOdt(input);

for (const w of warnings) {
  console.warn("[odf-kit/docx]", w);
}

// Example warnings:
// "Image rId5 references media/image1.wmf which is not in the ZIP"
// "Unrecognized field instruction: \" MERGEFIELD CustomerName\""
// "w:altChunk (imported external content) is not supported and was skipped"

Custom style mapping

Word documents sometimes use custom style names for headings — for example "Section Title" instead of "Heading 1". Use styleMap to tell odf-kit how to map them to ODT heading levels:

const { bytes } = await docxToOdt(input, {
  styleMap: {
    "Section Title": 1,   // maps to text:h level="1"
    "Sub Title":     2,   // maps to text:h level="2"
    "Clause":        3,
  },
});

// Built-in heading detection (always applied):
// "heading 1"–"heading 6" → levels 1–6 (Word canonical names)
// "title" → level 1, "subtitle" → level 2
// w:outlineLvl in pPr → fallback for custom styles
// styleMap overrides all of the above

Related guides and tools

Convert your first .docx file

One package, one install — DOCX to ODT in Node.js and browsers.

$ npm install odf-kit