Overview
The union function merges multiple extensions into a single extension. This is the primary way to compose extensions in ProseKit.
Function Signature
function union<const E extends readonly Extension[]>(
...exts: E
): Union<E>
function union<const E extends readonly Extension[]>(
exts: E
): Union<E>
Parameters
exts
Extension[] | ...Extension[]
required
The extensions to merge. Can be provided as multiple arguments or as a single array.
Return Value
A unified extension that combines all nodes, marks, and commands from the input extensions. The return type preserves the type information of all input extensions.
Examples
Basic Usage with Multiple Arguments
import { union } from 'prosekit/core'
import { defineBold, defineItalic } from 'prosekit/extensions/mark'
import { defineParagraph, defineDoc } from 'prosekit/extensions/node'
const extension = union(
defineDoc(),
defineParagraph(),
defineBold(),
defineItalic()
)
Using Array Syntax
import { union } from 'prosekit/core'
import { defineBold, defineItalic } from 'prosekit/extensions/mark'
const marks = [
defineBold(),
defineItalic()
]
const extension = union(marks)
Composing Extension Groups
import { union } from 'prosekit/core'
import { defineParagraph, defineHeading } from 'prosekit/extensions/node'
import { defineBold, defineItalic, defineUnderline } from 'prosekit/extensions/mark'
// Define groups of related extensions
function defineBlockNodes() {
return union(
defineParagraph(),
defineHeading()
)
}
function defineTextMarks() {
return union(
defineBold(),
defineItalic(),
defineUnderline()
)
}
// Combine the groups
const extension = union(
defineBlockNodes(),
defineTextMarks()
)
Building Reusable Extension Presets
import { union } from 'prosekit/core'
import {
defineDoc,
defineParagraph,
defineText,
defineHeading,
defineCodeBlock
} from 'prosekit/extensions/node'
import {
defineBold,
defineItalic,
defineCode
} from 'prosekit/extensions/mark'
export function defineBasicExtension() {
return union(
defineDoc(),
defineParagraph(),
defineText(),
defineBold(),
defineItalic()
)
}
export function defineAdvancedExtension() {
return union(
defineBasicExtension(),
defineHeading(),
defineCodeBlock(),
defineCode()
)
}
Type-Safe Extension Merging
import { createEditor, union } from 'prosekit/core'
import { defineBold, defineItalic } from 'prosekit/extensions/mark'
const extension = union(
defineBold(),
defineItalic()
)
const editor = createEditor({ extension })
// TypeScript knows both marks are available
editor.marks.bold.create()
editor.marks.italic.create()
// The union type is properly inferred
type Marks = typeof extension extends Extension<infer T>
? T['Marks']
: never
// Marks = { bold: {}, italic: {} }
Error Handling
The union function requires at least one extension. Calling it with no arguments will throw an error:
import { union } from 'prosekit/core'
// This will throw: "At least one extension is required"
const extension = union()
How It Works
When you merge extensions with union:
- Schema Merging: Node and mark specifications are combined into a unified schema
- Command Merging: All commands from all extensions are made available
- Plugin Merging: ProseMirror plugins are combined in the correct order
- Type Preservation: TypeScript types are preserved, so you get full type safety
Extension Ordering
The order of extensions matters in some cases:
- Node/Mark Priority: Later extensions take precedence for nodes and marks with the same name
- Plugin Order: Plugins are registered in the order extensions are provided
- Parse Rules: DOM parsing rules from later extensions override earlier ones
For explicit control over extension priority, use the withPriority function.
The union function is efficient and can be called during editor initialization without performance concerns. The merging happens once during editor creation.
import { createEditor, union } from 'prosekit/core'
// This is efficient - union is called once during initialization
const editor = createEditor({
extension: union(
defineDoc(),
defineParagraph(),
// ... many more extensions
)
})
See Also