Overview

Office.js-compatible editing API: an object model that batches its work, running on a server over bytes or in a page against an editor already open.

@docx-editor.dev/editor-api is an Office.js-compatible editing API for DOCX. It implements the same object model (context.document.body.paragraphs, load() then sync(), search(), getFirstOrNullObject()) so code written for a Word add-in compiles and runs here.

You describe work against objects (paragraphs, ranges, comments, revisions, content controls) and one sync() sends it as a single ordered batch that either applies whole or not at all. The same code runs in two places: on a server over DOCX bytes, or in a page against a document a reader already has open.

npm install @docx-editor.dev/editor-api

On a server

Runs headless: it takes bytes and returns bytes.

import { readFile, writeFile } from 'node:fs/promises';
import { DocxEditor } from '@docx-editor.dev/editor-api';

const runtime = await DocxEditor.createServer(await readFile('contract.docx'), {
  author: 'Review bot',
});
try {
  await runtime.run(async (context) => {
    const matches = context.document.body.search('$50k');
    matches.load();
    await context.sync(); // one round trip: now you know what was found

    for (const match of matches.items) match.insertText('$500k', 'Replace');
    await context.sync(); // one atomic batch: all of the writes, or none
  });
  await writeFile('contract.filled.docx', await runtime.save());
} finally {
  runtime.dispose();
}

In the browser

The browser entry takes an editor the host already created (from @docx-editor.dev/react or a plain page) and drives it in place. Edits land in the open document with the reader's undo stack intact, so there is no save(): the host saves the way it already did.

import { DocxEditor } from '@docx-editor.dev/editor-api/browser';

const runtime = DocxEditor.createBrowser(editor);
await runtime.run(async (context) => {
  const heading = context.document.body.paragraphs.getFirstOrNullObject();
  heading.load('text');
  await context.sync();

  if (!heading.isNullObject) heading.font.bold = true;
  await context.sync();
});

The /browser entry includes integration with the painted engine. Import the root entry on servers to keep that browser code out of the bundle.

Programming model

  • Reading a property you did not load() throws. This catches typos before they affect later writes.
  • sync() is the only round trip. Everything queued between two syncs is one ordered transaction. If any operation in it is refused, none of them happened.
  • Objects are proxies into a document the runtime owns. They survive sync() calls within one run. To carry one into a later run, track it and pass it to runtime.run(object, callback) for adoption. No proxy survives dispose().
  • getFirstOrNullObject and getLastOrNullObject answer an object whose isNullObject is true after the sync, which is the difference between "no such heading" and a crash.

Capabilities

runtime.capabilities reports host differences. save is false in the browser; selection, scrolling and layout are false on a server. Branch on these values because they remain frozen for the life of the runtime.

Entries

EntryUse when
@docx-editor.dev/editor-apiServers, workers, build scripts: bytes in, bytes out
@docx-editor.dev/editor-api/browserA page, driving an editor the host already created

Both export the same vocabulary (the lifecycle types, the object model, the error type) so consumer code compiles against either. They differ by one member: createBrowser.

Building an AI feature on this

This package ships no model integration, tool catalog or chat UI. The application that owns the model defines which operations to expose, how to describe them and how to handle refusals. A tool such as add_comment performs its document work inside a run block. Keep its chat UI with the application's other chrome.

License

This package is not Apache 2.0 like the editor packages. It is licensed under the EigenPal Pro Evaluation License 1.0, which lets you read, run and modify it internally, free of charge, to evaluate it. Production use (a live or customer-facing environment, live or business-operational data, or this package inside something you offer to others) requires a written commercial agreement, and so does redistribution. Commercial licensing: licensing@eigenpal.com.

Next steps

On this page