Types
The extension exports TypeScript types for working with suggestions.
SuggestionType
Public-facing suggestion type returned by the query API:
type SuggestionType = 'add' | 'delete' | 'replace' | 'markChange' | 'sink' | 'lift' | 'split'sinkandliftare pending list-item nesting changes (Tab and Shift-Tab)splitis a suggested block split. Accepting keeps the two blocks, rejecting merges them back together
SuggestionMarkType
Internal mark-level type stored on suggestion marks. Replace suggestions use separate mark types for their deletion and insertion parts:
type SuggestionMarkType = 'add' | 'delete' | 'replaceDeletion' | 'replaceInsertion' | 'markChange'SuggestionNodeType
Internal type stored in node attributes. Covers every mark-level type plus the structural variants that can only exist at the node level:
type SuggestionNodeType = SuggestionMarkType | 'sink' | 'lift' | 'blockSplit'blockSplit is the node-level counterpart of the public split type. It is what you find in suggestionType node attributes and in the data-suggestion-type DOM attribute, so use blockSplit in CSS selectors and split when filtering or switching on suggestion.type.
Use nodeSuggestionTypeToSuggestionType to convert (see Type helpers).
SuggestionMarkChangeOperation
Operation stored in a mark-change suggestion:
type SuggestionMarkChangeOperation = 'added' | 'removed'SuggestionMarkChange
Describes one mark mutation stored on a markChange suggestion:
type SuggestionMarkChange = {
operation: SuggestionMarkChangeOperation
markName: string
markAttrs: Record<string, unknown> | null
}Suggestion
Represents a suggestion found in the document:
type Suggestion = {
id: string
type: SuggestionType
userId: string
createdAt: string
updatedAt: string
userMetadata: Record<string, unknown> | null
from: number
to: number
text: string
fullText: string
// Only present for 'replace' suggestions:
deletionRange?: { from: number; to: number }
insertionRange?: { from: number; to: number }
replacedText?: string
// Only present for 'markChange' suggestions:
markChanges?: SuggestionMarkChange[]
// Only present when the suggestion affects block-level nodes:
insertedNodes?: JSONContent[]
deletedNodes?: JSONContent[]
}createdAt— ISO 8601 timestamp of when the suggestion was first createdupdatedAt— ISO 8601 timestamp of the most recent edit within the suggestion. Falls back tocreatedAtfor legacy data that predates this field.
text vs fullText
text— the text authored by this suggestion alone, excluding text inserted by nested (stacked) suggestions layered on top of it. Forreplacesuggestions, this is the new (inserted) text.fullText— the complete, continuous text spanned by this suggestion's marks, including any text inserted by nested suggestions from other authors. Forreplacesuggestions, this is the new (inserted) text.
For the vast majority of suggestions (no stacking) text === fullText. They diverge only when another author has inserted text via a nested suggestion inside this one.
Note that text only drops text that a nested suggestion inserted. A nested delete or mark-change does not insert content, so it never reduces the outer author's text. For example, if author A inserts "hello" and author B deletes "ll" inside it, A's text is still "hello" — B's deletion didn't add any text that belongs to B.
Worked example
Author A inserts "Hello world", then author B inserts "great " before "world" (nested inside A's addition):
| Suggestion | text | fullText |
|---|---|---|
| A (add, user-1) | "Hello world" | "Hello great world" |
| B (add, user-2) | "great " | "great " |
import { findSuggestions } from '@tiptap-pro/extension-tracked-changes'
const suggestions = findSuggestions(editor)
const outer = suggestions.find(s => s.userId === 'user-1')
outer.text // "Hello world" → show this in a review sidebar
outer.fullText // "Hello great world" → use when you need the literal spanDefault to text when displaying "what this person changed" (review panels, comment threads, activity logs). Reach for fullText only when you need the exact contiguous text the mark covers in the document.
For replace suggestions:
textcontains the new (inserted) textreplacedTextcontains the old (deleted) textdeletionRangeandinsertionRangegive the exact positions of each partfromandtocover the full range of both parts
For markChange suggestions:
textcontains the affected text contentmarkChangescontains the tracked mark operations, including mark names and attrs
For block-level node suggestions (e.g. inserting or deleting a paragraph or heading):
textalways contains the actual text content of the affected node — never a[nodeName]placeholderinsertedNodescontains the full JSON representation of each inserted nodedeletedNodescontains the full JSON representation of each deleted node
Use insertedNodes and deletedNodes to inspect node types and attributes when building richer UIs, such as displaying a node type badge alongside the suggestion text.
For split suggestions:
textis always empty, because a block split neither adds nor removes textfromandtospan the second block, the one created by the split
Render your own label for splits (for example "Paragraph break") instead of suggestion.text.
SuggestionMarkAttrs
Attributes stored on the suggestion mark:
type SuggestionMarkAttrs = {
id: string
type: SuggestionMarkType
userId: string
createdAt: string
updatedAt: string
userMetadata: Record<string, unknown> | null
markChanges?: SuggestionMarkChange[] | null
}SuggestionChangedEventPayload
Payload for the trackedChanges:suggestionChanged event. Fires whenever any content-bearing field of a suggestion changes, including range positions, text, mark changes, or type.
type SuggestionChangedEventPayload = {
suggestionId: string
oldSuggestion: Suggestion
suggestion: Suggestion
transaction: Transaction
}oldSuggestion— snapshot of the suggestion before the changesuggestion— the current (updated) suggestiontransaction— the ProseMirror transaction that caused the change
SuggestionRangeChangedEventPayload
Payload for the trackedChanges:suggestionRangeChanged event. Fires only when a suggestion's document positions shift.
type SuggestionRangeChangedEventPayload = {
suggestionId: string
oldRange: { from: number; to: number }
newRange: { from: number; to: number }
suggestion: Suggestion
transaction: Transaction
}BlockSplitMarkerRenderer
Builds the marker element shown at a block split. Called once per marker, so it must return a new element every time:
type BlockSplitMarkerRenderer = () => HTMLElementTrackedChangesOptions
Configuration options for the extension:
type TrackedChangesOptions = {
enabled: boolean
userId: string
userMetadata?: Record<string, unknown> | null
ignoredTrackingMarks: string[]
onSuggestionCreate?: (suggestion: Suggestion) => void
onSuggestionAccept?: (id: string) => void
onSuggestionReject?: (id: string) => void
blockSplitMarker?: string | HTMLElement | BlockSplitMarkerRenderer | null
}blockSplitMarker— the marker rendered at a suggested block split. Defaults to'¶'. Set it tonullto render no marker. See blockSplitMarker.
Type helpers
The extension exports constants and helper functions for working with suggestion types:
import {
SUGGESTION_TYPES,
isAddLikeType,
isDeleteLikeType,
isReplaceMarkType,
markTypeToSuggestionType,
nodeSuggestionTypeToSuggestionType,
} from '@tiptap-pro/extension-tracked-changes'
// Constants
SUGGESTION_TYPES.ADD // 'add'
SUGGESTION_TYPES.DELETE // 'delete'
SUGGESTION_TYPES.REPLACE_DELETION // 'replaceDeletion'
SUGGESTION_TYPES.REPLACE_INSERTION // 'replaceInsertion'
SUGGESTION_TYPES.MARK_CHANGE // 'markChange'
SUGGESTION_TYPES.SINK // 'sink'
SUGGESTION_TYPES.LIFT // 'lift'
SUGGESTION_TYPES.BLOCK_SPLIT // 'blockSplit' (node-level)
SUGGESTION_TYPES.SPLIT // 'split' (query API)
// Check if a mark type represents added content (matches 'add' and 'replaceInsertion')
isAddLikeType('add') // true
isAddLikeType('replaceInsertion') // true
// Check if a mark type represents deleted content (matches 'delete' and 'replaceDeletion')
isDeleteLikeType('delete') // true
isDeleteLikeType('replaceDeletion') // true
// Check if a mark type is one of the replace sub-types
isReplaceMarkType('replaceDeletion') // true
// Convert a mark-level type to the public-facing suggestion type
markTypeToSuggestionType('replaceDeletion') // 'replace'
markTypeToSuggestionType('add') // 'add'
// Convert a node-level type to the public-facing suggestion type
nodeSuggestionTypeToSuggestionType('blockSplit') // 'split'
nodeSuggestionTypeToSuggestionType('replaceInsertion') // 'replace'