---
title: "Extend your DOCX export with Headers & Footers"
description: "Learn how to extend your DOCX exports with Headers & Footers"
canonical_url: "https://tiptap.dev/docs/conversion/export/docx/headers-footers"
---

# Extend your DOCX export with Headers & Footers

Learn how to extend your DOCX exports with Headers & Footers

- **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).

The `@tiptap-pro/extension-export-docx` extension includes built-in support for customizing the headers and footers of the exported document.

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

When adding this extension it will allow you to configure your `ExportDocx` with some additional properties:

## Headers Configuration

The `headers` object allows you to customize the headers of your exported DOCX document:

| Property             | Type                                | Description                                                                                                                                                                               |
| -------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `evenAndOddHeaders`  | `boolean`                           | Whether to use different headers for odd and even pages                                                                                                                                   |
| `differentFirstPage` | `boolean`                           | Whether to use a different header on the first page. When `true`, the `first` value is used on page one instead of `default`.                                                             |
| `default`            | `Header \| (() => Promise<Header>)` | The standard default header on every page, or header on odd pages when the `evenAndOddHeaders` option is activated. Can be a `Header` object or an async function that returns a `Header` |
| `first`              | `Header \| (() => Promise<Header>)` | The header on the first page. Only used when `differentFirstPage` is set to `true`. Can be a `Header` object or an async function that returns a `Header`                                 |
| `even`               | `Header \| (() => Promise<Header>)` | The header on even pages when the `evenAndOddHeaders` option is activated. Can be a `Header` object or an async function that returns a `Header`                                          |

## Footers Configuration

The `footers` object allows you to customize the footers of your exported DOCX document:

| Property             | Type                                | Description                                                                                                                                                                               |
| -------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `evenAndOddFooters`  | `boolean`                           | Whether to use different footers for odd and even pages                                                                                                                                   |
| `differentFirstPage` | `boolean`                           | Whether to use a different footer on the first page. When `true`, the `first` value is used on page one instead of `default`.                                                             |
| `default`            | `Footer \| (() => Promise<Footer>)` | The standard default footer on every page, or footer on odd pages when the `evenAndOddFooters` option is activated. Can be a `Footer` object or an async function that returns a `Footer` |
| `first`              | `Footer \| (() => Promise<Footer>)` | The footer on the first page. Only used when `differentFirstPage` is set to `true`. Can be a `Footer` object or an async function that returns a `Footer`                                 |
| `even`               | `Footer \| (() => Promise<Footer>)` | The footer on even pages when the `evenAndOddFooters` option is activated. Can be a `Footer` object or an async function that returns a `Footer`                                          |

# Usage Examples

## Extension Configuration

Headers and footers are configured through the `ExportDocx.configure()` method. You can use direct `Docx` namespace objects like `Docx.Header` and `Docx.Footer` for full control:

> **Note about Header and Footer Objects:**
>
> The `Header`, `Footer`, `Paragraph`, and `TextRun` objects are accessed through the `Docx` namespace exported from `@tiptap-pro/extension-export-docx`. You can customize them with any valid content including paragraphs, text runs, and more if available within the standard elements that can be used within a DOCX header or footer.

```javascript
import { ExportDocx, Docx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      headers: {
        evenAndOddHeaders: true,
        default: new Docx.Header({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  text: "Default Header",
                  bold: true,
                }),
              ],
            }),
          ],
        }),
        first: new Docx.Header({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  text: "First Page Header",
                  size: 24,
                  bold: true,
                }),
              ],
            }),
          ],
        }),
        even: new Docx.Header({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  text: "Even Page Header",
                }),
              ],
            }),
          ],
        }),
      },
      footers: {
        default: new Docx.Footer({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  text: "Default Footer",
                }),
              ],
            }),
          ],
        }),
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```

### Using Helper Functions (Alternative Approach)

For easier handling of Tiptap-style content, you can use the `convertHeader` and `convertFooter` helper functions that automatically handle mark conversion, links, and other Tiptap features.

