Text Editor - Plugins

Extend the editor with custom commands, state, schema, and UI through the plugin API.

The plugin API extends the editor without forking it. A plugin adds commands, reads and mutates the selection, contributes nodes or marks to the schema, registers raw ProseMirror plugins, and connects to external services. Each plugin is scoped behind its own namespace.

This page is the conceptual reference. The Included Plugins section links to standalone pages, each with full source and a live demo.

#Anatomy of a Plugin

defineTextEditorPlugin(name, install, options?) creates a plugin. The result is passed to the plugins prop on <TextEditor.Root>.

import { defineTextEditorPlugin } from '@primeui/vue-texteditor';

export const myPlugin = defineTextEditorPlugin('myPlugin', (ctx) => ({
    commands: {
        doSomething: () => {
            /* use ctx here */
        }
    }
}));

name is the namespace under which the plugin's commands are exposed, as pluginCommands.myPlugin. install(ctx) runs once when the editor mounts; it receives the editor context and returns the commands the plugin contributes. The optional options argument carries schema and initial ProseMirror-plugin contributions, covered under Extending the Editor.

#Plugin Context

The install function receives a context object. The first group of helpers covers most plugins; the ProseMirror methods provide lower-level access when needed.

MethodDescription
getSelectedText()Currently selected text
replaceSelection(content, asHtml?)Replaces the selection; asHtml inserts HTML
getEditorElement()The root editor DOM element
optionsThe plugin's own options, see Options
onUnmounted(fn)Registers cleanup for when the plugin unmounts
getView() / getState()The live ProseMirror EditorView / EditorState
registerProseMirrorPlugin(pl)Adds a ProseMirror plugin at runtime; returns a remover
runCommand(cmd)Runs a raw ProseMirror command

#Registering Plugins

The plugins prop on <TextEditor.Root> accepts the plugin list. The plugin set is frozen when the editor state is constructed, so every plugin must be present on the initial render. Plugins cannot be added after mount.

<TextEditor.Root v-model="value" :plugins="[myPlugin]">
    <TextEditor.Toolbar>
        <MyButton />
    </TextEditor.Toolbar>
    <TextEditor.Content />
</TextEditor.Root>

#Using Plugin Commands

A plugin's commands are reachable from any widget through pluginCommands, namespaced by the plugin name. A component reads them from the context accessor:

<script setup lang="ts">
import { useTextEditorContext } from '@primeui/vue-texteditor';

const { pluginCommands } = useTextEditorContext();
</script>

<template>
    <button @click="pluginCommands.myPlugin?.doSomething()">Run</button>
</template>

The toolbar slot also exposes the same map as plugins for inline use:

<TextEditor.Toolbar v-slot="{ plugins }">
    <button @click="plugins.myPlugin?.doSomething()">Run</button>
</TextEditor.Toolbar>

The same pluginCommands map is available inside <TextEditor.ContextToolbar> and <TextEditor.ContextToolbarMore>.

#Options

A tuple [plugin, options] passes options to a plugin. They arrive on the context as options; a bare plugin gets options: undefined. The options type is supplied as the generic parameter for type-checking.

const plugins = [myPlugin, [codeHighlightPlugin, { theme: 'github-dark' }]];
defineTextEditorPlugin<{ theme?: string }>('codeHighlight', ({ options }) => {
    const theme = options?.theme ?? 'github-dark';
    /* ... */
});

#Extending the Editor

The optional third argument contributes to the schema or the initial ProseMirror state.

defineTextEditorPlugin('collab', (ctx) => ({/* commands */}), {
    schema: { marks: { highlight: highlightMarkSpec } },
    prosemirrorPlugins: (schema, options) => [ySyncPlugin(options.fragment)]
});

schema contributes new nodes and marks to the per-instance schema. To enrich an existing node, such as adding an id attribute to heading, extendNodeSpec(baseNodes.heading, { attrs: { id: { default: null } } }) derives a spec from the base rather than redefining it. A collision with an existing node or mark name produces a console warning.

prosemirrorPlugins contributes ProseMirror plugins to the initial editor state. It receives the assembled schema and the plugin's options, so configuration-dependent setup such as collaboration providers can be built there. This is the channel for plugins that must exist from the first render, including Yjs collaboration.

#Included Plugins

These zero-dependency example plugins ship with the docs as starting points, from the smallest possible plugin to a schema-extending one. Each has its own page with full source and a live demo.

  • Uppercase — the minimal plugin: one command, no ProseMirror access
  • Typography — smart replacements (dashes, ellipsis, arrows) via input rules
  • Emoji — :shortcode: to emoji, plus an insert command
  • Character Count — live stats with a subscription API
  • Focus — dims every block except the caret's, via decorations
  • Invisible Characters — toggles space and paragraph markers
  • Details — a collapsible block that extends the schema

Plugins with external dependencies are covered in Code Highlight (Shiki), Translate, and AI Improve.