Extend your PDF export with Headers & Footers

Available in Start planBetav0.3.0

The @tiptap-pro/extension-export-pdf extension includes built-in support for customizing the headers and footers of the exported document. You can configure different headers and footers for the first page, odd pages, and even pages.

Each header and footer slot (default, first, even) accepts one of the shapes below. Mix and match freely across slots; pick whichever matches your data.

ShapeExampleNotes
Plain text'Company name, 2026'Rendered as an unstyled header/footer.
Stringified Tiptap JSONContentJSON.stringify(myHeaderDoc)Pre-serialized Tiptap node. Rich formatting (bold, italic, links, etc.) is preserved on render.
Tiptap JSONContent object{ type: 'doc', content: [/* … */] }A Tiptap node passed directly. Same fidelity as stringified JSONContent, but no JSON.stringify step.
docx Header / Footernew Docx.Header({ children: [/* … */] })Full DOCX-level control over paragraphs and runs. Requires @tiptap-pro/extension-export-docx.
Async factory returning Header / Footer() => convertHeader({ node })Built on demand when the export runs. Typical use: convertHeader / convertFooter from the DOCX extension.

Tiptap JSONContent objects must include a type field. The extension uses it at runtime to distinguish a Tiptap node from a docx instance. Objects without type that aren't Header / Footer instances are dropped with a console.warn naming the offending value; they're never forwarded to the DOCX serializer.

In TypeScript these collapse to two aliases (the type requirement is encoded in TiptapNodeContent):

import type { JSONContent } from '@tiptap/core'
import type { Footer, Header } from 'docx'

type TiptapNodeContent = JSONContent & { type: string }

type HeaderSlotValue = string | TiptapNodeContent | Header | (() => Promise<Header>)
type FooterSlotValue = string | TiptapNodeContent | Footer | (() => Promise<Footer>)

The first three shapes work on their own with @tiptap-pro/extension-export-pdf. The Header / Footer instance and async-factory shapes require @tiptap-pro/extension-export-docx to be installed; they take the DOCX-first export path, which is loaded on demand so PDF-only consumers aren't affected.

Headers Configuration

The headers object configures the running header. Each slot accepts a header slot value.

PropertyTypeDescription
evenAndOddHeadersbooleanWhether to use different headers for odd and even pages.
differentFirstPagebooleanWhether to use a different header on the first page. When true, the first value is used on page one instead of default.
defaultHeaderSlotValueThe standard default header on every page, or the header on odd pages when evenAndOddHeaders is active.
firstHeaderSlotValueThe header on the first page. Only used when differentFirstPage is true.
evenHeaderSlotValueThe header on even pages. Only used when evenAndOddHeaders is true.

Footers Configuration

The footers object mirrors headers. Each slot accepts a footer slot value.

PropertyTypeDescription
evenAndOddFootersbooleanWhether to use different footers for odd and even pages.
differentFirstPagebooleanWhether to use a different footer on the first page. When true, the first value is used on page one instead of default.
defaultFooterSlotValueThe standard default footer on every page, or the footer on odd pages when evenAndOddFooters is active.
firstFooterSlotValueThe footer on the first page. Only used when differentFirstPage is true.
evenFooterSlotValueThe footer on even pages. Only used when evenAndOddFooters is true.

Complete example

import { ExportPdf } from '@tiptap-pro/extension-export-pdf'

const editor = new Editor({
  extensions: [
    // Other extensions...
    ExportPdf.configure({
      token: 'YOUR_TOKEN',
      appId: 'YOUR_APP_ID',
      headers: {
        evenAndOddHeaders: true,
        default: 'My Document - Confidential',
        first: 'Welcome to My Document',
        even: 'My Document - Even Page',
      },
      footers: {
        evenAndOddFooters: true,
        default: 'Company Name - All Rights Reserved',
        first: 'Draft Version 1.0',
        even: 'Company Name - Even Page Footer',
      },
    }),
    // Other extensions...
  ],
})

// Export with headers and footers
editor
  .chain()
  .exportPdf({
    onCompleteExport(result) {
      const url = URL.createObjectURL(result)
      const a = document.createElement('a')

      a.href = url
      a.download = 'document-with-headers.pdf'
      a.click()

      URL.revokeObjectURL(url)
    },
  })
  .run()

Simple headers and footers

If you don't need different headers for odd/even pages, you can provide just the default values:

ExportPdf.configure({
  token: 'YOUR_TOKEN',
  appId: 'YOUR_APP_ID',
  headers: {
    default: 'My Document Title',
  },
  footers: {
    default: 'Page Footer - Company Name',
  },
})

Rich headers via Tiptap JSONContent

Pass a Tiptap JSONContent node directly on any slot. This is useful when you already have the node in memory and don't want to build a Docx.Header by hand:

ExportPdf.configure({
  token: 'YOUR_TOKEN',
  appId: 'YOUR_APP_ID',
  headers: {
    default: {
      type: 'doc',
      content: [
        {
          type: 'paragraph',
          content: [
            { type: 'text', marks: [{ type: 'bold' }], text: 'My Document' },
            { type: 'text', text: ' · Confidential' },
          ],
        },
      ],
    },
  },
})

For full control over paragraph properties, alignment, and run styles you can pass a docx Header / Footer instance (or an async factory that returns one). This requires @tiptap-pro/extension-export-docx to be installed alongside the PDF extension:

import { Docx, convertHeader } from '@tiptap-pro/extension-export-docx'

ExportPdf.configure({
  token: 'YOUR_TOKEN',
  appId: 'YOUR_APP_ID',
  headers: {
    // Direct Docx.Header instance
    default: new Docx.Header({
      children: [
        new Docx.Paragraph({
          children: [new Docx.TextRun({ text: 'My Document' })],
        }),
      ],
    }),
    // Async factory: convert a Tiptap node to a Docx.Header on demand
    first: () =>
      convertHeader({
        node: {
          type: 'doc',
          content: [
            { type: 'paragraph', content: [{ type: 'text', text: 'Welcome' }] },
          ],
        },
      }),
  },
})