npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@docx4j/generated-objects-ts

v0.2.0

Published

Office Open XML (WordprocessingML, DrawingML, PresentationML, SpreadsheetML, VML, ...) as typed JavaScript: Jsonix mappings and TypeScript declarations generated from docx4j's schemas, with a docx4j-style facade.

Readme

@docx4j/generated-objects-ts

Office Open XML as typed JavaScript: Jsonix mappings and TypeScript declarations for the schema set that docx4j maintains (WordprocessingML, DrawingML, PresentationML, SpreadsheetML, VML, the Microsoft Office extensions, OPC relationships, document properties, and more), generated by jsonix-schema-compiler from docx4j's xsd/ROOT.xsd, with a small facade that gives them docx4j's names.

It is the counterpart of docx4j's docx4j-generated-objects module: the object model alone, usable on its own wherever the document arrives as XML (a Word add-in via Office JS, a custom XML part, a file you unzip yourself). The packaging and resolution layer that corresponds to docx4j-core (parts, relationships, styles, numbering) is the separate package @docx4j/core-ts (repository plutext/docx4j-core-ts), which depends on this one and re-exports its facade. "docx4j-ts" names the TypeScript line as a whole.

npm install @docx4j/generated-objects-ts

Why

  1. There is no Microsoft object model for OOXML in JavaScript. Office JS gives an add-in the document's XML only as strings: Body.getOoxml() and Range.getOoxml() return a flat OPC package, insertOoxml() takes one back. Everything in between, reading a paragraph's properties or building a table, is string and DOM work against a 6,000-page specification.
  2. Word on the web has WordApi 1.4 custom XML parts, with getXml(), setXml(), query(), insertElement() and updateElement(), but again only XML strings: there is no mapping between the XML and JavaScript objects.
  3. docx4j is that object model, in Java. Its generated classes, names and rules (org.docx4j.wml.P, getParent(), deepCopy, unwrap) are how a large body of code manipulates Office documents. This package is the same model for JavaScript and TypeScript, generated from the same schemas: org_docx4j_wml.P is docx4j's P, with the same properties, a TYPE_NAME discriminant, PARENT pointers, and the ContentAccessor / SdtElement interfaces as union types.
  4. Typed. The declarations say what may appear where: which elements a paragraph can hold, which attributes are required, which values an enumeration allows. The TypeScript compiler checks the tree you build before Word sees it.

The runtime is @docx4j/jsonix, a fork of the Jsonix XML-to-JSON library with parent pointers and deep copy; it works in browsers and in Node.

Examples

An add-in: from getOoxml() to typed objects and back

getOoxml() returns a flat OPC package (pkg:package), mapped here as org_docx4j_xmlPackage. Its parts are xsd:any processContents="skip", so plain unmarshalling leaves them as DOM; unmarshalPackage additionally unmarshals every part whose root element the model knows (w:document, w:styles, relationships, ...) into typed objects, and marshalPackage reverses that for insertOoxml():

import { unmarshalPackage, marshalPackage, unwrap } from '@docx4j/generated-objects-ts';
import type { DocumentElement, P, R, Text } from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';

async function shout(body: Word.Body): Promise<void> {
  const ooxml = body.getOoxml();               // OfficeExtension.ClientResult<string>
  await body.context.sync();

  const pkg = await unmarshalPackage(ooxml.value);
  const part = unwrap(pkg).part?.find((p) => p.name === '/word/document.xml');
  const document = part?.xmlData?.any as DocumentElement | undefined;   // typed: w:document is known to the model
  if (!document) return;

  for (const entry of unwrap(document).body?.content ?? []) {
    if (entry.value.TYPE_NAME !== 'org_docx4j_wml.P') continue;
    const paragraph: P = entry.value;
    for (const run of paragraph.content ?? []) {
      if (run.value.TYPE_NAME !== 'org_docx4j_wml.R') continue;
      for (const item of (run.value as R).content ?? []) {
        if (item.value.TYPE_NAME === 'org_docx4j_wml.Text') {
          const text = item.value as Text;
          text.value = text.value?.toUpperCase();
        }
      }
    }
  }

  body.insertOoxml(await marshalPackage(pkg), 'Replace');
  await body.context.sync();
}

