# How to Extend Tiptap Rich Text Editor with Custom Extensions in Plane

> Learn how to extend Tiptap rich text editor in Plane by creating custom extensions and registering them in the extension registry. Enhance your editor easily and efficiently.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: how-to-guide
- Published: 2026-06-18

---

**You extend Tiptap in Plane by creating a standard Tiptap extension (Mark, Node, or Extension), exporting it from the editor package, and registering it in the `extensionRegistry` array within [`rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/rich-text-extensions.tsx), where the `isEnabled` predicate controls visibility.**

Plane's rich-text editor is built on **Tiptap v2** and uses a centralized extension registry to manage optional features. The codebase in the `makeplane/plane` repository provides a structured way to add custom marks, nodes, and commands without modifying core editor logic.

## Understanding Plane's Extension Architecture

Plane's editor architecture separates core functionality from optional extensions through a registry pattern. The main editor component in [`packages/editor/src/core/components/editors/rich-text/editor.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/components/editors/rich-text/editor.tsx) initializes the Tiptap instance using a `getExtensions` callback that aggregates extensions from multiple sources.

Optional extensions are managed by `RichTextEditorAdditionalExtensions` in [`packages/editor/src/ce/extensions/rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/ce/extensions/rich-text-extensions.tsx). This utility reads from an `extensionRegistry` array that determines which extensions to load based on `isEnabled` predicates and instantiates them via `getExtension` factories.

This design allows extensions to be conditionally enabled or disabled through props like `disabledExtensions` and `flaggedExtensions`, making it possible to toggle features per workspace or user preference.

## Step-by-Step Guide to Extending Tiptap

### Step 1: Create a Custom Tiptap Extension

Create your extension file following Tiptap v2 conventions. Plane supports standard Mark, Node, and Extension types.

```typescript
// packages/editor/src/core/extensions/custom-highlight.ts
import { Mark, mergeAttributes } from "@tiptap/core";

export const Highlight = Mark.create({
  name: "highlight",

  addOptions() {
    return { HTMLAttributes: { class: "bg-yellow-200" } };
  },

  parseHTML() {
    return [{ tag: "mark" }];
  },

  renderHTML({ HTMLAttributes }) {
    return ["mark", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
  },

  addCommands() {
    return {
      toggleHighlight:
        () =>
        ({ commands }) => {
          return commands.toggleMark(this.name);
        },
    };
  },
});

```

### Step 2: Export the Extension from the Package Index

Update the barrel file to make your extension available to the registry.

```typescript
// packages/editor/src/core/extensions/index.ts
export * from "./custom-highlight";

```

Now the extension can be imported elsewhere using `import { Highlight } from "@/extensions"` (the `@` alias points to `packages/editor/src`).

### Step 3: Register in the Extension Registry

Add a new entry to `extensionRegistry` in [`rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/rich-text-extensions.tsx). The `isEnabled` function checks against disabled extensions, while `getExtension` returns the extension class.

```typescript
// packages/editor/src/ce/extensions/rich-text-extensions.tsx
import { Highlight } from "@/extensions/custom-highlight";

const extensionRegistry: TRichTextEditorAdditionalExtensionsRegistry[] = [
  // Existing entries...
  {
    isEnabled: (disabledExtensions) => !disabledExtensions.includes("highlight"),
    getExtension: () => Highlight,
  },
];

```

### Step 4: Implement UI Controls

Access your custom commands through the editor context. Plane's `useEditorContext` hook provides access to the Tiptap editor instance.

```typescript
// Example: a toolbar button inside a custom editor UI
import { useEditorContext } from "@/hooks";

export function HighlightButton() {
  const { editor } = useEditorContext();

  return (
    <button
      onClick={() => editor?.chain().focus().toggleHighlight().run()}
      className="p-2"
    >
      Highlight
    </button>
  );
}

```

When the button is clicked, the `toggleHighlight` command defined in the extension runs, wrapping the selection in a `<mark>` element styled by the CSS class defined in `addOptions`.

### Step 5: Conditionally Disable Extensions

Control extension availability by passing the extension name to the `disabledExtensions` prop.

```tsx
<RichTextEditorWithRef
  disabledExtensions={["highlight"]}   // hides the Highlight extension
  {...otherProps}
/>

```

## Key Implementation Files

- **Core Editor Component** ([`packages/editor/src/core/components/editors/rich-text/editor.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/components/editors/rich-text/editor.tsx)): Creates the Tiptap editor instance and calls `getExtensions` to assemble the extension list.
- **Extension Registry** ([`packages/editor/src/ce/extensions/rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/ce/extensions/rich-text-extensions.tsx)): Contains the `extensionRegistry` array and `RichTextEditorAdditionalExtensions` logic that filters extensions based on `isEnabled` predicates.
- **Export Barrel** ([`packages/editor/src/core/extensions/index.ts`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/extensions/index.ts)): Central export point for custom extensions within the editor package.
- **Slash Commands Example** ([`packages/editor/src/extensions/slash-commands/root.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/extensions/slash-commands/root.tsx)): Reference implementation of an optional extension using the registry pattern.

## Summary

- Plane uses an `extensionRegistry` pattern in [`packages/editor/src/ce/extensions/rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/ce/extensions/rich-text-extensions.tsx) to manage optional Tiptap extensions.
- Custom extensions must implement standard Tiptap v2 APIs (Mark, Node, or Extension) and be exported from [`packages/editor/src/core/extensions/index.ts`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/extensions/index.ts).
- Register extensions with `isEnabled` and `getExtension` properties to control initialization logic.
- Control visibility via the `disabledExtensions` prop on the editor component.
- UI commands integrate through the `useEditorContext` hook and standard Tiptap command chains like `editor.chain().focus().toggleHighlight().run()`.

## Frequently Asked Questions

### What is the extension registry in Plane?

The extension registry is an array of configuration objects defined in [`packages/editor/src/ce/extensions/rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/ce/extensions/rich-text-extensions.tsx). Each entry contains an `isEnabled` function that determines if the extension should load based on current props, and a `getExtension` factory that returns the Tiptap extension instance. This pattern decouples extension availability from the core editor initialization in [`editor.tsx`](https://github.com/makeplane/plane/blob/main/editor.tsx).

### How do I disable a custom extension for specific users?

Pass the extension name to the `disabledExtensions` array prop on the `RichTextEditorWithRef` component. The `isEnabled` predicate in the registry checks against this array, preventing the extension from being included in the editor's extension list for that specific instance.

### Can I add custom nodes as well as marks?

Yes. Plane's registry supports all Tiptap v2 extension types including Node, Mark, and plain Extension. The registration process remains identical regardless of the extension type—create the extension file, export it from the index, and add it to the `extensionRegistry` with appropriate `isEnabled` logic.

### Where should I store custom extension files?

Store custom extensions in `packages/editor/src/core/extensions/` or a subdirectory thereof. Import them into [`packages/editor/src/core/extensions/index.ts`](https://github.com/makeplane/plane/blob/main/packages/editor/src/core/extensions/index.ts) to maintain the package's export structure, then reference them in [`packages/editor/src/ce/extensions/rich-text-extensions.tsx`](https://github.com/makeplane/plane/blob/main/packages/editor/src/ce/extensions/rich-text-extensions.tsx) using the `@/extensions` alias.