Export custom nodes to .pdf
The @tiptap-pro/extension-export-pdf extension supports custom node conversion, allowing you to define how custom nodes in your Tiptap schema should be rendered in PDF exports.
Requires Export DOCX extension
Custom node conversion for PDF export requires the @tiptap-pro/extension-export-docx extension to be installed and configured in your editor. The PDF export uses DOCX generation under the hood before converting to PDF. Without it, the customNodes option will have no effect.
Export custom nodes to .pdf
Pass your custom node definitions via the customNodes option when configuring ExportPdf:
import { ExportPdf } from '@tiptap-pro/extension-export-pdf'
import { ExportDocx } from '@tiptap-pro/extension-export-docx'
const editor = new Editor({
extensions: [
ExportDocx,
ExportPdf.configure({
token: 'YOUR_TOKEN',
appId: 'YOUR_APP_ID',
customNodes: [
{
type: 'hintbox',
render: node => {
// Define how your custom node renders in the document
},
},
],
onCompleteExport(result) {
// Handle the exported PDF
},
}),
],
})Custom node conventions
Custom node converters must adhere to the underlying DOCX generation library's requirements. In practice, a custom converter function for DOCX should return one of the allowed DOCX elements for that node: a Paragraph class (or an array of Paragraph classes), a Table class, a TextRun class, an ExternalHyperlink class, or null if the node should be skipped in the output.
Define the custom node extension
For the sake of the example, suppose your editor has a custom node type hintbox (a callout-styled box). You can define how it should appear in the exported document.
Here's how the Hintbox extension's custom node might look like:
import { mergeAttributes, Node } from '@tiptap/core'
export interface ParagraphOptions {
/**
* The HTML attributes for a paragraph node.
* @default {}
* @example { class: 'foo' }
*/
HTMLAttributes: Record<string, any>
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
hintbox: {
/**
* Set a hintbox
* @example editor.commands.setHintbox()
*/
setHintbox: () => ReturnType
/**
* Toggle a hintbox
* @example editor.commands.toggleHintbox()
*/
toggleHintbox: () => ReturnType
}
}
}
/**
* This extension allows you to create hintboxes.
* @see https://www.tiptap.dev/api/nodes/paragraph
*/
export const Hintbox = Node.create<ParagraphOptions>({
name: 'hintbox',
priority: 1000,
addOptions() {
return {
HTMLAttributes: {
style: 'padding: 20px; border: 1px solid #b8d8ff; border-radius: 5px; background-color: #e6f3ff;',
},
}
},
group: 'block',
content: 'inline*',
parseHTML() {
return [{ tag: 'p' }]
},
renderHTML({ HTMLAttributes }) {
return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
},
addCommands() {
return {
setHintbox:
() =>
({ commands }) => {
return commands.setNode(this.name)
},
toggleHintbox:
() =>
({ commands }) => {
return commands.toggleNode(this.name, 'paragraph')
},
}
},
addKeyboardShortcuts() {
return {
'Mod-Alt-h': () => this.editor.commands.toggleHintbox(),
}
},
})Define the custom node render function
And we will define how the Hintbox custom node should be rendered in the exported document:
import { ExportPdf } from '@tiptap-pro/extension-export-pdf'
import {
convertTextNode,
Docx,
ExportDocx,
lineHeightToDocx,
pixelsToHalfPoints,
pointsToTwips,
} from '@tiptap-pro/extension-export-docx'
const editor = new Editor({
extensions: [
// Other extensions ...
ExportDocx,
ExportPdf.configure({
token: 'YOUR_TOKEN',
appId: 'YOUR_APP_ID',
onCompleteExport: result => {
const url = URL.createObjectURL(result)
const a = document.createElement('a')
a.href = url
a.download = 'export.pdf'
a.click()
URL.revokeObjectURL(url)
},
customNodes: [
{
type: 'hintbox',
render: node => {
return new Docx.Paragraph({
children: node.content.map(content => convertTextNode(content)),
style: 'Hintbox',
})
},
},
],
styleOverrides: {
paragraphStyles: [
{
id: 'Hintbox',
name: 'Hintbox',
basedOn: 'Normal',
next: 'Normal',
quickFormat: false,
run: {
font: 'Aptos Light',
size: pixelsToHalfPoints(16),
},
paragraph: {
spacing: {
before: pointsToTwips(12),
after: pointsToTwips(12),
line: lineHeightToDocx(1),
},
border: {
top: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
bottom: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
right: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
left: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
},
shading: {
type: Docx.ShadingType.SOLID,
color: 'e6f3ff',
},
},
},
],
},
}),
// Other extensions ...
],
})You can construct any supported DOCX elements in the render function using the Docx library classes (Paragraph, TextRun, Table, etc.) that are provided via the Docx import from the @tiptap-pro/extension-export-docx package.