Skip to content
Documentation GitHub
Development Guides

Extend the Editor

Guide for adding custom TipTap extensions to the Inklings editor.

Prerequisites

  • Familiarize yourself with the TipTap / Loro editor architecture in apps/desktop/src-react/components/editor/ for the editor component tree and LoroSync bridge
  • Read Working with Loro CRDT for CRDT binary pass-through rules

Overview

The editor uses TipTap (ProseMirror-based) with Loro CRDT for persistent undo/redo. Custom extensions live in apps/desktop/src-react/components/editor/extensions/.

Existing custom extensions:

ExtensionTypePurpose
WikiLinkInline node (atom)[[Display|slug]] with optional #heading
PropertyRefInline node (atom){{name:value}} property references
ImageBlockBlock nodeImage blocks with metadata
ImageBlockDropPluginDrag-drop and paste handling for images
AttachmentNodeInline nodeGeneric attachment references
AttachmentUploadPluginFile upload handling

Step 1: Create the Extension File

Create a new file in apps/desktop/src-react/components/editor/extensions/:

MyExtension.ts
import { Node } from "@tiptap/core";
export const MyExtension = Node.create({
name: "myExtension",
group: "inline", // or "block"
inline: true, // for inline nodes
atom: true, // for non-editable inline nodes
addAttributes() {
return {
myAttr: { default: null },
};
},
parseHTML() {
return [{ tag: "my-extension" }];
},
renderHTML({ HTMLAttributes }) {
return ["my-extension", HTMLAttributes];
},
});

Step 2: Register in the Editor

Add your extension to the TipTap editor configuration in InklingsEditor.tsx:

import { MyExtension } from "./extensions/MyExtension";
const editor = useEditor({
extensions: [
// ... existing extensions
MyExtension,
],
});

Step 3: Add a NodeView (Optional)

For extensions that need React rendering, create a NodeView component:

MyExtensionView.tsx
export function MyExtensionView({ node, editor }: NodeViewProps) {
return <span className="my-extension">{node.attrs.myAttr}</span>;
}

Register it in the extension:

addNodeView() {
return ({ node, editor, getPos }) => {
const dom = document.createElement("span");
// Vanilla DOM NodeView — see TipTap docs for React NodeView alternative
return { dom };
};
},

For NodeViews that depend on React hook data (e.g., resolved properties), use the extensionOptions mutation pattern.

Step 4: Markdown Round-Trip

If your extension needs to survive markdown serialization, add conversion logic in apps/desktop/src-react/components/editor/markdownUtils.ts:

  • To markdown: Handle your node type in the serializer
  • From markdown: Add a regex or parser rule to reconstruct the node

Step 5: Grid Layout Compatibility

If the extension is used in grid layouts, ensure it works with per-cell LoroDoc isolation. Each grid cell has an independent editor instance — extensions must not assume shared state across cells.

Checklist

  • Extension file created in extensions/
  • Registered in InklingsEditor.tsx
  • NodeView added if needed (vanilla DOM preferred for performance)
  • Markdown round-trip works (if applicable)
  • Works in both single-editor and grid layout modes
  • pnpm typecheck passes

Was this page helpful?