```javascript
import { ExportDocx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      headers: {
        evenAndOddHeaders: true,
        default: async () =>
          convertHeader({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Header',
                  marks: [{ type: 'textStyle', attrs: { color: 'red' } }],
                },
              ],
            },
          }),
        first: async () =>
          convertHeader({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'First Page Header',
                  marks: [{ type: 'bold' }],
                },
              ],
            },
          }),
        even: async () =>
          convertHeader({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Even Page Header',
                  marks: [{ type: 'textStyle', attrs: { color: 'blue' } }],
                },
              ],
            },
          }),
      },
      footers: {
        default: async () =>
          convertFooter({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Footer',
                  marks: [{ type: 'textStyle', attrs: { color: 'red' } }],
                },
              ],
            },
          }),
        first: async () =>
          convertFooter({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'First Page Footer',
                  marks: [{ type: 'bold' }],
                },
              ],
            },
          }),
        even: async () =>
          convertFooter({
            node: {
              type: 'paragraph',
              content: [
                {
                  type: 'text',
                  text: 'Even Page Footer',
                  marks: [{ type: 'textStyle', attrs: { color: 'blue' } }],
                },
              ],
            },
          }),
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```

The `convertHeader` and `convertFooter` helper functions expect a Tiptap node, which in this case it's a paragraph, and they will automatically handle:

- Mark conversion (bold, italic, colors, etc.)
- Link conversion
- Text styling and formatting
- Complex Tiptap node structures

This makes it much easier to create rich headers and footers using familiar Tiptap JSON structure.

> **Important note!:**
>
> Be aware that if you use the `convertHeader` and `convertFooter` helpers you'd need to then use an asynchronous arrow functions as this `convertHeader` and `convertFooter` utility functions internally call `convertParagraph` helper which is an asynchronous function due to our image resolution implementation to handle images.

## Advanced Examples

### Dynamic Headers and Footers with Async Functions

You can also provide asynchronous functions that return `Header` or `Footer` objects. This is useful for dynamic content generation, such as fetching user data, formatting current dates, or processing images.

> **Async Function Benefits:**
>
> Using async functions for headers and footers enables powerful use cases like: Fetching user information from APIs, including current timestamps or dynamic dates, processing and embedding images, calculating document statistics, retrieving data from databases or external services.

```javascript
import { ExportDocx, Docx } from '@tiptap-pro/extension-export-docx'

// Function to fetch user information
async function getCurrentUser() {
  // This could be an API call, database query, etc.
  return {
    name: 'John Doe',
    department: 'Engineering',
    email: 'john.doe@company.com'
  }
}

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      headers: {
        default: async () => {
          const user = await getCurrentUser()
          const currentDate = new Date().toLocaleDateString()
          
          return new Docx.Header({
            children: [
              new Docx.Paragraph({
                children: [
                  new Docx.TextRun({
                    text: `Document prepared by: ${user.name} (${user.department}) on ${currentDate}`,
                    size: 20,
                  }),
                ],
              }),
            ],
          })
        },
      },
      footers: {
        default: async () => {
          const user = await getCurrentUser()
          
          return new Docx.Footer({
            children: [
              new Docx.Paragraph({
                children: [
                  new Docx.TextRun({
                    text: `Contact: ${user.email} | Generated: ${new Date().toISOString()}`,
                    size: 16,
                    italics: true,
                  }),
                ],
              }),
            ],
          })
        },
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```

> **Async function lifecycle:**
>
> The asynchronous functions provided will be called after the entire document conversion has been processed, right before constructing the document.

### Mixed Static and Dynamic Headers/Footers

You can combine static headers/footers with dynamic ones for different pages:

```javascript
import { ExportDocx, Docx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      headers: {
        // Static first page header
        first: new Docx.Header({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  text: "Company Confidential Report",
                  size: 24,
                  bold: true,
                }),
              ],
            }),
          ],
        }),
        // Dynamic default header for subsequent pages
        default: async () => {
          const reportData = await fetchReportMetadata()
          
          return new Docx.Header({
            children: [
              new Docx.Paragraph({
                children: [
                  new Docx.TextRun({
                    text: `${reportData.title} - Generated ${reportData.timestamp}`,
                    size: 18,
                  }),
                ],
              }),
            ],
          })
        },
      },
      footers: {
        default: async () => {
          const stats = await getDocumentStats()
          
          return new Docx.Footer({
            children: [
              new Docx.Paragraph({
                children: [
                  new Docx.TextRun({
                    text: `Page Count: ${stats.pages} | Word Count: ${stats.words}`,
                    size: 14,
                  }),
                ],
              }),
            ],
          })
        },
      },
    }),
  ],
})

async function fetchReportMetadata() {
  // Simulate API call
  return {
    title: 'Quarterly Business Report',
    timestamp: new Date().toLocaleDateString()
  }
}

async function getDocumentStats() {
  // Simulate document analysis
  return {
    pages: 10,
    words: 2500
  }
}
```

