Developer guide · Document extraction

Extract text and tables from DOCX in JavaScript and TypeScript

Turn Word content into plain text and JSON for search, application records or AI input. Use the same typed query API in the browser and Node.js, without rendering pages or starting a headless browser.

Get the extraction sample on GitHub · Guide source · Download the Word sample

Try DOCX-to-text and DOCX-to-JSON extraction

View the included project brief beside its extracted content. Choose body text, table rows or combined JSON, then copy or download the output. The Advanced section demonstrates a delivery-record query for the sample’s particular table headers. It returns an empty array when no table matches.

Open the extraction demo in a full tab. The demo processes selected files locally without uploading them or calling an AI service.

Install the SDK

npm install @onodocs/sdk

Use the analysis entry point @onodocs/sdk to read document content without rendering pages. TypeScript declarations are included. The complete extraction sample contains browser and Node.js applications in JavaScript and TypeScript, with setup instructions. Evaluation needs no license key. SDK on npm · Release archives.

Read plain text and typed paragraphs

Open a browser File, an ArrayBuffer or a Uint8Array. Query the document’s text and structure without a canvas, fonts or page layout.

import { openDocument } from "@onodocs/sdk";

const doc = await openDocument(file);
const body = doc.query.stories().where({ story: "body" });
const text = body.map(story => story.text).join("\n");
const paragraphs = body.paragraphs().map(paragraph => ({
  text: paragraph.text,
  style: paragraph.style
}));

file is the selected browser File. The complete browser sample supplies the file picker. Paragraph text joins its runs, so formatting changes do not split a sentence into separate records. Paragraph queries include paragraphs inside tables and content controls.

Body text includes table-cell text, so appending the tables would duplicate it. Plain text preserves imported text and paragraph boundaries, not the page layout.

Extract tables as arrays of rows and cells

const tables = body.tables().map(table => table.textRows);

The result is an array of tables containing rows of cell strings. textRows includes the first row. It does not guess whether that row contains headers or convert dates, numbers or currencies. Convert those values in your application if needed.

For structural access, use table.rows, row.cells and each cell's children. A merged or nested table is not necessarily a rectangular spreadsheet. textRows lists the imported cells without repeating values across merged spans. Nested table text can occur inside an outer cell, while tables() also returns nested tables. Choose the level your application needs before flattening.

Find a table by its headers and map rows to JSON

The sample brief uses the first-row labels Deliverable, Owner and Due. Match those labels in their exact order, then remove the header row and map the remaining cells:

const matches = body.tables().where({
  headers: ["Deliverable", "Owner", "Due"]
});
const deliveryTables = matches.map(table =>
  table.textRows.slice(1).map(([deliverable = "", owner = "", due = ""]) => ({
    deliverable, owner, due
  }))
);

Header matching compares the complete first row, including case and whitespace. It does not search later rows or infer synonyms.

The output contains one entry per matching table, or [] if none match. Use one() only when exactly one match is required; it throws for zero or multiple results. optional() allows zero but still throws for multiple matches.

This record mapping is for the simple three-column sample. Missing cells become empty strings. Additional cells remain available in the raw table output. Validate required values, merged cells and date formats in your application before importing records. Due dates remain as written, such as “Nov 04”. The sample does not infer a year.

Share extraction logic between browser and Node.js

The same function selects the text, paragraphs and tables for JSON output in both environments:

import type { InspectedDocument } from "@onodocs/sdk";

export function extractContent(doc: InspectedDocument) {
  const body = doc.query.stories().where({ story: "body" });
  const tables = body.tables();
  return {
    text: body.map(story => story.text).join("\n"),
    paragraphs: body.paragraphs().map(paragraph => ({ text: paragraph.text, style: paragraph.style })),
    tables: tables.map(table => table.textRows),
    deliveryTables: tables.where({ headers: ["Deliverable", "Owner", "Due"] }).map(table =>
      table.textRows.slice(1).map(([deliverable = "", owner = "", due = ""]) => ({ deliverable, owner, due }))
    )
  };
}

The complete sample includes a source preview, file picker, replacement, cancellation, copy and download controls, and JavaScript equivalents. The preview uses browser rendering and Canvas; the shared extraction function runs independently of it. Call doc.dispose() after extracting plain values, and use AbortSignal to cancel pending work.

Extract content in Node.js

Save this as extract.mjs beside the shared JavaScript extraction function, then run node extract.mjs document.docx:

import { readFile } from "node:fs/promises";
import { openDocument } from "@onodocs/sdk";
import { extractContent } from "./extract.js";

const doc = await openDocument(await readFile(process.argv[2]));
try { console.log(JSON.stringify(extractContent(doc), null, 2)); }
finally { doc.dispose(); }

No browser or page rendering is required. The complete Node.js command includes an authored document and accepts a path to your own file. If you add an upload endpoint, authentication, storage and deletion belong to your application.

Scope stories and search paragraph text

Body, headers, footers, notes and textboxes are separate stories. Query them deliberately when defining the contents of an export. A header story contains the header once, even when it repeats on several pages.

const headers = doc.query.stories().where({ story: "header" })
  .paragraphs().map(paragraph => paragraph.text);

const matches = body.findText(/launch/gi).map(range => ({
  text: range.text,
  paragraph: range.paragraph.text,
  start: range.start,
  end: range.end
}));

Search returns paragraph-local ranges and can span formatting runs within one paragraph, but not paragraph boundaries. Offsets follow JavaScript UTF-16 string indexing and the end offset is exclusive. See the query API, element types, and text ranges.

Extraction limits, privacy and licensing

Extraction reads supported Word text and structure. It does not recognize text inside images or preserve the full OOXML. Floating objects and page columns can appear in a different order from the rendered page. Extraction does not preserve styling or exact pagination, and it does not recalculate every Word field.

Supported packages include DOCX, DOCM, DOTX and DOTM. Macros are not executed. Password-protected files and older DOC, DOT or RTF files must be unlocked or converted first. For supported editing and DOCX saving, use the Editor workflow.

The browser sample does not send document contents, filenames, extracted values or analytics. The initial load downloads application assets. Extraction, copying and downloading then run locally. Nothing is sent to an AI model. If your application later sends extracted content elsewhere, that is a separate data-handling decision.

Evaluation is registration-free and exposes the complete query API. The rendering watermark does not get inserted into extracted text. Production use requires a commercial application license. Signed keys are verified offline. Sample code, guides and authored documents are MIT-licensed, while SDK and Canvas use a separate commercial application license. Editor code is also MIT-licensed. Extracted data is royalty-free to distribute. Recipients need no license unless their software runs OnoDocs outside the covered application scope.

Build on the extracted content

The browser demo includes a visual preview. For its rendering APIs, see the JavaScript and TypeScript viewer guide or the React viewer guide. Explore Queries and text output for ASCII output, a document tree and a locally simulated email draft. All guides and runnable samples are on GitHub.

Markdown and source references

Use extractDocument for structured JSON, Markdown and references that resolve to the current document. The source-linked extraction guide and report demo show evidence highlighting and reviewed edits.