---
title: "UniqueID extension"
description: "Add a unique ID to every single node and keep track of them with the UniqueID extension. More in the docs!"
canonical_url: "https://tiptap.dev/docs/editor/extensions/functionality/uniqueid"
---

# UniqueID extension

Add a unique ID to every single node and keep track of them with the UniqueID extension. More in the docs!

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.

> **Interactive demo:** [UniqueID](https://embed.tiptap.dev/preview/Extensions/UniqueID)

## Install

```bash
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'`

```js
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: `[]`

```js
UniqueID.configure({
  types: ['heading', 'paragraph'],
})
```

```js
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()`

```js
UniqueID.configure({
  generateID: ({ node }) => `${node.type.name}-${uuidv4()}`,
})
```

### filterTransaction

Ignore some mutations, for example applied from other users through the collaboration plugin.

Default: `null`

```js
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`

```js
UniqueID.configure({
  updateDocument: false,
})
```

## Usage with Collaboration

> **Important:**
>
> When using the UniqueID extension together with the [Collaboration extension](https://tiptap.dev/docs/editor/extensions/functionality/collaboration.md), 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.

```tsx
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

```tsx
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](#filtertransaction).
- **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](https://tiptap.dev/docs/collaboration/getting-started/install.md) 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

```js
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`.