Building content as typed literals

import type { P } from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';

const W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
const paragraph: P = {
  pPr: { pStyle: { val: 'Heading1' } },
  content: [{
    name: { namespaceURI: W, localPart: 'r' },
    value: { rPr: { b: {} }, content: [{ name: { namespaceURI: W, localPart: 't' }, value: { value: 'Hello' } }] },
  }],
};
// paragraph.content[0].value.rPr.bold   -> compile error: no such property
const xml = await marshalString({ name: { namespaceURI: W, localPart: 'p' }, value: paragraph });

Building content with the factories

Each module also ships docx4j's ObjectFactory (compiler CR-010), generated from the same model: factory/<module> has a creator per class (createP(init?), which sets TYPE_NAME) and a wrapper per element declaration with XJC's names (createRT(value) for w:t in a run, createPElement(value) for the global w:p), and el/<module> has one wrapper per element name of the module's namespace. Both are ES modules of named exports, so a bundle keeps only what a program uses.

import { createP, createR, createText, createRElement, createRT } from '@docx4j/generated-objects-ts/factory/org_docx4j_wml';
import * as el from '@docx4j/generated-objects-ts/el/org_docx4j_wml';

const paragraph = createP({
  pPr: { pStyle: { val: 'Heading1' } },
  content: [createRElement(createR({ rPr: { b: {} }, content: [createRT(createText({ value: 'Hello' }))] }))],
});
const same = el.p({ pPr: { pStyle: { val: 'Heading1' } }, content: [el.r({ rPr: { b: {} }, content: [el.t({ value: 'Hello' })] })] });
// createRT(createP())          -> compile error: a run holds Text, not P

A wrapper sets TYPE_NAME on a literal value when the declaration determines the type; el.sdt and el.customXml, one element name with four types by scope, leave it to the caller (the scoped createSdtPrAlias-style wrappers are the precise form). A creator's init is partial, so a literal (above) remains the stricter form: it is what checks required properties at compile time.

Fragments, text sugar and traversal (builders/wml)

builders/wml is the counterpart of docx4j-core's XmlUtils.unmarshalString, TextUtils and TraversalUtil (CR-002): things that need only the object model.

import { wml, p, r, tbl, textOf, find } from '@docx4j/generated-objects-ts/builders/wml';

const [heading, table] = await wml`
  <w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr>${r('Report', { bold: true })}</w:p>
  ${tbl([['Item', 'Qty'], ['Widget', '3']], { style: 'TableGrid' })}`;
const para = p('Hello', { style: 'Heading1', italic: true, highlightColor: '#FFFF00' });
textOf(heading);                          // 'Report'
find(table, 'org_docx4j_wml.Tc').length;  // 4

wml parses sibling elements as written inside document.xml, with docx4j's namespace declarations added, and returns them typed: the fragment is wrapped in the container docx4j would put it in (w:body, w:p, w:tbl, ...; a content control by its content), since the runtime unmarshals global elements only, so w:r, w:tr or w:t fragments work too. In the tagged form a typed element marshals in place, a string is escaped as text and wml.raw(xml) is inserted verbatim; the plain form wml(xml, { wrapper, preprocess }) takes options. p, r, t, br, tab and tbl build content over el; run options use the names of Office JS Word.Font (bold, name, size, highlightColor, ...) through applyRunOptions / readRunOptions, the one mapping to w:rPr that @docx4j/core-ts's Font view shares. textOf reads the text back; runItemsOf gives a holder's run-level list under whichever property name the model uses (w:ins, w:del, w:moveFrom, w:moveTo, a run-level content control); walk, find and linkParents are TraversalUtil, ClassFinder and what the unmarshaller does for PARENT.

