Unique ID
UniqueID automatically assigns unique IDs to block nodes. Every paragraph, heading, blockquote, list, and other configured node gets a UUID attribute. IDs are assigned on creation, and pasted content gets new IDs when duplicates are detected. Useful for collaborative editing, deep linking, change tracking, and content addressing.
Not included in StarterKit. Add it separately.
import { Editor, Document, Paragraph, Text, UniqueID } from '@domternal/core';import '@domternal/theme';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [Document, Paragraph, Text, UniqueID],});
// Each paragraph now has a unique id attribute// <p id="a1b2c3d4-...">text</p>import { Component, signal } from '@angular/core';import { DomternalEditorComponent } from '@domternal/angular';import { Editor, Document, Paragraph, Text, UniqueID } from '@domternal/core';
@Component({ selector: 'app-editor', imports: [DomternalEditorComponent], templateUrl: './editor.html',})export class EditorComponent { editor = signal<Editor | null>(null); extensions = [Document, Paragraph, Text, UniqueID];}<domternal-editor [extensions]="extensions" (editorCreated)="editor.set($event)"/>import { Domternal } from '@domternal/react';import { Document, Paragraph, Text, UniqueID } from '@domternal/core';
export default function Editor() { return ( <Domternal extensions={[Document, Paragraph, Text, UniqueID]} > <Domternal.Content /> </Domternal> );}import { Editor, Document, Paragraph, Text, Heading, UniqueID } from '@domternal/core';
const editor = new Editor({ element: document.getElementById('editor')!, extensions: [ Document, Paragraph, Text, Heading, UniqueID.configure({ types: ['paragraph', 'heading'], attributeName: 'data-id', generateID: () => crypto.randomUUID(), }), ],});
// Access node IDs via the documenteditor.state.doc.descendants((node) => { const id = node.attrs['data-id']; if (id) console.log(node.type.name, id);});Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
types | string[] | 11 node types (see below) | Node types that receive unique IDs |
attributeName | string | 'id' | HTML attribute name for the ID |
generateID | () => string | generateUUID | Function to generate unique IDs |
filterDuplicates | boolean | true | Regenerate IDs for duplicates when pasting |
Default types
Section titled “Default types”The default list covers all common block nodes:
paragraph, heading, blockquote, codeBlock, bulletList, orderedList, taskList, listItem, taskItem, image, horizontalRule
Custom ID generator
Section titled “Custom ID generator”The built-in generator creates UUIDs in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. You can replace it with any function that returns a unique string:
UniqueID.configure({ generateID: () => crypto.randomUUID(), // native browser API})Or shorter IDs:
UniqueID.configure({ generateID: () => Math.random().toString(36).slice(2, 10), // e.g. "k5f2m8n1"})Custom attribute name
Section titled “Custom attribute name”UniqueID.configure({ attributeName: 'data-block-id', // renders as data-block-id="..." instead of id="..."})Disabling duplicate filtering
Section titled “Disabling duplicate filtering”UniqueID.configure({ filterDuplicates: false, // allow pasted content to keep original IDs})Commands
Section titled “Commands”UniqueID does not register any commands.
Keyboard shortcuts
Section titled “Keyboard shortcuts”UniqueID does not register any keyboard shortcuts.
Input rules
Section titled “Input rules”UniqueID does not register any input rules.
Toolbar items
Section titled “Toolbar items”UniqueID does not register any toolbar items.
How it works
Section titled “How it works”ID assignment
Section titled “ID assignment”UniqueID assigns IDs at three points:
- Initial load: When the editor view is ready, a
setTimeout(0)dispatches a transaction that walks the entire document and assigns IDs to any configured node that lacks one - New nodes: The
appendTransactionhook runs after every document change. It walks the new document and assigns IDs to any node that doesn’t have one yet (new paragraphs from Enter, new list items, etc.) - Pasted content: The
transformPastedprop intercepts paste operations before they are applied
Global attributes
Section titled “Global attributes”UniqueID uses addGlobalAttributes() to inject the ID attribute into all configured node types:
addGlobalAttributes() { return [{ types: this.options.types, attributes: { [this.options.attributeName]: { default: null, parseHTML: (element) => element.getAttribute(this.options.attributeName), renderHTML: (attributes) => { const id = attributes[this.options.attributeName]; if (!id) return null; return { [this.options.attributeName]: id }; }, }, }, }];}The attribute is parsed from and rendered to HTML as a standard HTML attribute (not a style):
<p id="a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d">Paragraph with unique ID</p>Duplicate filtering on paste
Section titled “Duplicate filtering on paste”When filterDuplicates is true (default), pasting content triggers the transformPasted hook:
- Collects all existing IDs in the current document
- Walks every node in the pasted slice
- If a pasted node’s ID already exists in the document, generates a new ID
- If the ID is unique, keeps it and adds it to the tracking set (to catch duplicates within the pasted content itself)
This prevents duplicate IDs when users copy-paste blocks within the same editor.
Initial ID assignment timing
Section titled “Initial ID assignment timing”The initial assignment uses setTimeout(0) to avoid dispatching a transaction during plugin initialization. The timeout callback creates a fresh transaction from editorView.state (not a stale reference) and only dispatches if the document actually changed. The timeout is cleaned up in the plugin’s destroy() method.
UUID generator
Section titled “UUID generator”The built-in generateUUID() function creates RFC 4122 v4 UUIDs without external dependencies:
function generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); });}This uses Math.random() which is sufficient for editor IDs but not cryptographically secure. For stronger uniqueness guarantees, use crypto.randomUUID() via the generateID option.
Exports
Section titled “Exports”import { UniqueID, uniqueIDPluginKey } from '@domternal/core';import type { UniqueIDOptions } from '@domternal/core';| Export | Type | Description |
|---|---|---|
UniqueID | Extension | The unique ID extension |
uniqueIDPluginKey | PluginKey | The ProseMirror plugin key |
UniqueIDOptions | TypeScript type | Options for UniqueID.configure() |
Source
Section titled “Source”@domternal/core - UniqueID.ts