---
title: Plugins
description: What an Edytor plugin is, how to pass plugins to the editor, how plugin order decides conflicts, and which plugins ship with the package.
icon: plug
---

Everything the editor knows about content comes from plugins: block kinds, marks, inline atoms, key bindings, and hooks that can inspect or veto every edit. The core ships no block kinds of its own; `<Edytor>` adds the rich text, image and arrow move plugins by default, so `<Edytor />` alone is a working rich text editor.

## What a plugin is

A plugin is a function of the editor that returns definitions and hooks:

```ts title="logger.ts" check
import type { Plugin } from 'edytor';

export const loggerPlugin: Plugin = (edytor) => ({
	onBeforeOperation: ({ operation }) => {
		console.log('about to run', operation);
	},
	onChange: (value) => {
		console.log('document now has', value.children?.length, 'top-level blocks');
	}
});
```

The function runs once, when the editor is created. It receives the `Edytor` instance, so it can keep state in its closure and call editor methods from its hooks. Everything it returns is optional:

| Field                        | What it contributes                                                         |
| ---------------------------- | --------------------------------------------------------------------------- |
| `blocks`                     | Block kinds, see [Blocks](/docs/customization/blocks)                       |
| `marks`                      | Text marks, see [Marks](/docs/customization/marks)                          |
| `inlineBlocks`               | Inline atoms, see [Inline blocks](/docs/customization/inline-blocks)        |
| `hotkeys`                    | Key bindings, see [Hotkeys](/docs/customization/hotkeys)                    |
| `commands`                   | Named commands for menus such as the slash menu                             |
| `placeholder`                | Text for empty blocks, see [Placeholder](/docs/customization/placeholder)   |
| `onBeforeOperation` and more | Hooks into every edit, the DOM and the clipboard                            |

[Writing plugins](/docs/plugins/writing-plugins) documents every field and hook.

## Passing plugins to the editor

Pass an array to the `plugins` prop of `<Edytor>`:

```svelte title="Editor.svelte" check
<script lang="ts">
	import {
		Edytor,
		codePlugin,
		slashMenuPlugin,
		toolbarPlugin,
		markdownShortcutsPlugin,
		blockMenuPlugin
	} from 'edytor';

	const plugins = [codePlugin, markdownShortcutsPlugin, slashMenuPlugin, toolbarPlugin, blockMenuPlugin];
</script>

<Edytor {plugins} />
```

Plugins are read once, when the component mounts. Changing the array afterwards has no effect.

### Default plugins

`<Edytor>` completes the list with the plugins every editor needs, after yours so your kinds, marks and keys take precedence, unless the list already has them:

| Plugin | Position | Replaced by |
| --- | --- | --- |
| [`arrowMovePlugin`](/docs/plugins/arrow-move) | after yours | `arrowMovePlugin` in your list |
| [`imagePlugin`](/docs/plugins/image) | after yours | `imagePlugin` or any `createImagePlugin(...)` in your list |
| [`richTextPlugin`](/docs/plugins/rich-text) | last | `richTextPlugin` in your list |
| [Block handles](/docs/plugins/block-handles) | first | the `blockHandles` prop, or a handles plugin in your list |

The example above therefore runs block handles, your five plugins, then arrow move, image and rich text. A default you list yourself stays where you put it, so older code that passes `plugins={[richTextPlugin]}` still works.

Pass `defaultPlugins={false}` to get exactly your list (block handles still follow `blockHandles`). The defaults are added by the `<Edytor>` component, per view; the editor instance underneath has none of its own.

## Plugin order

The array order is the precedence order. Two rules follow from it:

- **Definitions: first wins.** When two plugins define the same block kind, mark, inline atom or command id, the one listed first is used and the later one is ignored. To replace a definition from a bundled plugin, list your plugin before it.
- **Prevention: first wins.** Hooks and key bindings run in list order. The first plugin that calls `prevent()` stops the operation or claims the key, and the plugins after it are not asked.

Order therefore matters whenever two plugins touch the same thing: the same kind name, the same chord, or the same operation. A plugin that overrides or refuses something a bundled plugin does goes before it in the list. The three defaults are added after your plugins, so yours already come first; to let a default win over one of your plugins, list the default yourself before it.

One exception: two plugins that declare different `defaultChild` values for the same block kind are an error, whatever their order. The editor throws when it is created.

## Why plugins are Svelte files

Block, mark and inline atom definitions render through Svelte snippets. A snippet can only be declared in a `.svelte` file, and a component's `<script module>` block can reference the snippets declared in its markup. So a plugin that renders anything is usually a `.svelte` file that exports the plugin from `<script module>`:

```svelte title="HighlightPlugin.svelte" check
<script module lang="ts">
	import type { Snippet } from 'svelte';
	import type { Plugin } from 'edytor';

	export const highlightPlugin: Plugin = () => ({
		marks: { marker: { snippet: marker } }
	});
</script>

{#snippet marker({ content }: { content: Snippet })}
	<mark class="marker">{@render content()}</mark>
{/snippet}
```

Import it like any other module: `import { highlightPlugin } from './HighlightPlugin.svelte'`. A plugin that only adds hooks or key bindings can be a plain `.ts` file.

## Bundled plugins

| Plugin                                                 | Export                    | What it adds                                                                               |
| ------------------------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------------ |
| [Rich text](/docs/plugins/rich-text)                   | `richTextPlugin`          | Paragraphs, headings, lists, to-dos, toggles, callouts, quotes, dividers, ten marks, and Notion's shortcuts. On by default |
| [Code](/docs/plugins/code)                             | `codePlugin`              | Code blocks with syntax highlighting and a Copy button                                     |
| [Image](/docs/plugins/image)                           | `imagePlugin`, `createImagePlugin` | Notion's image block: embed from a link or your upload, with an editable caption. On by default |
| [Block handles](/docs/plugins/block-handles)           | `blockHandlesPlugin`, `createBlockHandlesPlugin` | A `+` and a drag grip beside each block, drag and drop, keyboard moves. On by default |
| [Block menu](/docs/plugins/block-menu)                 | `blockMenuPlugin`, `createBlockMenuPlugin` | Notion's block menu on a grip click: search, Turn into, Duplicate, Move, Delete |
| [Slash menu](/docs/plugins/slash-menu)                 | `slashMenuPlugin`, `createSlashMenuPlugin` | A sectioned command menu opened by typing `/`                             |
| [Toolbar](/docs/plugins/toolbar)                       | `toolbarPlugin`, `createToolbarPlugin` | A floating toolbar over text selections: Turn into, link, marks and colors    |
| [Markdown shortcuts](/docs/plugins/markdown-shortcuts) | `markdownShortcutsPlugin` | Block prefixes such as `# ` or `- `, and inline `**bold**`, `*italic*`, `` `code` ``, `~strike~` |
| [Arrow move](/docs/plugins/arrow-move)                 | `arrowMovePlugin`         | <kbd>Mod</kbd> + <kbd>Shift</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> moves the caret's block; <kbd>Mod</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> moves selected blocks. On by default |
| [Mention](/docs/plugins/mention)                       | (not exported)            | A reference implementation of an inline atom                                               |

All exports come from the package root, `edytor`. The `create…` factories take options, including snippets that replace the menus' and handles' markup (see [Menus and handles](/docs/customization/menus)). For the matching document look, add the [Notion theme](/docs/customization/styling#notion-theme).
