---
title: "Import .docx in your editor"
description: "Learn how to import DOCX (Word) documents into a Tiptap editor using the Import extension in our docs."
canonical_url: "https://tiptap.dev/docs/conversion/import/docx/editor-extension"
---

# Import .docx in your editor

Learn how to import DOCX (Word) documents into a Tiptap editor using the Import extension in our docs.

- **1. Activate trial or subscribe**

  Start a [free trial](https://cloud.tiptap.dev/v2?trial=true) or [subscribe to the Start
  plan](https://cloud.tiptap.dev/v2/billing) in your account.
- **2. Install from private registry**

  To install this frontend extensions, authenticate to Tiptap’s private npm registry by following
  the [setup guide](https://tiptap.dev/docs/guides/pro-extensions.md).

> **Interactive demo:** [ImportDocx](https://embed-pro.tiptap.dev/preview/Extensions/ImportDocx)

Converting `.docx` files to Tiptap JSON is simple with the `@tiptap-pro/extension-import-docx` editor extension, which integrates directly into your Tiptap editor.

You can also import via the [REST API](https://tiptap.dev/docs/conversion/import/docx/rest-api.md). Both paths use the same conversion service and produce identical content output. The main differences:

|                        | Editor extension                             | REST API                         |
| ---------------------- | -------------------------------------------- | -------------------------------- |
| Runs in                | Your editor (client-side)                    | Tiptap cloud (server-side)       |
| Footnotes and endnotes | Auto-applied via Pages; data on the callback | Returned in response             |
| Headers and footers    | Auto-applied via Pages extension             | Returned as separate data fields |
| Unknown content        | Rewritten against your editor schema         | Raw JSON, no schema filtering    |

Choose the REST API if you need server-side processing or raw unfiltered JSON.

## Install the DOCX Import extension

The Conversion extensions are published in Tiptap’s private npm registry. Integrate the extensions by following the [private registry guide](https://tiptap.dev/docs/guides/pro-extensions.md).

Install the Tiptap Import extension package:

```bash
npm i @tiptap-pro/extension-import-docx
```

Ensure your editor includes all necessary Tiptap extensions to handle content from DOCX. For the complete list of extensions, configuration, and what each one handles, see the [ConvertKit](https://tiptap.dev/docs/conversion/import/docx/convertkit.md) page.

## Required extensions

Install [ConvertKit](https://tiptap.dev/docs/conversion/import/docx/convertkit.md): it bundles every extension the importer needs: the standard nodes and marks, `TextStyleKit` for color/font/size/line-height/background, `TextAlign` preconfigured for `['paragraph', 'heading']`, `Highlight` with `multicolor: true`, an `Image` with DOCX crop attributes, `ConvertTableKit` for DOCX-aware tables, and `PageBreak`:

```ts
import { ConvertKit } from '@tiptap-pro/extension-convert-kit'

new Editor({ extensions: [ConvertKit] })
```

You can disable any individual slot via `ConvertKit.configure({ slot: false })` if you need to swap one of the bundled extensions for your own (e.g. when pairing with the [Pages extension](https://tiptap.dev/docs/pages/getting-started/overview.md), set `table: false` and register `TableKit` from `@tiptap-pro/extension-pages-tablekit` instead). See the [ConvertKit reference](https://tiptap.dev/docs/conversion/import/docx/convertkit.md) for the full slot list.

## Configure

Add the Import extension to your editor setup.

```ts
import { ImportDocx } from '@tiptap-pro/extension-import-docx'

const editor = new Editor({
  extensions: [
    // Other extensions ...
    ImportDocx.configure({
      token: 'your-jwt', // JWT for authentication (see Authentication documentation)
      imageUploadConfig: {
        url: 'https://your-image-upload-endpoint.com',
      },
    }),
    // Other extensions ...
  ],
  // Other editor settings ...
})
```

| Property            | Description                                                                                                                                                                                                                                                                                                                            |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`             | A JWT generated by your server for the Convert service. It is the only credential a request needs. See [Authentication](https://tiptap.dev/docs/authentication.md) for how to issue one.                                                                                                                                               |
| `endpoint`          | The Convert API base URL. Set it only to point at another deployment. When set, it overrides `region`.                                                                                                                                                                                                                                 |
| `region`            | The Tiptap Cloud region that runs the conversion, `'eu'` or `'us'`. Defaults to `'eu'`. Ignored when `endpoint` is set.                                                                                                                                                                                                                |
| `imageUploadConfig` | Structured configuration for image uploads during import. See [Image upload configuration](#image-upload-configuration) for details.                                                                                                                                                                                                   |
| `prosemirrorNodes`  | Renames the node types the import produces, for schemas that use different names. See [custom node mapping](https://tiptap.dev/docs/conversion/import/docx/custom-node-mapping.md).                                                                                                                                                    |
| `prosemirrorMarks`  | Renames the mark types the import produces, and maps Word character styles onto marks. See [custom mark mapping](https://tiptap.dev/docs/conversion/import/docx/custom-mark-mapping.md).                                                                                                                                               |
| `cssStyles`         | Opts into extracting the document's default styles as CSS so you can apply them to the editor. See [CSS injection](https://tiptap.dev/docs/conversion/import/docx/css-injection.md).                                                                                                                                                   |
| `trackedChanges`    | Decides what happens to Word insertions and deletions: keep them as suggestions, apply them, or discard them. Defaults to `auto`, which chooses based on your schema. See [tracked changes](https://tiptap.dev/docs/conversion/import/docx/tracked-changes.md).                                                                        |
| `taskLists`         | Whether Word checkbox lists become `taskList` / `taskItem` nodes. `'auto'` (default) imports them when both nodes are in your editor schema, which is the case with `ConvertKit`. `'import'` forces it, `'ignore'` turns it off. See [Task lists](https://tiptap.dev/docs/conversion/content-types/text-and-formatting/task-lists.md). |

## Image upload configuration

The `imageUploadConfig` option lets you configure how the conversion service uploads images found in the DOCX file to your server. This replaces the deprecated `imageUploadCallbackUrl` string option and gives you full control over headers, HTTP method, and query parameters.

```ts
ImportDocx.configure({
  token: 'your-jwt',
  imageUploadConfig: {
    url: 'https://your-server.com/upload-image',
    headers: {
      Authorization: 'Bearer your-upload-token',
      'X-Custom-Header': 'custom-value',
    },
    method: 'PUT',
    queryParams: {
      bucket: 'images',
      folder: 'docx-imports',
    },
  },
})
```

| Property      | Type                     | Required | Description                                                                                                                                                                                                               |
| ------------- | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`         | `string`                 | Yes      | The endpoint URL that will receive the uploaded images. Must be a valid HTTP or HTTPS URL. Use HTTPS in production: the headers below are sent with every upload, so an HTTP endpoint exposes any credentials they carry. |
| `headers`     | `Record<string, string>` | No       | Additional HTTP headers forwarded by the convert service when uploading images. Useful for authentication (e.g., Bearer tokens, API keys). Use a plain object of header names to values.                                  |
| `method`      | `string`                 | No       | The HTTP method used to upload images. Defaults to `POST`.                                                                                                                                                                |
| `queryParams` | `Record<string, string>` | No       | Query parameters appended to the upload URL by the convert service.                                                                                                                                                       |

> **Migrating from imageUploadCallbackUrl:**
>
> The `imageUploadCallbackUrl` option is deprecated. Replace it with `imageUploadConfig`:
>
> ```ts
> // Before (deprecated)
> ImportDocx.configure({
>   imageUploadCallbackUrl: 'https://your-server.com/upload-image',
> })
>
> // After
> ImportDocx.configure({
>   imageUploadConfig: {
>     url: 'https://your-server.com/upload-image',
>   },
> })
> ```
>
> If both `imageUploadConfig` and `imageUploadCallbackUrl` are provided, `imageUploadConfig` takes precedence and a console warning is emitted.

## Import a DOCX file

Once the extension is configured, you can import a DOCX file selected by the user.

### Basic import

The simplest approach is to pass the file directly to the `importDocx` command. Here it replaces the current editor content with the converted content and focuses the editor:

```ts
editor.chain().focus().importDocx({ file }).run()
```

In most cases, this one-liner is all you need to let users import `.docx` files. The extension handles sending the file to the conversion endpoint, retrieving the converted Tiptap JSON, and inserting it into the editor.

### Import handling

In order to have more control after the import process have finished, you would use the `onImport` callback to handle the conversion result. This callback provides the converted content, any errors that occurred, and a function called `setEditorContent` to insert the content from `context.content` into the editor. If you don't provide an `onImport` callback, the extension will automatically insert the content into the editor but you won't be able to handle anything else like errors or loading states.

```ts
editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      // Discriminated union: handle the failure branch first.
      if (context.error) {
        showErrorToast({ message: context.error.message })
        return
      }

      const { setEditorContent, content } = context

      // You could modify the content before inserting it.
      content.content?.push({
        type: 'paragraph',
        content: [{ type: 'text', text: 'Hello!' }],
      })

      // You can change the loading state of your application for example
      isLoading = false

      // Insert the (possibly modified) content. Pass it to the context-provided
      // `setEditorContent` function, which does the right wiring around it
      // (including auto-applying headers/footers when Pages is registered).
      setEditorContent(content)
    },
  })
  .focus()
  .run()
```

Operations that we have controlled in the example above:

| Operation            | Description                                                                                                                                                                                                                                                                         |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Error Handling       | If the conversion fails, you can display a toast or log the error.                                                                                                                                                                                                                  |
| Content Modification | You can insert extra nodes, remove certain nodes, or otherwise adjust the converted Tiptap JSON as needed.                                                                                                                                                                          |
| Editor Insertion     | If you want to rely on the extension's default insertion behavior (replacing the editor content), you can call the `setEditorContent()` function provided in the callback. If you modify the content yourself, you must manually set it with `editor.commands.setContent(content)`. |

## Headers & Footers

When importing a `.docx` file that contains headers and footers, the import extension automatically detects and applies them if the [Pages extension](https://tiptap.dev/docs/pages/getting-started/overview.md) is installed. Without Pages, the header/footer data is still returned to your `onImport` callback (see [Manual handling](#manual-handling) below) but has nowhere to render. Install Pages to make them visible in the editor.

### Automatic handling

If the Pages extension is registered in your editor, headers and footers are applied automatically when you call `setEditorContent()`:

```ts
editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      if (context.error) {
        console.error(context.error)
        return
      }

      // Headers and footers are applied automatically alongside the body content
      context.setEditorContent()
    },
  })
  .focus()
  .run()
```

An import defines the whole document. A header, footer, footnote or endnote slot the file does not carry is cleared, so nothing from a previous import stays on the page. First page and odd/even variants are switched off when the file has none.

If the Pages extension is not installed, the header and footer data is still available in the `onImport` callback but is not automatically applied to the editor.

### Manual handling

The `onImport` callback provides all header and footer fields for manual processing:

```ts
editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      if (context.error) {
        console.error(context.error)
        return
      }

      // Access header/footer data directly
      const {
        header, // Default header (Tiptap JSON or null)
        footer, // Default footer (Tiptap JSON or null)
        headerFirstPage, // First page header (Tiptap JSON or null)
        footerFirstPage, // First page footer (Tiptap JSON or null)
        headerOdd, // Odd page header (Tiptap JSON or null)
        footerOdd, // Odd page footer (Tiptap JSON or null)
        headerEven, // Even page header (Tiptap JSON or null)
        footerEven, // Even page footer (Tiptap JSON or null)
      } = context

      // Set body content without automatic header/footer application
      editor.commands.setContent(context.content)

      // Apply headers and footers via Pages commands. Clear the slots the
      // file does not carry, so nothing from a previous import stays.
      if (header) editor.commands.setHeader(header)
      else editor.commands.unsetHeader()
      if (footer) editor.commands.setFooter(footer)
      else editor.commands.unsetFooter()

      editor.commands.setDifferentFirstPage(Boolean(headerFirstPage || footerFirstPage))
      if (headerFirstPage) editor.commands.setHeaderFirstPage(headerFirstPage)
      else editor.commands.unsetHeader('first')
      if (footerFirstPage) editor.commands.setFooterFirstPage(footerFirstPage)
      else editor.commands.unsetFooter('first')

      // Word shows the default header on odd pages, so the odd slot falls back to it
      const hasOddEven = Boolean(headerOdd || headerEven || footerOdd || footerEven)
      editor.commands.setDifferentOddEven(hasOddEven)
      const oddHeader = hasOddEven ? (headerOdd ?? header) : null
      const oddFooter = hasOddEven ? (footerOdd ?? footer) : null
      if (oddHeader) editor.commands.setHeaderOdd(oddHeader)
      else editor.commands.unsetHeader('odd')
      if (headerEven) editor.commands.setHeaderEven(headerEven)
      else editor.commands.unsetHeader('even')
      if (oddFooter) editor.commands.setFooterOdd(oddFooter)
      else editor.commands.unsetFooter('odd')
      if (footerEven) editor.commands.setFooterEven(footerEven)
      else editor.commands.unsetFooter('even')
    },
  })
  .focus()
  .run()
```

### Available fields

| Field             | Description                                                                        |
| ----------------- | ---------------------------------------------------------------------------------- |
| `header`          | Default header content as Tiptap JSON, or `null`                                   |
| `footer`          | Default footer content as Tiptap JSON, or `null`                                   |
| `headerFirstPage` | First page header (when "Different First Page" is enabled in Word), or `null`      |
| `footerFirstPage` | First page footer (when "Different First Page" is enabled in Word), or `null`      |
| `headerOdd`       | Odd page header (when "Different Odd & Even Pages" is enabled in Word), or `null`  |
| `footerOdd`       | Odd page footer (when "Different Odd & Even Pages" is enabled in Word), or `null`  |
| `headerEven`      | Even page header (when "Different Odd & Even Pages" is enabled in Word), or `null` |
| `footerEven`      | Even page footer (when "Different Odd & Even Pages" is enabled in Word), or `null` |

### Page-number fields

Word headers and footers commonly contain `PAGE` and `NUMPAGES` field codes that render as live page numbers. By default, when the [Pages extension](https://tiptap.dev/docs/pages/getting-started/overview.md) is installed, `importDocx` translates those fields into text tokens that match your active Pages registry (`{page}` and `{total}` out of the box) so they round-trip cleanly back to live Word fields on re-export with no extra config.

When you rename the built-ins, the importer forwards your registry to the convert service so it emits the same names:

```ts
import { Pages } from '@tiptap-pro/extension-pages'

Pages.configure({
  placeholders: { total: 'pages' },
})

// A Word `NUMPAGES` field now imports as `{pages}`, which the editor
// preview substitutes live and the export turns back into a `NUMPAGES`
// field.
```

This wiring is automatic: `importDocx` reads `Pages.options.placeholders` and forwards it to the convert service. You don't need to do anything in the `onImport` callback.

#### Disabling token translation

Pass `placeholders: false` on the import command to opt out. Word's cached numeric value flows through as plain text instead. This is the historical behavior, useful when no Pages extension is installed downstream:

```ts
editor.chain().importDocx({ file, placeholders: false }).run()
```

When `placeholders` is omitted, the command opts in by default whenever the Pages extension is installed, unless you've disabled token substitution with `Pages.configure({ placeholders: false })`, in which case it stays off. Pass `true` (or an explicit `{ page?, total? }` rename map) to force translation on regardless, with the rename map taking precedence over the editor's Pages config.

## Footnotes & Endnotes

The import API extracts footnote and endnote content from DOCX files. Inline references in the document body are represented as `footnoteReference` and `endnoteReference` nodes, each with a `noteId` attribute, and the note content is delivered keyed by those ids.

The `onImport` callback's `ImportContext` includes both data sets:

```js
editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      if (context.error) return

      context.footnotes // Record<noteId, JSONContent>, footnote content per id
      context.endnotes // Record<noteId, JSONContent>, endnote content per id

      context.setEditorContent(context.content)
    },
  })
  .run()
```

> **Auto-applied with Tiptap Pages:**
>
> When the [Pages extension](https://tiptap.dev/docs/pages/getting-started/overview.md) is installed with
> [footnotes](https://tiptap.dev/docs/pages/core-concepts/footnotes.md) and/or [endnotes](https://tiptap.dev/docs/pages/core-concepts/endnotes.md)
> enabled, imported notes are applied automatically: markers render in the body, footnote content
> lands in the per-page footnotes areas, endnote content lands in the document-end endnotes list,
> and numbering matches the source document. No extra wiring required.

Without Pages, the `footnoteReference` / `endnoteReference` nodes require a [custom extension](https://tiptap.dev/docs/editor/extensions/custom-extensions/extend-existing.md) to render in the editor. The data on `ImportContext` gives you everything needed to build your own presentation.

## Warnings and errors

Every import returns a `logs` object on the `onImport` context, holding `warn` and `error` arrays. A
warning means content did not make it into the document, so it is worth showing to whoever uploaded
the file.

```ts
editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      for (const warning of context.logs.warn) {
        console.warn(warning.message)
      }

      context.setEditorContent()
    },
  })
  .run()
```

The commonest warning is an image that could not be imported, which usually means no
`imageUploadConfig` was set. See [Image handling](https://tiptap.dev/docs/conversion/import/docx/image-handling.md).

## Support & Limitations

For a detailed breakdown of which document features are supported at each stage of the pipeline, see the [Supported features](https://tiptap.dev/docs/conversion/getting-started/feature-support-matrix.md) matrix.