Navigating with TYPE_NAME and PARENT

import { unmarshalString, unwrap, deepCopy } from '@docx4j/generated-objects-ts';
import type { DocumentElement, Tc } from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';

const doc = unwrap(await unmarshalString<DocumentElement>(documentXml));
const firstParagraph = doc.body?.content?.find((e) => e.value.TYPE_NAME === 'org_docx4j_wml.P')?.value;
const container = firstParagraph?.PARENT;   // Body | Tc | Hdr | Ftr | ... as docx4j's getParent()
if (container?.TYPE_NAME === 'org_docx4j_wml.Tc') {
  const cell: Tc = container;                // narrowed
}
const copy = deepCopy(firstParagraph);       // children re-linked to the copy; copy.PARENT unset, as docx4j's copy()

Custom XML parts

Custom XML parts hold your own schemas. The compiler that produced this package works on any XML Schema: run jsonix-schema-compiler -generateTypeScript over yours, put its mapping in a context next to these, and getXml() / setXml() become typed round trips too. For OOXML content in a part (a w:sdt, document properties), the modules here already apply:

async function readPart(part: Word.CustomXmlPart): Promise<void> {
  const xml = part.getXml();
  await part.context.sync();
  const element = await unmarshalString(xml.value);   // TypedNamedValue<unknown>; narrow by TYPE_NAME or a type argument
  part.setXml(await marshalString(element));
}

Node

import JSZip from 'jszip';
import { readFile } from 'node:fs/promises';
import { unmarshalString, unwrap } from '@docx4j/generated-objects-ts';
import type { DocumentElement } from '@docx4j/generated-objects-ts/modules/org_docx4j_wml';

const zip = await JSZip.loadAsync(await readFile('report.docx'));
const doc = unwrap(await unmarshalString<DocumentElement>(await zip.file('word/document.xml')!.async('string')));
console.log(doc.body?.content?.length, 'block-level items');

The Office JS signatures above (Body.getOoxml, insertOoxml, CustomXmlPart.getXml/setXml) are the documented ones; the snippets are compile-checked against minimal stubs in test/readme-examples.ts.

