ConvertKit: DOCX-aware editor extensions
Beta feature
ConvertKit's APIs, attribute names, and bundled extensions are stabilising but may still see refinements before general availability. Pin exact package versions if you depend on ConvertKit, and read the What won't work section before you adopt it.
What this does
A DOCX file carries formatting metadata that the stock Tiptap extensions do not know about: paragraph spacing in twips, table-row height rules, image crop percentages, vertical cell alignment, per-side border widths. If you render DOCX content with vanilla Tiptap, that information is lost or mis-rendered.
ConvertKit is a single extension that configures your editor for DOCX-imported content. Drop it in and you get:
- 28 bundled extensions wired up to handle everything the Convert API produces.
- Custom Paragraph, Heading, Image, Table, TableRow, TableCell, and TableHeader extensions that accept the DOCX-specific attributes.
- A small CSS reset injected at editor creation that normalises browser defaults so DOCX spacing values render accurately.
- Every bundled extension is individually configurable or disableable.
Install
npm i @tiptap-pro/extension-convert-kit@^0.1.0How it works
Three parts:
- A bundle of pre-configured extensions. ConvertKit registers a curated set of Tiptap extensions (base extensions for marks and lists, plus DOCX-aware custom overrides for paragraphs, headings, images, and tables). You can pass
falsefor any slot inConvertKit.configure({…})to exclude that extension. - DOCX attributes on schema nodes. The DOCX-aware overrides extend their base counterparts and declare additional attributes (paragraph spacing, image crop, table cell properties). The conversion service produces these attributes; ConvertKit's nodes render them.
- Scoped CSS reset. ConvertKit injects a small CSS reset that normalises browser defaults for paragraph margins, table line-heights, cell vertical-alignment, and exact-height rows so DOCX spacing values render accurately. The reset is removed when the editor is destroyed.
import { Editor } from '@tiptap/core'
import { ConvertKit } from '@tiptap-pro/extension-convert-kit'
const editor = new Editor({
extensions: [ConvertKit],
})Customise or disable specific children:
new Editor({
extensions: [
ConvertKit.configure({
heading: { levels: [1, 2, 3] }, // only allow H1–H3
codeBlock: false, // drop the code-block node entirely
table: false, // swap for a different table stack
}),
],
})What works
Bundled extensions
ConvertKit ships 28 extensions inside the single @tiptap-pro/extension-convert-kit package. The list below is reference material so you know what shows up in the editor's schema; you do not install or register these separately.
| Category | Extensions |
|---|---|
| Core | Document, Text, Paragraph, Heading |
| Marks | Bold, Italic, Underline, Strike, Code, Link |
| Blocks | Blockquote, HorizontalRule, HardBreak, CodeBlock, PageBreak |
| Lists | BulletList, OrderedList, ListItem, ListKeymap |
| Media | Image* |
| Styling | TextStyleKit (TextStyle, Color, BackgroundColor, FontFamily, FontSize, LineHeight), TextAlign, Highlight (multicolor), Superscript, Subscript |
| UI helpers | Dropcursor, Gapcursor |
| Tables | ConvertTableKit (Table*, TableRow*, TableCell*, TableHeader*) |
Extensions marked with * are custom, DOCX-aware overrides of the Tiptap base. See the next section for what they add.
DOCX attributes added on top of Tiptap base extensions
| Extension | Added attributes | Source |
|---|---|---|
Paragraph | spacingBefore, spacingAfter, lineHeight, fontSize, indent, firstLineIndent, contextualSpacing | DOCX w:pPr + w:rPr on paragraph mark |
Heading | same as Paragraph | DOCX w:pPr + w:rPr on paragraph mark |
Image | cropTop, cropBottom, cropLeft, cropRight | DOCX a:srcRect on drawings |
Table | width, indent (+ cellMinWidth: 1 default, vs Tiptap's 25) | DOCX w:tblW, w:tblInd, w:tblGrid |
TableRow | height, heightRule (exact / atLeast) | DOCX w:trHeight + w:hRule |
TableCell / TableHeader | background, verticalAlign, per-side border (width / style / color on top/bottom/left/right) | DOCX w:tcPr |
What the CSS reset normalises
The reset is scoped to .tiptap and adjusts a few areas where browser defaults would otherwise misalign with DOCX spacing:
- Paragraph spacing. Top and bottom margins on paragraphs and on
*descendants are zeroed so DOCX-authored spacing values (which carry their own before/after values) render accurately rather than being added on top of the browser's default margins. - Table line-height and cell padding. Defaults are tightened so a table that declared specific row heights actually renders at those heights.
- Exact-height rows clip overflowing content. Rows with
heightRule: "exact"clip via the inner cell wrapper; content taller than the declared row height is hidden, matching Word's behaviour. - Trailing-break artefacts. ProseMirror's trailing break gets zero margin and padding so it doesn't introduce phantom space at the end of a paragraph.
If you need to override any of this, target your own selectors inside .tiptap — your rules win against the reset.
Small-but-important defaults
cellMinWidth: 1on Table. Tiptap defaults to25, which inflates DOCX spacer columns (1–15 px narrow columns used for layout) into visible gaps. ConvertKit caps the minimum at1px.textAlignconfigured withtypes: ['paragraph', 'heading']so DOCX paragraph justification flows through both blocks.highlightconfigured withmulticolor: trueso DOCXw:highlightcolours come through as-is.
What won't work
These are intentional limits
These are not bugs. They are explicit, documented boundaries of the feature. If your workflow depends on any of the items below, this feature is not the right tool.
- Floating tables. DOCX
w:tblpPr(tables positioned relative to an anchor with text wrapping) render as plain stacked inline tables. The positioning metadata is dropped by the converter. This is intentional; float layout fidelity is out of scope. - Negative table indents.
w:tblIndvalues below zero are preserved inattrs.indentbut clamped to0at render time. Word renders negative indents into the paper gutter; browsers have no gutter, so rendering them would push the table past the page edge. - Exact-height row overflow. Rows with
heightRule: "exact"clip overflowing content via the.cell-contentwrapper'soverflow: hidden. Content taller than the declared height is not visible. - Over-wide table widths. Tables whose declared
widthexceeds the container render withmax-width: 100%. The declared width is preserved inattrs.widthbut visually clamped. - Multi-page layout. ConvertKit does not paginate. If you need tables that span pages, an actual page container, headers/footers, or page breaks to be respected visually, pair ConvertKit with the Pages extension and use PagesTableKit for the table extensions.
- Footnotes, endnotes, comments. Not rendered. The converter emits them as paragraphs or drops them; ConvertKit does not add any footnote/comment UI.
What to expect
- A small, scoped CSS reset is applied while the editor is mounted and removed when the editor is destroyed. Multiple ConvertKit-powered editors on the same page share the reset (it's inserted once).
- A consistent render of DOCX-imported content across your app. Images with non-zero DOCX crops actually render cropped; rows with
heightRule: "atLeast"grow vertically when their columns shrink; cells withverticalAlign: "center"centre their content. - Full compatibility with the rest of the Tiptap Pro extension ecosystem.
@tiptap-pro/extension-import-docxand@tiptap-pro/extension-export-docxare the natural siblings;@tiptap-pro/extension-pages-tablekitstacks on top if you need pagination. - Tight default typography. The CSS reset zeroes paragraph margins and applies tight line-heights. If you're using ConvertKit for content that wasn't imported from DOCX, layer your own typography rules on top — the reset is intentional, not a default we expect to be visually polished.
What not to expect
- DOCX fidelity beyond the node-attr level. ConvertKit wires up the attributes the Convert API produces. If the converter doesn't parse a given DOCX feature, ConvertKit can't render it.
- Automatic resize handles on tables. ConvertKit's Table extension inherits from
@tiptap/extension-tableand supports the standard resize behaviour, but the defaultcellMinWidth: 1means handles can drag columns down to 1 px wide, which is usually not what you want for hand-editing. Overridetable.cellMinWidthwhen mixing DOCX imports with hand authoring.
What ConvertKit does not cover
ConvertKit handles every feature the Convert API produces as Tiptap nodes and marks. Some imported DOCX content sits outside that scope and still needs handling at the application level:
- Footnote and endnote references. The import REST API returns footnote and endnote data, but the editor extension does not surface it and there is no Tiptap extension that renders footnote markers or notes. See Footnotes and endnotes.
- Multi-column layouts. The import produces
columnsandcolumnnodes for multi-column sections, but there is no editor extension to render them. See Page structure. - Table of contents. Imported as a
tableOfContentsnode. Renders as plain content unless you add a custom extension. See Supported features. - Word styles. Style names are used during import for structural detection (headings, blockquotes, code blocks). The visual styling defined in the document's style catalog is not applied. Pair ConvertKit with CSS injection to bring the style catalog through, or see Word styles.
- Math equations. OMML is not parsed by the Convert API. See Math equations.
- Letter spacing. The Convert API extracts character spacing as
textStyle.letterSpacingon per-run marks, but the bundledTextStyleKitdoes not declare aletterSpacingattribute, so the value is dropped on render. CSS injection does not extract it either (only export-side compilation accepts it). To render imported letter spacing, extendTextStylewith aletterSpacingattribute that emitsstyle="letter-spacing: …"inrenderHTML. See Font family and size. - Page layout. Page size, margins, headers, and footers are document-level concerns. ConvertKit renders content; pair it with the Pages extension for paginated layout.
For guidance on extending ConvertKit's bundled extensions and styling converted content with CSS, see the Styling converted content guide.
Configuration reference
Every key is optional. Pass false to exclude an extension. Pass a partial options object to forward to the underlying extension's configure(). Defaults for each key are {}.
ConvertKit.configure({
document: Record<string, never> | false,
text: Record<string, never> | false,
paragraph: Partial<ParagraphOptions> | false,
heading: Partial<HeadingOptions> | false,
blockquote: Partial<BlockquoteOptions> | false,
bold: Partial<BoldOptions> | false,
italic: Partial<ItalicOptions> | false,
underline: Partial<UnderlineOptions> | false,
strike: Partial<StrikeOptions> | false,
code: Partial<CodeOptions> | false,
link: Partial<LinkOptions> | false,
bulletList: Partial<BulletListOptions> | false,
orderedList: Partial<OrderedListOptions> | false,
listItem: Partial<ListItemOptions> | false,
listKeymap: Partial<ListKeymapOptions> | false,
hardBreak: Partial<HardBreakOptions> | false,
horizontalRule: Partial<HorizontalRuleOptions> | false,
dropcursor: Partial<DropcursorOptions> | false,
gapcursor: Record<string, never> | false,
codeBlock: Partial<CodeBlockOptions> | false,
image: Partial<ImageOptions> | false,
textStyleKit: Partial<TextStyleKitOptions> | false,
textAlign: Partial<TextAlignOptions> | false, // default: { types: ['paragraph', 'heading'] }
highlight: Partial<HighlightOptions> | false, // default: { multicolor: true }
superscript: Partial<SuperscriptExtensionOptions> | false,
subscript: Partial<SubscriptExtensionOptions> | false,
pageBreak: Partial<PageBreakOptions> | false,
table: Partial<ConvertTableKitOptions> | false,
})Related
- PagesTableKit: companion package that wraps ConvertKit's Table extensions in a CSS-Grid NodeView compatible with the Pages extension's pagination.
- CSS injection (import): pair ConvertKit with CSS injection to get typographic fidelity from the DOCX style catalog.
- Editor extension (import DOCX): how the
ImportDocxextension calls into the Convert API.