UniqueID extension

VersionDownloads

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-id

Settings

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:

  1. Initialize the Y.Doc
  2. Start the collaboration provider
  3. Wait for the synced event
  4. Check if the collaborative document is empty
  5. 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 filterTransaction to 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 to
  • extensions (Extensions): The extensions to use. Must include the UniqueID extension. To customize how the IDs are generated, you can pass options to the UniqueID extension.

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.