## Page Numbering

The export supports two ways to emit live page-number fields:

1. **`{page}` / `{total}` tokens in plain-text headers and footers** — works automatically with content extracted from the [Pages extension](https://tiptap.dev/docs/pages/getting-started/overview.md) and with plain-text strings passed to `headers` / `footers`. The export converts each token to a live `PAGE` / `NUMPAGES` field, so Word renders the actual current page and total page count.
2. **The `Docx.PageNumber` API** — required when you build header/footer content manually with `new Docx.Header()` / `new Docx.Footer()`, or when you need formatting around the field that goes beyond the bare token. The `convertHeader` / `convertFooter` helpers do not currently process `Docx.PageNumber` references — pass the `Docx` instance directly.

### Page numbers via `{page}` / `{total}` tokens

Any plain-text header or footer (including those auto-extracted from the Pages extension via `editor.commands.setHeader('Page {page} of {total}')`) renders as live page-number fields in the exported DOCX:

```javascript
import { ExportDocx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      // Plain-text headers/footers — `{page}` and `{total}` are converted
      // to live `PAGE` / `NUMPAGES` fields on export.
      headers: {
        default: 'Confidential — Page {page} of {total}',
      },
      footers: {
        default: 'Page {page} of {total}',
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```

The same conversion runs automatically when headers/footers are auto-extracted from the Pages extension, so a footer set via `editor.commands.setFooter('Page {page} of {total}')` exports with live page numbers without any extra configuration.

### Custom token names

If your editor renames the built-in tokens via `Pages.configure({ placeholders: { total: 'pages' } })` (see [Pages → renaming placeholders](https://tiptap.dev/docs/pages/core-concepts/page-header-footer.md#renaming-or-disabling-placeholders)), the export honors the same token names — `{pages}` becomes a live `NUMPAGES` field, and the original `{total}` stops being recognized. There is no `placeholders` option on `ExportDocx` itself; configuring on Pages keeps the editor preview and the exported `.docx` consistent.

When calling `convertHeader` / `convertFooter` directly (for example, from server-side code without an editor), you can pass `placeholders` explicitly:

```javascript
import { convertFooter } from '@tiptap-pro/extension-export-docx'

const footer = await convertFooter({
  node: { type: 'paragraph', content: [{ type: 'text', text: 'Page {page} of {pages}' }] },
  placeholders: { total: 'pages' },
})
```

Pass `placeholders: false` to disable substitution and emit the tokens as literal text. Omitting `placeholders` entirely on a direct `convertHeader` / `convertFooter` call also disables substitution — you opt in explicitly.

### Basic Page Numbers

```javascript
import { ExportDocx, Docx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      headers: {
        default: new Docx.Header({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  children: ['Page ', Docx.PageNumber.CURRENT],
                }),
              ],
            }),
          ],
        }),
      },
      footers: {
        default: new Docx.Footer({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  children: ['Page ', Docx.PageNumber.CURRENT],
                }),
              ],
            }),
          ],
        }),
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```

### Page Numbers with Total Pages

```javascript
import { ExportDocx, Docx } from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // ... other extensions
    ExportDocx.configure({
      onCompleteExport: result => {
        // Handle the export result
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      footers: {
        default: new Docx.Footer({
          children: [
            new Docx.Paragraph({
              children: [
                new Docx.TextRun({
                  children: [
                    'Page ',
                    Docx.PageNumber.CURRENT,
                    ' of ',
                    Docx.PageNumber.TOTAL_PAGES,
                  ],
                }),
              ],
            }),
          ],
        }),
      },
    }),
  ],
})

// Trigger export
editor.commands.exportDocx()
```