What is here

  • The facade (src/index.mts, built to dist/): getContext() builds one Jsonix.Context over all 103 modules, lazily on first use, with parentPointers: true; unmarshalString, marshalString, unmarshalNode, marshalNode, unwrap and deepCopy are docx4j's XmlUtils names over it, with deepCopyAs for a copy typed as a base type (w:pPrChange holds a PPrBase, not a PPr) and deepCopyAsSync / getContextSync where a caller cannot await; unmarshalPackage / marshalPackage handle flat OPC packages with typed parts; Jsonix is re-exported. The facade is asynchronous because the mappings load as ES modules; for a synchronous setup import the modules you need from @docx4j/generated-objects-ts/modules/<module> and build your own context.
  • Bundling: no bundler configuration is needed. The mappings are imported by literal specifiers from a registry (MODULES), so a bundler sees exactly the 103 modules a context loads, rather than the template specifier earlier versions used - which Vite could not resolve without dynamicImportVarsOptions and esbuild expanded to a glob over every .mjs in modules/, including the .el and .factory siblings (a 3.5MB esbuild bundle against 2.0MB now). One obstacle remains and is the runtime's: bundled for Node, @docx4j/jsonix loses the @xmldom/xmldom it injects through its UMD wrapper and reaches for browser globals, so a Node bundle needs globalThis.DOMParser and globalThis.XMLSerializer set; a browser has them.
  • A smaller context: getContext({ modules: modulesFor('org_docx4j_wml') }) builds a context over one root's modules and their closure - twelve modules for WordprocessingML, which reads a Word 365 document.xml - instead of all 103. modulesFor follows the type references in the mappings themselves, since a hand-picked list almost always misses one and fails with "Type info [...] is not known in this context". Options are read when the context is built, so call resetContext() first if one already exists; and a smaller context knows less, so a document carrying content its modules do not cover will throw where the full context would type it.
  • Namespace prefixes: the context uses docx4j's prefix table, exported as NAMESPACE_PREFIXES (namespace URI to prefix: w, w14, mc, r, a, pkg, ...), so marshalled XML reads as Word writes it. The facade's marshal functions declare on the root element the namespaces the tree uses plus every prefix the root's mc:Ignorable names, and nothing else; a Relationships root uses the default namespace, as docx4j's relationships part mapper. To add or change prefixes, pass a table before the context is first used: getContext({ namespacePrefixes: { ...NAMESPACE_PREFIXES, 'urn:my-ns': 'my' } }). A table passed to getContext replaces the default (spread it to extend it). One namespace is written as the default rather than with a prefix (SpreadsheetML main, as docx4j), so a prefix mc:Ignorable names for it cannot come from the table; IGNORABLE_PREFIX_ALIASES supplies it, and the prefix is declared beside the default declaration (Excel writes mc:Ignorable="x xr10" on slicer, slicer cache and timeline parts). A prefix neither table resolves is dropped from mc:Ignorable, with a warning, since Office repairs a file naming an undeclared prefix.
  • 103 modules under modules/, one per JAXB package reachable from ROOT.xsd, named after the package with dots as underscores (docx4j's org.docx4j.wml is org_docx4j_wml). Each has <module>.js (UMD), <module>.mjs (ES module), <module>.d.ts (declarations) and <module>.d.mts (typed re-export for the .mjs). Modules reference each other by name, so a context needs all of them.
  • @docx4j/generated-objects-ts/helpers/wml (src/helpers/wml.mts): docx4j's highlight colour table, isQFormat (via PARENT) and isCustomStyle.
  • @docx4j/generated-objects-ts/builders/wml (src/builders/wml.mts): wml fragments, p / r / t / tbl, the run mapping, tr / tc, inlinePicture, sdt / sdtPr / sdtProperty / sdtKindOf for content controls, rPrToElements / rPrFromElements, walkAll, mcBranchOf, textOf, runItemsOf, walk / find / linkParents (CR-002, CR-003).
  • modules/bindings.xjb: the Jsonix customizations the files were generated with (kept for reference; the source of truth is the compiler repository's OfficeOpenXML/bindings.xjb).

Mapping names, module names and TYPE_NAME discriminants all use the docx4j package names, so org_docx4j_wml.P is the type that docx4j calls org.docx4j.wml.P.

The declarations

  • Required properties are non-optional; collections are arrays; choices are unions; elementRef properties (Body.content, P.content, R.content, ...) are unions of TypedNamedValue<T> for every element the schema allows there, including substitution groups, plus string where content is mixed.
  • TYPE_NAME is a literal union over the type and its subtypes and is set by Jsonix on unmarshal.
  • readonly PARENT? lists the types that can contain each type, as docx4j's getParent() would return; the runtime fills it in (parentPointers, on by default in the facade) and deepCopy re-links it.
  • docx4j's hand-written interfaces (ContentAccessor, SdtElement, SdtContent, CTCustomXmlElement, the VML attribute interfaces) are union type aliases.
  • Dates are Jsonix calendars (XmlCalendar), not JavaScript Dates; xs:double values are number (including NaN defaults in DrawingML diagrams).
  • Attributes are emitted sorted by name, except v:line (id style from to, which Word requires); elements keep schema order. An absent w:customStyle means a built-in style (helpers/wml: isCustomStyle).

Regeneration

See generate.md. The files are regenerated from the compiler repository, which owns the bindings and the generation script; commits here cite the compiler and docx4j commits used.

Licence

Apache-2.0, as docx4j. The schemas are ECMA-376 / Microsoft Open Specifications material as redistributed by docx4j.