@anmjin/docx-importer
v1.0.1
Published
Convert Word .docx files into Tiptap/ProseMirror JSON (and Payload Lexical state), with embedded images and human-readable import errors
Maintainers
Readme
@anmjin/docx-importer
Converts Word .docx files into Tiptap/ProseMirror JSON,
extracts the embedded images, and explains failures in plain language for the
person who uploaded the file. An optional second entry point turns the result
into Lexical editor state for Payload CMS.
Built for Dhivehi (Thaana, right-to-left) publishing, but nothing in it is language-specific.
What it handles: headings (Word heading styles, and outline levels on custom
styles), paragraphs, bold/italic/underline/strike/code/sub/superscript,
hyperlinks, bullet and numbered lists, tables (flattened to cell | cell
rows), manual line breaks, tracked changes (insertions kept, deletions dropped),
embedded images with the caption paragraph after them, and a suggested title.
Installing
npm install @anmjin/docx-importerNode 20 or newer. Versions before 1.0.1 were published only to GitHub
Packages. If your project's .npmrc still maps @anmjin to
npm.pkg.github.com, remove that line.
Usage
import { readFile } from 'node:fs/promises'
import { importDocx, DocxImportError } from '@anmjin/docx-importer'
try {
const result = await importDocx(await readFile('essay.docx'), 'my-essay')
result.tiptapJson // { type: 'doc', content: [...] } — Tiptap/ProseMirror JSON
result.images // [{ filename, buffer }] — embedded images, in document order
result.suggestedTitle // "Title"-style text, else the first Heading 1, else the first heading
result.warnings // non-fatal problems, e.g. a skipped .emf drawing
result.headingCount
result.imageCount
} catch (err) {
if (err instanceof DocxImportError) {
// safe to show to the uploader as-is
console.log(err.userMessage, err.where, err.recoverable)
} else {
throw err
}
}Images
Each image becomes a figureImage node whose src is a placeholder built from
the image's filename; by default /images/articles/<slug>/<filename>. Upload
the buffers from result.images wherever you store media, then swap the
placeholders for real URLs. To choose the placeholder format:
await importDocx(buffer, 'my-essay', {
imageSrc: (filename, slug) => `pending-upload://${slug}/${filename}`,
})Payload CMS (Lexical)
import { tiptapToLexical } from '@anmjin/docx-importer/lexical'
// map each figureImage src to the id of the media document you uploaded
const { state, warnings } = tiptapToLexical(result.tiptapJson, (src) => mediaIdBySrc.get(src) ?? null)
await payload.create({ collection: 'articles', data: { title, content: state }, draft: true })Images whose src resolves to null are dropped and reported in warnings.
Text direction (rtl/ltr) is detected per block from its script.
Developing
npm install
npm test # builds, then runs the regression suite
DOCX_FIXTURES_DIR=path/to/docs npm test # also imports a folder of real .docx filesReal-world fixtures aren't committed: they tend to be unpublished articles.
Releasing
Bump the version and push the tag; the CI workflow runs the tests and publishes to npm (via npm trusted publishing, so no token is stored in the repo):
npm version patch # or minor / major
git push --follow-tagsLicense
MIT
