docx-editor

Getting started

Install the package, mount the editor, and export the edited document.

Install the package

npm i @portone/docx-editor

Import the stylesheet

import "@portone/docx-editor/styles.css";

Once per application; it includes the ProseMirror base styles. Styling covers the custom properties that restyle the frame.

Get a document to open

document takes a File, a Blob, an ArrayBuffer, or a Uint8Array:

const response = await fetch("/agreement.docx");
const file = new File([await response.blob()], "agreement.docx");

Mount the editor

import { DocxEditor, type DocxEditorHandle } from "@portone/docx-editor";
import { useRef } from "react";

export function Editor({ file }: { file: File }) {
  const editorRef = useRef<DocxEditorHandle | null>(null);

  return (
    <DocxEditor
      ref={editorRef}
      document={file}
      mode={{ kind: "edit", author: { id: user.id, name: user.name } }}
    />
  );
}

mode is a reader's, a commenter's, or an editor's surface, and author is the signed-in user comments are written under; Props describes the prop and Modes what each kind lets a person do. The editor fills the height it is given, so put it in a box with one.

Export the edited document

downloadDocx takes the handle from the ref and saves the current state as a file:

import { downloadDocx } from "@portone/docx-editor";

const download = () => {
  const result = downloadDocx(editorRef.current, { fileName: file.name });
  if (result.status === "blocked") {
    console.warn(result.problems.map((problem) => problem.code));
  }
};

<button onClick={download} type="button">
  Export .docx
</button>;

It reports unavailable while no document is open, empty when there is nothing to export, and blocked with the problems when the document cannot be written back safely. For the bytes without a download, call editorRef.current.exportBytes(); for the problems ahead of it, editorRef.current.exportProblems().

Check what you see

An opened document shows its own paper, fonts, and styles, with page boundaries drawn over the text and the toolbar above it. If a panel naming a reason appears instead, the document was refused; pass renderImportError to render the refusal yourself, and Errors lists the codes. Exporting and reopening the file in Word is the real check.

Next.js and other prerendering frameworks

The editor cannot render on the server, so load it from a client component through a dynamic import with server rendering turned off:

"use client";

import dynamic from "next/dynamic";

const Editor = dynamic(() => import("./Editor").then((m) => m.Editor), {
  ssr: false,
});

Fetch the document inside that client component too. The App Router also needs @portone/docx-editor in transpilePackages when it resolves to TypeScript sources, as a workspace link does.

On this page