Install the Tracked Changes extension

Paid add-on

Install and configure the Tracked Changes extension by following this guide.

Install

Tracked Changes is a paid add-on that isn't included in any plan. Test it during your free trial or purchase it in your dashboard.

Then configure your package manager by following the private registry guide.

Then, install the package:

npm install @tiptap-pro/extension-tracked-changes

Basic setup

import { Editor } from '@tiptap/core'
import { TrackedChanges } from '@tiptap-pro/extension-tracked-changes'

const editor = new Editor({
  extensions: [
    TrackedChanges.configure({
      enabled: true,
      userId: 'user-123',
      userMetadata: { name: 'John Doe' },
    }),
  ],
})

Settings

enabled

Enable or disable track changes mode. When enabled, all edits become suggestions instead of direct changes.

Default: false

TrackedChanges.configure({
  enabled: true,
})

userId

The ID of the current user making suggestions. This should come from your authentication system.

Default: 'anonymous'

TrackedChanges.configure({
  userId: 'user-123',
})

userMetadata

Arbitrary metadata about the current user, stored as a JSON-serializable object on each suggestion. Useful for storing display names, avatars, or other custom data alongside the suggestion without requiring a separate user store.

Default: null

TrackedChanges.configure({
  userId: 'user-123',
  userMetadata: {
    name: 'John Doe',
    avatar: 'https://example.com/avatar.jpg',
    role: 'editor',
  },
})

onSuggestionCreate

Callback fired when a new suggestion is created. Receives the suggestion object containing id, type, userId, createdAt, from, to, and text.

Default: undefined

TrackedChanges.configure({
  onSuggestionCreate: (suggestion) => {
    console.log('New suggestion created:', suggestion)
    // Update your UI, notify other users, etc.
  },
})

onSuggestionAccept

Callback fired when a suggestion is accepted. Receives the suggestion ID.

Default: undefined

TrackedChanges.configure({
  onSuggestionAccept: (id) => {
    console.log('Suggestion accepted:', id)
  },
})

onSuggestionReject

Callback fired when a suggestion is rejected. Receives the suggestion ID.

Default: undefined

TrackedChanges.configure({
  onSuggestionReject: (id) => {
    console.log('Suggestion rejected:', id)
  },
})

blockSplitMarker

The marker shown where a block split was suggested. It is rendered at the end of the block before the split.

Default: '¶'

Pass a string to change the character:

TrackedChanges.configure({
  blockSplitMarker: '↵',
})

Pass an element, or a function returning an element, for full control over the markup:

TrackedChanges.configure({
  // A function is called once per marker and must return a new element each time
  blockSplitMarker: () => {
    const marker = document.createElement('span')

    marker.className = 'my-split-marker'
    marker.textContent = '¶'

    return marker
  },
})

Pass null to render no marker at all:

TrackedChanges.configure({
  blockSplitMarker: null,
})

A single HTMLElement also works and is cloned for each marker. Whatever you return, the extension sets data-tracked-change-split="true", contenteditable="false", and disables selection and pointer events on it, so the marker never becomes editable content. See styling for the default CSS.