UniqueID extension
The UniqueID extension adds unique IDs to the node types you configure.
The extension keeps track of your nodes, even if you split them, merge them, undo/redo changes, crop content, paste content … It just works.
Also, you can configure which node types get an unique ID, and which not, and you can customize how those IDs are generated.
Install
npm install @tiptap/extension-unique-idSettings
attributeName
Name of the attribute that is attached to the HTML tag (will be prefixed with data-).
Default: 'id'
UniqueID.configure({
attributeName: 'uid',
})types
All node types that should get a unique ID, for example ['heading', 'paragraph'].
You can also pass 'all' to add IDs to every node type in the document except doc and text.
Default: []
UniqueID.configure({
types: ['heading', 'paragraph'],
})UniqueID.configure({
types: 'all',
})generateID
Function that generates and returns a unique ID. It receives a context object (for example { node, pos }), so you can customize ID generation based on the node's type or its position.
Default: ({ node, pos }) => uuidv4()
UniqueID.configure({
generateID: ({ node }) => `${node.type.name}-${uuidv4()}`,
})filterTransaction
Ignore some mutations, for example applied from other users through the collaboration plugin.
Default: null
import { isChangeOrigin } from '@tiptap/extension-collaboration'
// Adds support for collaborative editing
UniqueID.configure({
filterTransaction: (transaction) => !isChangeOrigin(transaction),
})updateDocument
Whether to update the document by adding unique IDs to the nodes. Set this property to false if the document is in readonly mode, is immutable, or you don't want it to be modified.
Default: true
UniqueID.configure({
updateDocument: false,
})Usage with Collaboration
Important
When using the UniqueID extension together with the Collaboration extension, ensure that the editor is only mounted after the collaboration provider has synced. Mounting the editor on an unsynced Y.Doc can lead to unintended document state (e.g., persistent empty paragraphs), which the UniqueID extension will then preserve.
Incorrect setup
In this example, the editor is mounted immediately with the Collaboration extension before the provider has synced. This causes the editor to initialize against an empty Y.Doc, which leads to unintended document mutations.
import Collaboration from '@tiptap/extension-collaboration'
import { isChangeOrigin } from '@tiptap/extension-collaboration'
import { EditorContent, useEditor } from '@tiptap/react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
import { useEffect } from 'react'
import * as Y from 'yjs'
const doc = new Y.Doc()
export default () => {
// ❌ Editor is mounted before the provider has synced
const editor = useEditor({
extensions: [
// ...other extensions
UniqueID.configure({
types: ['paragraph', 'heading'],
filterTransaction: (transaction) => !isChangeOrigin(transaction),
}),
Collaboration.configure({
document: doc,
}),
],
content: '<p>Initial content</p>', // This will be duplicated on every sync
})
useEffect(() => {
// Provider connects and syncs after the editor is already mounted
const provider = new TiptapCollabProvider({
name: 'your-document-name',
appId: 'YOUR_APP_ID',
token: 'YOUR_JWT',
document: doc,
})
}, [])
return <EditorContent editor={editor} />
}Correct setup
To avoid this issue, follow this initialization order:
- Initialize the
Y.Doc - Start the collaboration provider
- Wait for the
syncedevent - Check if the collaborative document is empty
- Mount the editor with the correct content
import Collaboration from '@tiptap/extension-collaboration'
import { isChangeOrigin } from '@tiptap/extension-collaboration'
import { EditorContent, useEditor } from '@tiptap/react'
import { TiptapCollabProvider } from '@tiptap-pro/provider'
import { useEffect } from 'react'
import * as Y from 'yjs'
const doc = new Y.Doc()
export default () => {
const editor = useEditor({
extensions: [
// ...other extensions
UniqueID.configure({
types: ['paragraph', 'heading'],
filterTransaction: (transaction) => !isChangeOrigin(transaction),
}),
Collaboration.configure({
document: doc,
}),
],
// Do not set content here — wait for sync
})
useEffect(() => {
const provider = new TiptapCollabProvider({
name: 'your-document-name',
appId: 'YOUR_APP_ID',
token: 'YOUR_JWT',
document: doc,
onSynced() {
// Only seed content if the collaborative document is empty
const isEmpty = doc.getXmlFragment('default').length === 0
if (isEmpty && editor) {
editor.commands.setContent(yourInitialContent)
}
},
})
}, [])
return <EditorContent editor={editor} />
}Key points
- Do not initialize editor content before sync. Do not rely on default editor state when using collaboration.
- Use
filterTransactionto ignore mutations from collaboration. See the filterTransaction setting. - Avoid reusing corrupted document names during development. If a bad state has already been persisted, use a new document name to start clean.
For the full collaboration setup guide, refer to the Install Collaboration documentation.
Server side Unique ID utility
The generateUniqueIds function allows you to add unique IDs to a Tiptap document on the server side, without needing to create an Editor instance. This is useful for processing documents server-side or when you need to add IDs to existing content.
Parameters
doc(JSONContent): A Tiptap JSON document to add unique IDs toextensions(Extensions): The extensions to use. Must include theUniqueIDextension. To customize how the IDs are generated, you can pass options to theUniqueIDextension.
Return type
Returns a new Tiptap document (a JSONContent object) with unique IDs added to the nodes.
Example
import { generateUniqueIds, UniqueID } from '@tiptap/extension-unique-id'
import { StarterKit } from '@tiptap/starter-kit'
const doc = {
type: 'doc',
content: [
{ type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] },
{ type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'My Heading' }] },
],
}
const newDoc = generateUniqueIds(doc, [
StarterKit,
UniqueID.configure({ types: ['paragraph', 'heading'] }),
])
// Result:
// {
// type: 'doc',
// content: [
// { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], attrs: { id: 'd4590f81-52e8-45ec-b317-2e9a805b03e3' } },
// { type: 'heading', content: [{ type: 'text', text: 'My Heading' }], attrs: { level: 1, id: 'c88f9b5f-7b91-442f-b4d9-ee0d04104827' } }
// ]
// }The function automatically picks up the configuration from the UniqueID extension, including options like types, attributeName, and generateID.