Utilities
The extension exports utility functions for querying suggestions and exporting base document JSON.
findSuggestions()
Find all suggestions in the document, with optional filtering.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
markName | string | 'suggestion' | The name of the suggestion mark |
options | FindSuggestionsOptions | {} | Optional filters (see below) |
Options
| Option | Type | Description |
|---|---|---|
type | SuggestionType | Filter by suggestion type |
userId | string | Filter by user ID |
id | string | Filter by suggestion ID |
Returns
Suggestion[] — Array of suggestions found in the document.
import { findSuggestions } from '@tiptap-pro/extension-tracked-changes'
// Find all suggestions
const allSuggestions = findSuggestions(editor)
// Find suggestions by type
const insertions = findSuggestions(editor, 'suggestion', { type: 'add' })
const deletions = findSuggestions(editor, 'suggestion', { type: 'delete' })
const replacements = findSuggestions(editor, 'suggestion', { type: 'replace' })
const splits = findSuggestions(editor, 'suggestion', { type: 'split' })
// Find suggestions by user
const userSuggestions = findSuggestions(editor, 'suggestion', { userId: 'user-123' })findSuggestionById()
Find a specific suggestion by ID.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
id | string | — | The suggestion ID to find |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
Suggestion | undefined — The suggestion if found, undefined otherwise.
import { findSuggestionById } from '@tiptap-pro/extension-tracked-changes'
const suggestion = findSuggestionById(editor, 'suggestion-123')
if (suggestion) {
console.log(`Found suggestion: ${suggestion.text}`)
}findSuggestionRanges()
Find all document ranges covered by a suggestion. A single suggestion may span multiple text nodes when it crosses formatting boundaries.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
id | string | — | The suggestion ID to find |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
Array<{ from: number; to: number }> — Array of position ranges covered by this suggestion. Adjacent ranges are automatically merged.
import { findSuggestionRanges } from '@tiptap-pro/extension-tracked-changes'
const ranges = findSuggestionRanges(editor, 'suggestion-123')
// Returns: [{ from: 10, to: 25 }, { from: 30, to: 35 }]getSuggestionAtSelection()
Get the suggestion at the current selection position. Works with both mark-based suggestions (text) and attribute-based suggestions (atom nodes).
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
Suggestion | undefined — The suggestion at the current cursor position, or undefined if none.
import { getSuggestionAtSelection } from '@tiptap-pro/extension-tracked-changes'
const suggestion = getSuggestionAtSelection(editor)
if (suggestion) {
// Show accept/reject buttons in your UI
showSuggestionPopup(suggestion)
}getSuggestionAtPosition()
Get the suggestion at a specific document position.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
pos | number | — | The document position to check |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
Suggestion | null — The suggestion at the position, or null if none found.
import { getSuggestionAtPosition } from '@tiptap-pro/extension-tracked-changes'
const suggestion = getSuggestionAtPosition(editor, 42)findSuggestionsInRange()
Find all suggestions that overlap with a specific document range.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
from | number | — | Start position of the range |
to | number | — | End position of the range |
filters | { userId?: string; type?: SuggestionType } | undefined | Optional filters |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
Suggestion[] — Array of suggestions overlapping the range.
import { findSuggestionsInRange } from '@tiptap-pro/extension-tracked-changes'
// Find all suggestions between positions 10 and 50
const suggestions = findSuggestionsInRange(editor, 10, 50)
// With filters
const userInsertions = findSuggestionsInRange(editor, 10, 50, {
userId: 'user-123',
type: 'add',
})suggestionExists()
Check whether a suggestion still exists in the document.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
suggestionId | string | — | The suggestion ID to check |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
boolean — true if the suggestion exists.
import { suggestionExists } from '@tiptap-pro/extension-tracked-changes'
if (suggestionExists(editor, 'suggestion-123')) {
// Suggestion is still in the document
}getSuggestionRange()
Get the bounding position range of a suggestion.
| Argument | Type | Default | Description |
|---|---|---|---|
editor | Editor | — | The Tiptap editor instance |
suggestionId | string | — | The suggestion ID |
markName | string | 'suggestion' | The name of the suggestion mark |
Returns
{ from: number; to: number } | null — The bounding range, or null if not found.
import { getSuggestionRange } from '@tiptap-pro/extension-tracked-changes'
const range = getSuggestionRange(editor, 'suggestion-123')
if (range) {
console.log(`Suggestion spans from ${range.from} to ${range.to}`)
}acceptSuggestions()
Accept multiple suggestions by their IDs. Processes each suggestion individually.
| Argument | Type | Description |
|---|---|---|
editor | Editor | The Tiptap editor instance |
suggestionIds | string[] | Array of suggestion IDs to accept |
Returns
boolean — true if at least one suggestion was accepted.
import { acceptSuggestions } from '@tiptap-pro/extension-tracked-changes'
acceptSuggestions(editor, ['suggestion-1', 'suggestion-2', 'suggestion-3'])rejectSuggestions()
Reject multiple suggestions by their IDs. Processes each suggestion individually.
| Argument | Type | Description |
|---|---|---|
editor | Editor | The Tiptap editor instance |
suggestionIds | string[] | Array of suggestion IDs to reject |
Returns
boolean — true if at least one suggestion was rejected.
import { rejectSuggestions } from '@tiptap-pro/extension-tracked-changes'
rejectSuggestions(editor, ['suggestion-1', 'suggestion-2'])withoutTrackedChanges()
Returns the base document by rejecting every pending suggestion on a copy. Suggested additions are removed, suggested deletions are restored, and replacements revert to their original content. Pending formatting, attribute, node type, list nesting, and block split changes are also reverted. Node type proposals restore the original type and attributes while retaining current content after other suggestions are rejected.
| Option | Type | Description |
|---|---|---|
content | string | JSONContent | HTML or a complete document JSON object. JSON strings and content arrays are not supported. |
schema | Schema | The source document's ProseMirror schema, including TrackedChanges and all custom nodes and marks. |
Both options are required. The options type is exported as WithoutTrackedChangesOptions.
Returns
JSONContent — A complete document JSON object without suggestion marks or reserved tracking attributes, including null tracking defaults. Other content, marks, and attributes are preserved as represented by the schema after rejection.
The utility leaves the input and source editor unchanged, including pending suggestions, selection, history, callbacks, comment threads, and the collaboration provider. Content without pending suggestions is supported. Repeated exports with the same content and schema produce deeply equal JSON.
import { withoutTrackedChanges } from '@tiptap-pro/extension-tracked-changes'
const base = withoutTrackedChanges({
content: editor.getJSON(),
schema: editor.schema,
})
// HTML input also returns JSON.
const baseFromHTML = withoutTrackedChanges({
content: editor.getHTML(),
schema: editor.schema,
})For example, a pending insertion of world after Hello produces base JSON containing only Hello. The insertion stays pending in the source editor.
Use without an editor
JSON input works without a DOM. Build the schema with getSchema and the same extensions used by the source document, including custom extensions.
import { getSchema } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import { TrackedChanges, withoutTrackedChanges } from '@tiptap-pro/extension-tracked-changes'
const schema = getSchema([StarterKit, TrackedChanges])
const base = withoutTrackedChanges({
content: { type: 'doc', content: [{ type: 'paragraph' }] },
schema,
})Limits and errors
HTML input requires a DOM environment and preserves text whitespace. Custom HTML renderers must retain suggestion data for the parser to restore the base content. HTML cannot restore data discarded by an earlier export or unsupported by the schema.
The utility throws an error for invalid document JSON, missing tracking schema support, or suggestions that cannot be restored. HTML input without a DOM also throws. Source content stays unchanged if export fails.
Inline atom suggestions must use node attributes. Suggestion marks on non-text nodes cause an error. Explicit list nesting deltas must be nonzero safe integers, positive for sink and negative for lift. Omitted or null deltas remain supported for older documents.
Only pending suggestions are reverted. The utility cannot undo accepted changes or edits made without tracking.
generateSuggestionId()
Generate a unique suggestion ID. Combines timestamp with random string for uniqueness.
Returns
string — A unique suggestion ID in the format suggestion-{timestamp}-{random}.
import { generateSuggestionId } from '@tiptap-pro/extension-tracked-changes'
const id = generateSuggestionId()
// Returns: 'suggestion-1706621696789-k7x2m9p'getTimestamp()
Get the current time as an ISO 8601 timestamp string.
Returns
string — Current time as ISO 8601 string.
import { getTimestamp } from '@tiptap-pro/extension-tracked-changes'
const timestamp = getTimestamp()
// Returns: '2024-01-30T12:34:56.789Z'getSuggestionHTMLAttributes()
Reads suggestion data from a live ProseMirror node and returns the corresponding data-suggestion-* DOM attributes, safe to spread onto a NodeView's root element.
| Argument | Type | Description |
|---|---|---|
node | Node | The ProseMirror node received as the node prop in your NodeView component |
Returns
SuggestionHTMLAttributes — An object containing only the data-suggestion-* attributes derived from node.attrs. Keys whose value is null or undefined are omitted, so non-suggestion nodes return {}.
The attributes produced are:
| Output key | Source (node.attrs) |
|---|---|
data-suggestion-id | suggestionId |
data-suggestion-type | suggestionType |
data-suggestion-user | suggestionUserId |
data-suggestion-created | suggestionCreatedAt |
data-suggestion-updated | suggestionUpdatedAt |
data-suggestion-user-metadata | suggestionUserMetadata (JSON-serialized) |
data-suggestion-nesting-delta | suggestionNestingDelta |
Node type proposals additionally produce data-suggestion-node-type-change from suggestionNodeTypeChange, even when a deletion or split uses the node's primary suggestion identity. For standalone type proposals, the helper supplies the usual attributeChange identity attributes too.
Why node, not HTMLAttributes
The HTMLAttributes prop in a NodeView is computed once at creation time and never updated. Passing it to this utility would produce stale attributes whenever suggestion state changes — for example, after a remote collaboration update or when a suggestion is accepted or rejected. The node prop is the reactive source of truth and is updated on every render, so always pass node.
Usage
import { getSuggestionHTMLAttributes } from '@tiptap-pro/extension-tracked-changes'
function MyNodeView({ node }) {
const suggestionAttrs = getSuggestionHTMLAttributes(node)
return (
<div {...suggestionAttrs}>
Your content here
</div>
)
}For detailed guidance on integrating NodeViews with Tracked Changes, see the NodeView Support guide.