Skip to main content

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

extension
Union<E>
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:
  1. Schema Merging: Node and mark specifications are combined into a unified schema
  2. Command Merging: All commands from all extensions are made available
  3. Plugin Merging: ProseMirror plugins are combined in the correct order
  4. 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.

Performance Considerations

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