(null);
const { tools } = useDocxAgentTools({ editorRef });
const { messages, sendMessage } = useChat({
api: "/api/agent-chat",
body: { tools },
});
return (
{/* render messages, sendMessage button, etc. */}
);
}
```
`useDocxAgentTools` returns tool definitions the AI SDK consumes; the call lifecycle is forwarded transparently. Server side, wrap the client's tool descriptors with `getAiSdkTools()`:
```ts
// app/api/agent-chat/route.ts
import { streamText } from "ai";
import { getAiSdkTools } from "@eigenpal/docx-editor-agents/ai-sdk/server";
export async function POST(req: Request) {
const { messages, tools: clientTools } = await req.json();
const tools = getAiSdkTools(clientTools);
return streamText({ model: "openai/gpt-4o", messages, tools }).toUIMessageStreamResponse();
}
```
## Tool call lifecycle
1. Model decides to call a tool (e.g. `add_comment`).
2. AI SDK forwards the call to the client.
3. `useDocxAgentTools` executes against the live editor via `editorRef`.
4. The editor renders the change (comment marker, tracked-change underline).
5. Tool result returns to the model on the next turn.
`paraId` is the trick that makes multi-turn work cleanly. The agent can `find_text` in turn 1 and `add_comment` on the same paragraph in turn 5 without re-resolving, because `paraId` is Word's stable `w14:paraId` attribute and survives concurrent edits.
## Agent UI primitives
The chat surfaces ship with the toolkit, not the editor:
```tsx
import {
AgentPanel,
AgentChatLog,
AgentComposer,
AgentSuggestionChip,
AgentTimeline,
} from "@eigenpal/docx-editor-agents/react";
```
Same for Vue: `@eigenpal/docx-editor-agents/vue`. The hooks don't care what UI you wire up, so bring your own if you prefer.
## Where to next
- [Bring your own agent](/docs/1.x/agents/bring-your-own): Vercel AI SDK, OpenAI, Anthropic, LangChain
- [agents/react API reference](/docs/1.x/api/agents/react), full hook signatures
- [Word JS API parity](/docs/1.x/agents/word-js-api): Office.js verb mapping
---
# Agents. Word JS API parity
Source: https://www.docx-editor.dev/docs/1.x/agents/word-js-api
## Why parity matters
The toolkit shadows the shape of Microsoft's Office.js Word API on purpose. If your model has been trained on Office.js examples, it picks up our tools without re-prompting. If you're migrating a Word add-in, the rename map below is the only diff.
`WordCompatBridge` is a TypeScript type alias that formally documents every method we mirror. A compile-time static assertion fails the build if a method goes out of sync.
## Verb map
| Office.js | DOCX Editor toolkit | Notes |
|---|---|---|
| `Range.insertComment(text)` | `addComment({ paraId, text })` | We address by stable `paraId`, not by `Range`. |
| `Body.search(query)` | `findText({ query })` | Returns paragraphs with `paraId`s. |
| `Comment.reply(text)` | `replyComment({ commentId, text })` | |
| `Comment.resolved = true` | `resolveComment({ commentId })` | |
| `Range.scrollIntoView()` | `scroll({ paraId })` | |
| `Range.insertText(text, location)` | `suggestChange({ paraId, ... })` | Inserts as a tracked change. |
| `Range.font.bold = true` | `applyFormatting({ paraId, bold: true })` | |
| `Range.paragraphFormat.styleName = "Heading 1"` | `setParagraphStyle({ paraId, style: "Heading1" })` | |
## Addressing model
Office.js uses `Range` objects that are live and tied to a document model. JSON-RPC tool transports can't ferry live objects, they need a string ID that survives serialization, network round-trips, and concurrent edits.
The toolkit uses Word's `w14:paraId` attribute. Every paragraph in a Word-authored `.docx` has one. They're stable across edits, concurrent collaboration, and tool loop iterations. `read_document` returns paragraphs tagged with their `paraId`; every mutate tool takes a `paraId` as input.
## Migrating a Word add-in
1. Replace `Word.run(async (context) => { ... })` with a `DocxReviewer.fromBuffer(buffer)` (headless) or `useDocxAgentTools({ editorRef })` (live editor) wiring.
2. Replace `Range`-based addressing with `paraId`-based addressing. Use `findText` or `read_document` to resolve a paragraph first.
3. Replace each Office.js method call with its tool equivalent from the table above.
For agents you're writing fresh, you don't need this mapping at all, just call the tools directly.
## Where to go next
- [Tool catalog](/docs/1.x/api/agents), full schemas
- [Live editor](/docs/1.x/agents/live-editor), wire to a ``
- [Bring your own agent](/docs/1.x/agents/bring-your-own), provider adapters
---
# 1.x/core/index
Source: https://www.docx-editor.dev/docs/1.x/core/index
## What's in it
The document tree, the OOXML parser and serializer, the ProseMirror schema, layout primitives, the plugin contract. No DOM, no framework. The React and Vue adapters depend on it. You install `-core` directly only when you don't want an editor UI on the page.
```bash
npm install @eigenpal/docx-editor-core
```
## When to use it directly
- Server-side `.docx` processing: parse buffer, walk the tree, mutate, serialize back out.
- Headless extraction: pull text, tables, headers/footers without rendering.
- Custom renderer or DOM bridge against the shared schema.
- Plugins that need to work in React, Vue, and headless contexts. Use the `core-plugins` subpath.
If you want an actual editor in a React or Vue app, install `@eigenpal/docx-editor-react` or `@eigenpal/docx-editor-vue`. Both pull `-core` in transitively, so you don't need it as a separate dep.
## Subpaths
61 published subpaths. The ones you'll touch most:
- `@eigenpal/docx-editor-core`: root barrel
- `@eigenpal/docx-editor-core/docx-parser`: `parseDocx(buffer)` → `Document` tree
- `@eigenpal/docx-editor-core/docx-serializer`: `Document` → `.docx` buffer
- `@eigenpal/docx-editor-core/types`: `Document`, `Paragraph`, `Run`, `Insertion`, `Deletion`, `Comment`
- `@eigenpal/docx-editor-core/core-plugins`: plugin contract for non-React, non-Vue hosts
- `@eigenpal/docx-editor-core/headless`: bridge-shaped APIs (no DOM)
All 61 are in the [API reference](/docs/1.x/api/core).
## Parse, mutate, serialize
```ts
import { parseDocx } from "@eigenpal/docx-editor-core/docx-parser";
import { serializeDocx } from "@eigenpal/docx-editor-core/docx-serializer";
const buffer = await fetch("./document.docx").then((r) => r.arrayBuffer());
const tree = await parseDocx(buffer);
for (const para of tree.body) {
if (para.type === "paragraph") {
console.log(para.paraId, para.runs.map((r) => r.text).join(""));
}
}
const out = await serializeDocx(tree);
```
The tree shape is the same the editor renders against. Anything you build against `-core` ports without translation when the same buffer reaches the live editor.
## Content controls
Block-level content controls (Word's `w:sdt`) are addressable by tag, alias, id, or type. `findContentControls` lists the matches and `findContentControl` returns one; each carries its text plus modeled state (`showingPlaceholder`, `checked`, `dateFormat`, `listItems`, `dataBinding`). `setContentControlContent` fills a control with a string or block content, `setContentControlValue` sets a typed value (dropdown selection, checkbox, date), and `removeContentControl` deletes or unwraps one.
```ts
import { findContentControls, setContentControlValue } from "@eigenpal/docx-editor-core/headless";
const boxes = findContentControls(tree, { type: "checkbox" });
setContentControlValue(tree, { tag: "agreed" }, true);
```
Edits preserve the control's identity and raw properties, so the document still round-trips. Locked and typed controls are refused unless you force them. That stability is what makes content controls usable as anchors for templates and document automation. Repeating sections (`w15:repeatingSection`) round-trip the same way: `addRepeatingSectionItem` clones an item with fresh ids and `removeRepeatingSectionItem` drops one.
## Where to next
- [API reference (61 subpaths)](/docs/1.x/api/core)
- [Plugins](/docs/1.x/plugins) for the plugin contract
- [`@eigenpal/docx-editor-agents`](/docs/1.x/agents): `DocxReviewer` wraps `-core` for AI-driven document review
---
# Interactive Examples
Source: https://www.docx-editor.dev/docs/1.x/examples
## Playground
Try the full interactive playground to toggle props and see changes immediately:
## Read-Only Mode
Compare the full editor with a read-only preview. The `readOnly` prop strips the toolbar and all editing UI:
## Mode Toggle
Switch between editing, suggesting, and viewing modes to see how the editor behaves in each:
## Toolbar Customization
Customize the title bar with a logo, document name, and right-side actions:
## Author Attribution
Set the `author` prop to attribute comments and track changes to specific users:
## Agent Chat in the Editor
Added in 0.2.0. Wire `` to the agent UI kit ([``, ``, ``](/docs/1.x/react)) and a chat backend in one component. The agent reads paragraphs, adds comments, suggests tracked changes, and scrolls while the user watches.
The full client + server snippet is on the [Live editor page](/docs/1.x/agents/live-editor). For OpenAI, Anthropic, and Vercel AI SDK adapters see [Bring your own agent](/docs/1.x/agents/bring-your-own). Runnable Next.js example: [agent-chat-demo on GitHub](https://github.com/eigenpal/docx-editor/tree/main/examples/agent-chat-demo).
For the server-side flavor (no editor, batch review) see [Headless](/docs/1.x/agents) and [MCP server](/docs/1.x/agents).
---
# 1.x/i18n/index
Source: https://www.docx-editor.dev/docs/1.x/i18n/index
## Why the split
In 0.x, locales lived inside `@eigenpal/docx-js-editor` at `/i18n/.json`. With a Vue adapter joining React in 1.0, that path would have meant duplicating the same JSON in two places. `-i18n` extracts it to a standalone package both adapters depend on.
```bash
npm install @eigenpal/docx-editor-i18n
```
You usually don't install this directly. `-react` and `-vue` pull it in transitively. Install it explicitly only when you're using the locale data outside the editor (a custom toolbar, a search dropdown, that kind of thing).
## Available locales
| Code | Language | Named export |
|---|---|---|
| `en` | English | `en` |
| `pl` | Polish | `pl` |
| `de` | German | `de` |
| `fr` | French | `fr` |
| `pt-BR` | Brazilian Portuguese | `ptBR` |
| `he` | Hebrew | `he` |
| `hi` | Hindi | `hi` |
| `tr` | Turkish | `tr` |
| `zh-CN` | Chinese (Simplified) | `zhCN` |
Two import shapes are supported in 1.0.1+:
- **Named exports off the root** (`import { pl } from "@eigenpal/docx-editor-i18n"`). Use this when you ship a small static list of locales.
- **Per-locale subpath imports** (`import pl from "@eigenpal/docx-editor-i18n/pl"`). Use this when you dynamically load locales; the per-locale subpath code-splits so users only download the language they need.
The 0.x `import pl from "@eigenpal/docx-js-editor/i18n/pl.json"` JSON pattern is gone, but the new subpath form covers the same use case. See the [migration guide](/docs/1.x/migration#i18n-restructure) for the rewrite.
Verify what your installed version ships:
```bash
ls node_modules/@eigenpal/docx-editor-i18n/
```
To contribute a locale, PR the JSON to `packages/i18n/.json` upstream.
## Importing
Named exports off the package root:
```ts
import { en, pl, de, ptBR } from "@eigenpal/docx-editor-i18n";
```
Hyphenated codes (`pt-BR`, `zh-CN`) become camelCase named exports (`ptBR`, `zhCN`).
## Wiring into the editor
Pass the locale object to the `i18n` prop. React:
```tsx
import { DocxEditor } from "@eigenpal/docx-editor-react";
import { pl } from "@eigenpal/docx-editor-i18n";
;
```
Vue:
```vue
```
## Where to next
- [i18n API reference](/docs/1.x/api/i18n)
- [React API reference: i18n prop](/docs/1.x/api/react)
- [Migration: i18n restructure](/docs/1.x/migration#i18n-restructure)
---
# 1.x/index
Source: https://www.docx-editor.dev/docs/1.x/index
## Packages
| Package | What's in it |
|---|---|
| [`@eigenpal/docx-editor-core`](/docs/1.x/api/core) | Document tree, OOXML parser/serializer, ProseMirror schema. Framework-agnostic. The React and Vue adapters depend on this. |
| [`@eigenpal/docx-editor-react`](/docs/1.x/api/react) | `` plus hooks for history, find/replace, autosave, clipboard. Toolbar, dialogs, plugin host. |
| [`@eigenpal/docx-editor-vue`](/docs/1.x/api/vue) | Vue 3 adapter. Same ``, hooks become composables. Beta in 1.0. |
| [`@eigenpal/docx-editor-agents`](/docs/1.x/api/agents) | `DocxReviewer` (headless), live-editor bridge, AI SDK adapters, MCP server. 14 tools. |
| [`@eigenpal/docx-editor-i18n`](/docs/1.x/api/i18n) | Locale data. Ships `en`, `pl`, `de`, `pt-BR`, `he`, `tr`, `zh-CN`. |
All five are Apache 2.0. The agents package was AGPL-3.0 in 0.x; see the [migration guide](/docs/1.x/migration) if you tracked the license.
## Which package do I need?
**React app.** Install `-react`. It pulls in `-core` and `-i18n`.
```bash
npm install @eigenpal/docx-editor-react
```
**Vue 3 app.** Same shape, swap the adapter.
```bash
npm install @eigenpal/docx-editor-vue
```
**Server-side parsing or batch review.** Use `-core` for the document tree and `-agents` for `DocxReviewer`.
```bash
npm install @eigenpal/docx-editor-core @eigenpal/docx-editor-agents
```
**Add an AI agent to an existing editor.** Layer `-agents` on top of `-react` or `-vue`.
```bash
npm install @eigenpal/docx-editor-agents
```
**Expose tools over MCP.** Same install. The MCP server is the `/mcp` subpath of `-agents`.
## Render an editor (React)
```tsx
import { DocxEditor } from "@eigenpal/docx-editor-react";
import "@eigenpal/docx-editor-react/styles.css";
export default function App() {
return ;
}
```
`documentBuffer={null}` mounts an empty document. Pass an `ArrayBuffer` to load a `.docx`. See [Installation](/docs/1.x/installation) for Next.js, Vite, Remix, Astro, Vue specifics.
## Where to next
- [Installation](/docs/1.x/installation), per-framework setup
- [Migration: 0.x → 1.x](/docs/1.x/migration)
- [API reference](/docs/1.x/api), every published subpath, generated from the d.ts
- [Realtime collaboration](/docs/1.x/realtime-collaboration): Yjs
- [Plugins](/docs/1.x/plugins)
- [Examples](/docs/1.x/examples), interactive demos
---
# Installation
Source: https://www.docx-editor.dev/docs/1.x/installation
## Pick a package
Five packages, install what you need. The framework adapters pull `-core` and `-i18n` in transitively, so for most apps it's one install.
```bash
# React
npm install @eigenpal/docx-editor-react
# Vue 3
npm install @eigenpal/docx-editor-vue
# Headless / server-side (no UI)
npm install @eigenpal/docx-editor-core
# Agent toolkit (layer on top of an adapter)
npm install @eigenpal/docx-editor-agents
```
## React frameworks
### Next.js (App Router)
ProseMirror touches the DOM at mount, so the editor renders client-side. Mark the file `"use client"`:
```tsx
// app/editor/page.tsx
"use client";
import { DocxEditor } from "@eigenpal/docx-editor-react";
import "@eigenpal/docx-editor-react/styles.css";
export default function Page() {
return ;
}
```
Wrap the page in a server component if you want the chrome (nav, footer) to SSR; only the editor itself needs `"use client"`. React 19 + Next 15+: no extra config.
Runnable example: [`examples/nextjs`](https://github.com/eigenpal/docx-editor/tree/main/examples/nextjs).
### Vite
Vite is plain client-rendered React. No SSR caveat.
```tsx
// src/App.tsx
import { DocxEditor } from "@eigenpal/docx-editor-react";
import "@eigenpal/docx-editor-react/styles.css";
export default function App() {
return ;
}
```
Runnable example: [`examples/vite`](https://github.com/eigenpal/docx-editor/tree/main/examples/vite).
### Remix
Same constraint as Next. Render the editor inside a route module client-side, or use Remix's `` helper.
Runnable example: [`examples/remix`](https://github.com/eigenpal/docx-editor/tree/main/examples/remix).
### Astro
React island with `client:only="react"`:
```astro
---
import EditorIsland from "./EditorIsland.tsx";
---
```
`client:only` skips Astro's SSR step for the island, which is what you want here.
Runnable example: [`examples/astro`](https://github.com/eigenpal/docx-editor/tree/main/examples/astro).
## Vue 3
```vue
```
Same props as React, same dialogs, same plugin API. See the [Vue page](/docs/1.x/vue) for the parity contract.
Runnable example: [`examples/vue`](https://github.com/eigenpal/docx-editor/tree/main/examples/vue).
### Nuxt
On Nuxt, install the official module `@eigenpal/nuxt-docx-editor` instead of wiring the adapter by hand. It registers an SSR-safe `` as an auto-imported component, so there's no manual import and no `` wrapper.
```bash
npm install @eigenpal/nuxt-docx-editor
```
```ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@eigenpal/nuxt-docx-editor"],
});
```
```vue
```
The module wraps `@eigenpal/docx-editor-vue` (a transitive dependency), adds its stylesheet to Nuxt's CSS pipeline, and registers `` as a client-only component. Works on Nuxt 3 and 4. See the [Nuxt module page](/docs/1.x/vue/nuxt) for options and auto-imported composables.
Runnable example: [`examples/nuxt`](https://github.com/eigenpal/docx-editor/tree/main/examples/nuxt).
## Headless / Node
Skip the adapter when you don't need an editor on the page:
```ts
import { parseDocx, serializeDocx } from "@eigenpal/docx-editor-core";
const tree = await parseDocx(buffer);
// ...mutate the tree...
const outBuffer = await serializeDocx(tree);
```
For server-side AI review, layer `@eigenpal/docx-editor-agents` on top:
```ts
import { DocxReviewer } from "@eigenpal/docx-editor-agents";
const reviewer = await DocxReviewer.fromBuffer(buffer);
await reviewer.addComment({ paraId: "...", text: "..." });
```
## CSS
Each adapter ships a CSS bundle at `/styles.css`. Import once:
```ts
import "@eigenpal/docx-editor-react/styles.css";
// or
import "@eigenpal/docx-editor-vue/styles.css";
```
The editor's styles are scoped (`.docx-editor-root` and below), so they don't collide with Tailwind utilities or your app's CSS.
## Next steps
- [Migration: 0.x → 1.x](/docs/1.x/migration)
- [React API reference](/docs/1.x/api/react)
- [Vue API reference](/docs/1.x/api/vue)
- [Agents](/docs/1.x/agents) for AI integration
---
# Migration: 0.x → 1.x
Source: https://www.docx-editor.dev/docs/1.x/migration
## What moved where
0.x was a single package. 1.x splits it into five, by concern:
| What you used in 0.x | Where it is in 1.x |
|---|---|
| The `` component, toolbar, dialogs, hooks, plugin host | `@eigenpal/docx-editor-react` (or `@eigenpal/docx-editor-vue`) |
| The document model and OOXML parse/serialize (`Document`, `Paragraph`, `createEmptyDocument`, ...) | `@eigenpal/docx-editor-core` |
| Anything AI: the reviewer, agent UI components, the MCP server | `@eigenpal/docx-editor-agents` |
| Locale strings (`pl`, `de`, `pt-BR`, ...) | `@eigenpal/docx-editor-i18n` |
In practice a React app still imports from one package, `@eigenpal/docx-editor-react`, which pulls in `-core` and `-i18n` for you. You only name the other packages when you import the document model, the agent toolkit, or locale data directly. The rest of this guide is the symbol-level detail behind that split.
## TL;DR
1. Install the new package set (`-react` or `-vue`, plus `-core` and `-agents` if you need them).
2. **Update the symbols whose home changed across packages first** (see [Moved symbols](#moved-symbols)), a plain package-name swap leaves them unresolved.
3. Change every remaining `@eigenpal/docx-js-editor` import to `@eigenpal/docx-editor-react`.
4. Rename `FormattingBar` → `Toolbar` and `Toolbar` (composite) → `EditorToolbar`.
5. Remove `showPrintButton` from `` and toolbar props (deleted in 1.0).
6. Rewire i18n imports, locales are **named exports** now, not subpath JSON files.
7. License changes: every package is now Apache 2.0 (the agents package was AGPL-3.0).
The mechanical work is small but the moved symbols catch people. Read [Moved symbols](#moved-symbols) before you start updating imports.
## Package renames
The monolithic `@eigenpal/docx-js-editor` split into five packages under the `@eigenpal/docx-editor-*` namespace.
| Old (0.x) | New (1.x) |
|---|---|
| `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-react` |
| `@eigenpal/docx-js-editor/core` | `@eigenpal/docx-editor-core` |
| `@eigenpal/docx-js-editor/mcp` | `@eigenpal/docx-editor-agents/mcp` |
| `@eigenpal/docx-js-editor/i18n/.json` | `@eigenpal/docx-editor-i18n/` (subpath) or named export off the root |
| `@eigenpal/docx-editor-agents` (AGPL-3.0) | `@eigenpal/docx-editor-agents` (Apache 2.0) |
Install the package set you actually use:
```bash
# Bare minimum for a React editor:
npm uninstall @eigenpal/docx-js-editor
npm install @eigenpal/docx-editor-react
# Vue 3 instead:
npm install @eigenpal/docx-editor-vue
# Add the agent toolkit (also where the agent UI components live now):
npm install @eigenpal/docx-editor-agents
# Standalone i18n (transitively installed by -react and -vue):
npm install @eigenpal/docx-editor-i18n
```
`-core` and `-i18n` are pulled in transitively by `-react` and `-vue`. Install them explicitly only when you import directly from those packages.
## Moved symbols
Five categories of symbols moved across packages (not just subpaths). A plain package-name swap to `@eigenpal/docx-editor-react` won't resolve these. **If a symbol from this list lives in your code, point its import at the new home below, otherwise you'll get a wall of "no exported member" errors.**
| Symbol(s) | Was (0.x) | Now (1.x, recommended) |
|---|---|---|
| `createEmptyDocument`, `createDocumentWithText`, `CreateEmptyDocumentOptions` | `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-react` or `@eigenpal/docx-editor-vue` (re-exported in 1.0.1 from `-core`) |
| Document-tree types most apps reach for: `Document`, `Comment`, `Paragraph`, `Run`, `Insertion`, `Deletion`, `TrackedChangeInfo`, `Hyperlink`, `Table` | `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-core` (root entry surfaces them in 1.0.1) |
| Rarer document-tree types: `Image`, `Footnote`, `Endnote`, `TableRow`, `TableCell` | `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-core/headless` |
| Agent UI components: `AgentPanel`, `AgentChatLog`, `AgentComposer`, `AgentSuggestionChip`, `AgentTimeline` (and their `*Props` types) | `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-agents/react` (or `/vue`) |
| `PluginHost`, `PluginHostProps`, `PluginHostRef`, `templatePlugin` | `@eigenpal/docx-js-editor` | `@eigenpal/docx-editor-react/plugin-api` |
**Why these moved.** Agent UI primitives ship with the toolkit they pair with, not with the editor. Plugin types live next to the plugin contract. Document types and `createEmptyDocument` are declared in `-core` (framework-agnostic); 1.0.1 re-exports `createEmptyDocument`/`createDocumentWithText` from `-react` and `-vue` so most apps don't need a separate `-core` install, and surfaces the common document-tree types at the `-core` root entry so the deep `-core/headless` subpath only matters for the long tail.
**Pin to ≥1.0.1.** Earlier 1.0.0 required importing `createEmptyDocument` from `@eigenpal/docx-editor-core` (a separate install) and document types from `@eigenpal/docx-editor-core/headless` (an unguessable subpath). 1.0.1 fixed both. If you're on 1.0.0, upgrade the patch version before migrating; it removes most of the import-pain in this guide.
You don't need a script for this. TypeScript will point you at each one: a moved symbol that's still importing from the old package shows up as a `Module has no exported member` error, and the table above gives its new home. Fix them as they come up.
## Remaining imports
Once the moved symbols are handled, every remaining `@eigenpal/docx-js-editor` import changes to `@eigenpal/docx-editor-react`. Only the package name changes; the imported names stay the same.
A few 0.x subpaths don't live under `-react` and need a different package:
| 0.x subpath | 1.x home |
|---|---|
| `@eigenpal/docx-js-editor/core` | `@eigenpal/docx-editor-core` |
| `@eigenpal/docx-js-editor/mcp` | `@eigenpal/docx-editor-agents/mcp` |
| `@eigenpal/docx-js-editor/i18n/.json` | `@eigenpal/docx-editor-i18n` (see [i18n restructure](#i18n-restructure) below) |
## API renames
### `FormattingBar` → `Toolbar`
What was previously called the "formatting bar" is now `Toolbar`, a closer name to what it actually is.
```diff
- import { FormattingBar } from "@eigenpal/docx-js-editor";
+ import { Toolbar } from "@eigenpal/docx-editor-react/ui";
```
### `Toolbar` (composite) → `EditorToolbar`
The old `Toolbar` that combined a menu bar + the formatting buttons is now `EditorToolbar`. The formatting strip on its own is `Toolbar`. The lower-level pieces (`ToolbarButton`, `ToolbarGroup`, `ToolbarSeparator`, `ResponsiveToolbar`) are exported alongside.
```diff
- import { Toolbar } from "@eigenpal/docx-js-editor";
+ import { EditorToolbar } from "@eigenpal/docx-editor-react/ui";
// or compose your own:
+ import { Toolbar, ToolbarButton, ToolbarGroup } from "@eigenpal/docx-editor-react/ui";
```
If you want the same behavior as 0.x's `Toolbar`, use `EditorToolbar`. If you want only the formatting buttons (what 0.x called `FormattingBar`), use the new `Toolbar`.
`MenuBar` from 0.x is gone in 1.x. Compose your own from `ToolbarButton` and `ToolbarGroup`, or use `EditorToolbar` which includes a menu strip by default.
## Removed APIs
### `showPrintButton`
Removed from `` and the toolbar. Render your own button if you need it.
```diff
-
+
```
## i18n restructure
The package changed name (`-i18n` is now standalone) and gained two import styles. Both are supported in **1.0.1+**.
### Locales: subpath or named export
The 1.0 release shipped named exports only and broke per-locale code splitting in the process. 1.0.1 restored subpath exports, so you can pick:
```ts
// Subpath, bundlers tree-shake to just this locale's strings. Best for dynamic loading.
import pl from "@eigenpal/docx-editor-i18n/pl";
// Named, drops the dash for camelCase. Best for static "load all known locales" lists.
import { pl, ptBR } from "@eigenpal/docx-editor-i18n";
```
The diff from 0.x:
```diff
- import pl from "@eigenpal/docx-js-editor/i18n/pl.json";
+ import pl from "@eigenpal/docx-editor-i18n/pl";
```
### Locale codes
| Locale | 0.x JSON path | 1.x subpath | 1.x named export |
|---|---|---|---|
| English | `/i18n/en.json` | `/en` | `en` |
| Polish | `/i18n/pl.json` | `/pl` | `pl` |
| German | `/i18n/de.json` | `/de` | `de` |
| Brazilian Portuguese | `/i18n/pt-BR.json` | `/pt-BR` | `ptBR` |
| Hebrew | `/i18n/he.json` | `/he` | `he` |
| Turkish | `/i18n/tr.json` | `/tr` | `tr` |
| Chinese (Simplified) | `/i18n/zh-CN.json` | `/zh-CN` | `zhCN` |
### Dynamic locale imports
Path-templated dynamic imports work the same shape as before, just against the new package:
```diff
- import(`@eigenpal/docx-js-editor/i18n/${locale}.json`).then(setI18n);
+ import(`@eigenpal/docx-editor-i18n/${locale}`).then((m) => setI18n(m.default));
```
Each subpath emits its own chunk, so the bundler ships only the locale that loaded.
### TypeScript types
In 0.x each locale's type was the inferred `typeof `, concrete and indistinguishable from one another. In 1.x, only `en` is the source-of-truth `LocaleStrings`; the others are `PartialLocaleStrings` (some keys may be missing). Update your local type unions:
```diff
- type LocaleI18n = typeof pl | typeof de | undefined;
+ import type { LocaleStrings, PartialLocaleStrings } from "@eigenpal/docx-editor-i18n";
+ type LocaleI18n = LocaleStrings | PartialLocaleStrings | undefined;
```
## License change
The 0.x line of `@eigenpal/docx-editor-agents` was AGPL-3.0. In 1.0, all five packages are **Apache 2.0**. If you tracked license obligations for compliance review, update the entry. Apache 2.0 is permissive: no copyleft on derivative works.
The non-agents packages (`-core`, `-react`, `-vue`, `-i18n`) were also previously AGPL through `@eigenpal/docx-js-editor`; they're now Apache 2.0 as well.
## Vue users
Vue 3 is a published adapter in 1.0; it didn't exist in 0.x. If you were embedding the editor in a Vue app via the React adapter + a wrapper, drop the wrapper and use `@eigenpal/docx-editor-vue` directly. The surface mirrors React (same component name, hooks-as-composables, same plugin contract). Cross-adapter parity is enforced by `etc/parity.contract.json` upstream. See the [Vue package page](/docs/1.x/vue).
## Verifying the migration
1. **TypeScript first.** TypeScript will flag every moved symbol with a `Module has no exported member` error. Those are your roadmap to the [Moved symbols](#moved-symbols) table.
2. **Build the project.** `next build` / `vite build` catches dynamic-import path errors that TS misses.
3. **Run your test suite.** The behavior of every preserved API is unchanged; tests are the safety net for the import changes.
4. **Check the editor visually** for the missing print button and any toolbar configuration you used to pass via `showPrintButton`.
If something behaves differently and isn't covered above, it's likely a bug rather than an intended change, please file an issue in the [docx-editor repo](https://github.com/eigenpal/docx-editor/issues).
## Where to go next
- [Installation](/docs/1.x/installation), framework-specific setup for 1.x
- [Package overview](/docs/1.x), which package does what
- [API reference](/docs/1.x/api), every export, generated from upstream types
- [0.x archive](/docs/0.x), your old docs are still here if you need them
---
# CorePlugin & Headless API
Source: https://www.docx-editor.dev/docs/1.x/plugins/core-plugins
## What is the Headless API?
The package has two entry points:
```
src/index.ts → Browser editor (React, needs DOM)
src/headless.ts → Headless API (Node.js, no DOM needed)
```
The headless API gives you `DocumentAgent`, parsers, serializers, and template processing, everything you need to manipulate DOCX files programmatically in Node.js. No browser, no React, no ProseMirror.
```ts
import { DocumentAgent, parseDocx, processTemplate } from '@eigenpal/docx-editor-react/headless';
```
**CorePlugins** extend the headless API with custom command handlers. They're the server-side equivalent of EditorPlugins.
## When to Use the Headless API
- **API routes**, fill templates, generate documents server-side
- **CI/CD pipelines**, validate templates, extract variables
- **Node.js scripts**, batch-process DOCX files
- **Server-side agents**, programmatic document manipulation
If you need UI panels, overlays, or ProseMirror decorations, use an [EditorPlugin](/docs/1.x/plugins/editor-plugins) instead.
## DocumentAgent
`DocumentAgent` is the main entry point for headless document manipulation:
```ts
import { DocumentAgent } from '@eigenpal/docx-editor-react/headless';
import fs from 'fs';
// Load a DOCX file
const buffer = fs.readFileSync('template.docx');
const agent = await DocumentAgent.fromBuffer(buffer);
// Read content
console.log('Word count:', agent.getWordCount());
console.log('Variables:', agent.getVariables());
// Edit
const edited = agent
.insertText({ paragraphIndex: 0, offset: 0 }, 'Hello ')
.applyStyle(0, 'Heading1');
// Fill template variables
const filled = await edited.applyVariables({
customer_name: 'Jane Doe',
date: '2024-02-15',
});
// Export
const output = await filled.toBuffer();
fs.writeFileSync('output.docx', Buffer.from(output));
```
### In a Next.js API Route
```ts
// app/api/fill-template/route.ts
import { processTemplate } from '@eigenpal/docx-editor-react/headless';
export async function POST(req: Request) {
const formData = await req.formData();
const file = formData.get('file') as File;
const variables = JSON.parse(formData.get('variables') as string);
const buffer = await file.arrayBuffer();
const filled = await processTemplate(buffer, variables);
return new Response(filled, {
headers: {
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
},
});
}
```
### Template Processing Utilities
```ts
import {
processTemplate,
getTemplateTags,
validateTemplate,
} from '@eigenpal/docx-editor-react/headless';
// Get all template variables from a DOCX
const tags = await getTemplateTags(buffer);
// → ['name', 'date', 'items', 'total']
// Validate template syntax
const result = await validateTemplate(buffer);
// → { valid: true } or { valid: false, errors: [...] }
// Fill template with data
const filled = await processTemplate(buffer, {
name: 'Jane Doe',
date: '2024-02-15',
items: [{ name: 'Widget', price: 9.99 }],
total: '$9.99',
});
```
## CorePlugin Interface
CorePlugins extend `DocumentAgent` with custom command handlers:
```ts
interface CorePlugin {
id: string;
name: string;
version?: string;
description?: string;
commandHandlers?: Record;
initialize?: () => void | Promise;
destroy?: () => void | Promise;
dependencies?: string[];
}
```
### Fields
| Field | Required | Description |
| ----------------- | -------- | -------------------------------------------- |
| `id` | Yes | Unique identifier |
| `name` | Yes | Human-readable name |
| `version` | No | Semver version string |
| `description` | No | Short description |
| `commandHandlers` | No | Map of command type → handler function |
| `initialize` | No | Called once during registration |
| `destroy` | No | Cleanup on unregistration |
| `dependencies` | No | IDs of plugins that must be registered first |
## Command Handlers
A command handler is a pure function that receives a `Document` and a command, then returns a new `Document`:
```ts
type CommandHandler = (doc: Document, command: PluginCommand) => Document;
interface PluginCommand {
type: string;
id?: string;
position?: Position;
range?: Range;
[key: string]: unknown;
}
```
Example, a plugin that adds watermark text:
```ts
import type { CorePlugin, PluginCommand } from '@eigenpal/docx-editor-react';
import type { Document } from '@eigenpal/docx-editor-core';
const watermarkPlugin: CorePlugin = {
id: 'watermark',
name: 'Watermark',
commandHandlers: {
addWatermark(doc: Document, cmd: PluginCommand) {
const text = (cmd as { text: string }).text;
// .., transform doc to add watermark header
return doc;
},
},
};
```
Use it:
```ts
import { pluginRegistry } from '@eigenpal/docx-editor-react';
pluginRegistry.register(watermarkPlugin);
const handler = pluginRegistry.getCommandHandler('addWatermark');
if (handler) {
const newDoc = handler(doc, { type: 'addWatermark', text: 'DRAFT' });
}
```
## PluginRegistry
The global `pluginRegistry` manages all CorePlugins:
```ts
import { pluginRegistry } from '@eigenpal/docx-editor-react';
// Register
pluginRegistry.register(myPlugin);
// Query
pluginRegistry.has('watermark'); // true
pluginRegistry.getAll(); // CorePlugin[]
pluginRegistry.getCommandTypes(); // ['addWatermark']
// Unregister
pluginRegistry.unregister('watermark');
// Batch registration
import { registerPlugins } from '@eigenpal/docx-editor-react';
registerPlugins([pluginA, pluginB]);
```
## Reference Implementation: Docxtemplater Plugin
The built-in `docxtemplaterPlugin` in `src/core-plugins/docxtemplater/` is a full reference:
- **Command handlers**: `insertTemplateVariable`, `replaceWithTemplateVariable`
- Lazy dependency validation. `processTemplate` checks for `docxtemplater`/`pizzip` at call time
```ts
import { pluginRegistry, docxtemplaterPlugin } from '@eigenpal/docx-editor-react';
pluginRegistry.register(docxtemplaterPlugin);
// Now DocumentAgent can dispatch insertTemplateVariable commands
```
Note: there is also a separate **EditorPlugin** for template UI (`src/plugins/template/`) that handles syntax highlighting and the annotation panel in the browser. The two systems are independent but complement each other, a single feature can span both.
## Next Steps
- [EditorPlugin API](/docs/1.x/plugins/editor-plugins), browser-side UI plugins
- [Examples & Cookbook](/docs/1.x/plugins/examples), advanced patterns
- [Getting Started](/docs/1.x/plugins/getting-started), overview and hello world
---
# EditorPlugin API
Source: https://www.docx-editor.dev/docs/1.x/plugins/editor-plugins
EditorPlugins run in the browser alongside `DocxEditor`. They can contribute UI panels, document overlays, ProseMirror plugins, and scoped CSS.
## How It Works
`PluginHost` wraps `DocxEditor` and manages the plugin lifecycle:
Internally, `PluginHost` uses `React.cloneElement` to inject props into the child `DocxEditor`. This means `DocxEditor` **must** be the direct child of `PluginHost`.
## Quick Start
```tsx
import { DocxEditor, PluginHost, templatePlugin } from '@eigenpal/docx-editor-react';
function Editor({ file }: { file: ArrayBuffer }) {
return (
);
}
```
## EditorPlugin\
```ts
interface EditorPlugin {
id: string;
name: string;
proseMirrorPlugins?: ProseMirrorPlugin[];
Panel?: React.ComponentType>;
panelConfig?: PanelConfig;
onStateChange?: (view: EditorView) => TState | undefined;
initialize?: (view: EditorView | null) => TState;
destroy?: () => void;
styles?: string;
renderOverlay?: (
context: RenderedDomContext,
state: TState,
editorView: EditorView | null
) => ReactNode;
}
```
### Fields
| Field | Required | Description |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `id` | Yes | Unique identifier. Used as key for state storage and CSS `