Node.js Office Library — Quick Start
The office-oxide npm package is the native Node.js binding: it dynamically loads liboffice_oxide via koffi and exposes a small, idiomatic JavaScript API. Faster and smaller than the WebAssembly build, but ships platform-specific prebuilds rather than a single portable binary.
Need a single artifact that runs in browsers, bundlers, or Node without native libraries? Use
office-oxide-wasminstead.
Install
npm install office-oxide
On postinstall, the package looks for a matching prebuilt native library under node_modules/office-oxide/prebuilds/<platform>-<arch>/. If it’s missing, configure one of these before first use:
OFFICE_OXIDE_LIB— absolute path to the library (e.g./opt/office_oxide/liboffice_oxide.so).- Prebuild directory — drop the library into
prebuilds/<platform>-<arch>/(e.g.linux-x64,darwin-arm64,win32-x64). - System library path — install so the library is discoverable via
LD_LIBRARY_PATH/DYLD_LIBRARY_PATH/PATH.
Runs on Node 18+.
Read a document
import { Document } from 'office-oxide';
const doc = Document.open('report.docx');
try {
console.log(doc.plainText());
} finally {
doc.close();
}
Or with TC39 explicit resource management (Node 22+):
import { Document } from 'office-oxide';
using doc = Document.open('report.docx');
console.log(doc.plainText());
// doc.close() runs automatically at scope exit
One-shot helper:
import { extractText } from 'office-oxide';
console.log(extractText('report.docx'));
Core API
import { Document } from 'office-oxide';
const doc = Document.open('file.xlsx');
try {
console.log(doc.format); // "xlsx"
console.log(doc.plainText());
console.log(doc.toMarkdown());
console.log(doc.toHtml());
const ir = doc.toIr(); // parsed object
doc.saveAs('file.docx'); // target inferred from extension
} finally {
doc.close();
}
Open from raw bytes (Uint8Array or Buffer):
import { readFileSync } from 'node:fs';
import { Document } from 'office-oxide';
const data = readFileSync('report.pptx');
using doc = Document.fromBytes(data, 'pptx');
console.log(doc.toMarkdown());
format must be 'docx' | 'xlsx' | 'pptx' | 'doc' | 'xls' | 'ppt'.
Module-level helpers:
import { extractText, toMarkdown, toHtml, detectFormat, version } from 'office-oxide';
extractText('file.docx'); // string
toMarkdown('file.pptx'); // string
toHtml('file.xlsx'); // string
detectFormat('mystery.bin'); // "docx" | ... | null
version(); // "0.1.0"
Editing
Editable handles preserve every unmodified OPC part on save. DOCX, XLSX, and PPTX only.
import { EditableDocument } from 'office-oxide';
using ed = EditableDocument.open('template.docx');
const n = ed.replaceText('{{name}}', 'Alice');
console.log(`${n} replacements`);
ed.save('out.docx');
Set XLSX cells
import { EditableDocument } from 'office-oxide';
using wb = EditableDocument.open('budget.xlsx');
wb.setCell(0, 'A1', 'Total'); // string
wb.setCell(0, 'B1', 42.5); // number
wb.setCell(0, 'C1', true); // boolean
wb.setCell(0, 'D1', null); // empty
wb.save('budget.xlsx');
sheetIndex is zero-based; cellRef uses standard spreadsheet notation.
Format-agnostic IR
using doc = Document.open('report.docx');
const ir = doc.toIr();
for (const section of ir.sections) {
console.log(section.title);
for (const el of section.elements) {
// el.kind: "Heading" | "Paragraph" | "Table" | "List" | ...
}
}
Bytes-based pipelines
import { Document } from 'office-oxide';
const res = await fetch('https://example.com/report.docx');
const data = new Uint8Array(await res.arrayBuffer());
using doc = Document.fromBytes(data, 'docx');
console.log(doc.toMarkdown());
Legacy formats
using doc = Document.open('old.xls');
doc.saveAs('modern.xlsx');
CommonJS
const { Document } = require('office-oxide');
const doc = Document.open('file.docx');
try { console.log(doc.plainText()); } finally { doc.close(); }
Errors
Failures throw OfficeOxideError carrying numeric code and operation fields:
import { Document, OfficeOxideError } from 'office-oxide';
try {
using doc = Document.open('missing.docx');
} catch (e) {
if (e instanceof OfficeOxideError) {
console.error(`code=${e.code} op=${e.operation}`);
} else {
throw e;
}
}
| Code | Meaning |
|---|---|
| 0 | ok |
| 1 | invalid argument |
| 2 | io error |
| 3 | parse error |
| 4 | extraction failed |
| 5 | internal error |
| 6 | unsupported format |
Troubleshooting
| Symptom | Fix |
|---|---|
office-oxide: failed to load native library |
Set OFFICE_OXIDE_LIB to the absolute path, or drop a matching prebuild into the package. |
koffi: ABI mismatch |
Platform/arch prebuild doesn’t match this Node process — reinstall or fetch a fresh prebuild. |
TypeError: data must be a Uint8Array or Buffer |
Document.fromBytes only accepts binary types. Use Buffer.from(base64, 'base64'). |
Document is closed |
A method ran after close() or after leaving a using scope. Open a new handle. |
Legacy .doc opens but renders gibberish |
Encrypted Word 97 documents are not decrypted — decrypt first via LibreOffice. |
See also
- WASM build — single portable binary, runs in browsers and bundlers
- Performance benchmarks
- Package on npm