Skip to main content

Overview

The priority system in ProseKit allows you to control the order in which extensions are applied. This is useful when you need certain extensions to take precedence over others.

Priority Levels

ProseKit provides five priority levels:
const Priority = {
  lowest: 0,
  low: 1,
  default: 2,
  high: 3,
  highest: 4,
} as const
Priority.lowest
0
Lowest priority - applied first, overridden by all other priorities
Priority.low
1
Low priority - applied after lowest, before default
Priority.default
2
Default priority - used when no priority is specified
Priority.high
3
High priority - applied after default, before highest
Priority.highest
4
Highest priority - applied last, overrides all other priorities

withPriority()

Returns a new extension with the specified priority.

Function Signature

function withPriority<T extends Extension>(
  extension: T,
  priority: Priority
): T

Parameters

extension
Extension
required
The extension to apply priority to
priority
Priority
required
The priority level to assign

Return Value

extension
T
A new extension with the specified priority. The type is preserved.

Examples

Basic Usage

import { withPriority, Priority } from 'prosekit/core'
import { defineMyExtension } from './my-extension'

const highPriorityExtension = withPriority(
  defineMyExtension(),
  Priority.high
)

Overriding Default Behavior

Use priority to ensure your custom extension takes precedence over built-in extensions:
import { createEditor, union, withPriority, Priority } from 'prosekit/core'
import { defineKeymap } from 'prosekit/core'
import { defineBasicExtension } from 'prosekit/basic'

// Custom keymap that should override default shortcuts
const customKeymap = withPriority(
  defineKeymap({
    'Mod-b': () => {
      console.log('Custom bold behavior')
      return true
    }
  }),
  Priority.high
)

const editor = createEditor({
  extension: union([
    defineBasicExtension(), // includes default Mod-b handler
    customKeymap            // will override the default
  ])
})

Plugin Execution Order

Control when plugins run relative to each other:
import { union, withPriority, Priority } from 'prosekit/core'
import { definePlugin } from 'prosekit/core'

// This plugin should run first
const initPlugin = withPriority(
  definePlugin(() => {
    return {
      view(editorView) {
        console.log('Initialize first')
        return {}
      }
    }
  }),
  Priority.highest
)

// This plugin should run last
const cleanupPlugin = withPriority(
  definePlugin(() => {
    return {
      view(editorView) {
        console.log('Initialize last')
        return {}
      }
    }
  }),
  Priority.lowest
)

const extension = union([initPlugin, cleanupPlugin])

Schema Customization

Ensure custom node/mark specs override defaults:
import { union, withPriority, Priority } from 'prosekit/core'
import { defineNodeSpec } from 'prosekit/core'
import { defineParagraph } from 'prosekit/extensions/node'

// Custom paragraph implementation
const customParagraph = withPriority(
  defineNodeSpec({
    name: 'paragraph',
    content: 'inline*',
    group: 'block',
    parseDOM: [{ tag: 'p.custom' }],
    toDOM: () => ['p', { class: 'custom' }, 0]
  }),
  Priority.high
)

const extension = union([
  defineParagraph(),  // default paragraph
  customParagraph     // will override the default
])

Event Handler Priority

Control the order of event handlers:
import { withPriority, Priority, defineKeyDownHandler } from 'prosekit/core'

// Critical handler that should run first
const criticalHandler = withPriority(
  defineKeyDownHandler((view, event) => {
    if (event.key === 'Escape') {
      console.log('Critical escape handler')
      return true
    }
    return false
  }),
  Priority.highest
)

// Optional handler that runs later
const optionalHandler = withPriority(
  defineKeyDownHandler((view, event) => {
    console.log('Optional handler')
    return false
  }),
  Priority.low
)

Conditional Priority

Dynamically set priority based on configuration:
import { withPriority, Priority } from 'prosekit/core'
import { defineMyExtension } from './my-extension'

interface Config {
  overrideDefaults?: boolean
}

function createExtension(config: Config) {
  const extension = defineMyExtension()
  
  if (config.overrideDefaults) {
    return withPriority(extension, Priority.highest)
  }
  
  return extension
}

// High priority version
const ext1 = createExtension({ overrideDefaults: true })

// Default priority version
const ext2 = createExtension({ overrideDefaults: false })

How Priority Works

Extension Processing Order

When multiple extensions are combined with union, they are processed based on priority:
  1. Extensions are sorted by priority (lowest to highest)
  2. Higher priority extensions are applied last
  3. Later applications can override earlier ones

Plugin Order

For ProseMirror plugins:
  • Higher priority plugins are added later in the plugin array
  • Later plugins in the array receive events first
  • This allows high-priority plugins to intercept and prevent event handling by low-priority plugins

Schema Merging

For node and mark specifications:
  • Higher priority specs override lower priority specs with the same name
  • Parse rules from higher priority extensions take precedence
  • This allows customization of built-in node/mark behavior

Best Practices

Use Sparingly

Only use priority when necessary. Most extensions work fine with default priority:
// Good - no priority needed
const extension = union([
  defineDoc(),
  defineParagraph(),
  defineBold()
])

// Only when necessary
const extension = union([
  defineDoc(),
  defineParagraph(),
  withPriority(customBold(), Priority.high)
])

Document Priority Decisions

When using priority, document why:
// Good - documents the reason
const customKeymap = withPriority(
  defineKeymap({ /* ... */ }),
  Priority.high  // Override default shortcuts
)

// Not clear why priority is needed
const customKeymap = withPriority(
  defineKeymap({ /* ... */ }),
  Priority.high
)

Test Priority Interactions

When using multiple extensions with different priorities, test their interactions:
import { test } from 'vitest'
import { createEditor, union, withPriority, Priority } from 'prosekit/core'

test('high priority handler runs first', () => {
  let order: string[] = []
  
  const lowHandler = withPriority(
    defineKeyDownHandler(() => {
      order.push('low')
      return false
    }),
    Priority.low
  )
  
  const highHandler = withPriority(
    defineKeyDownHandler(() => {
      order.push('high')
      return false
    }),
    Priority.high
  )
  
  const editor = createEditor({
    extension: union([lowHandler, highHandler])
  })
  
  // Simulate event
  // ...
  
  expect(order).toEqual(['high', 'low'])
})

Common Use Cases

Custom Keyboard Shortcuts

Override default shortcuts with high priority

Plugin Initialization

Ensure setup plugins run before feature plugins

Schema Customization

Replace built-in node/mark implementations

Event Interception

Handle events before they reach default handlers

See Also