2025-06-16 12:15:40 +02:00
# Attribution Feature
The Attribution feature extends Yjs types to provide rich metadata about content
changes, including information about who created, deleted, or formatted content.
This enables powerful collaborative editing features such as authorship tracking
and change visualization. The information about who performed which changes can
2026-06-16 21:29:45 +02:00
be handled by a separate CRDT (which is part of the renderer).
2025-06-16 12:15:40 +02:00
## Core Concepts
2026-06-16 21:29:45 +02:00
### Renderer
2025-06-16 12:15:40 +02:00
2026-06-16 21:29:45 +02:00
A `renderer` renders Content (with its Attributions) to a delta. It is the
central component for the attribution feature: pass it to methods like
`toDelta()` / `getDelta()` to render content together with attribution metadata.
2025-06-16 12:15:40 +02:00
2026-06-16 21:29:45 +02:00
Different implementations of Renderer are available for different use cases:
- `DiffRenderer` : Highlights the differences between two Yjs documents
- `SnapshotRenderer` : Highlights the differences between two snapshots
2025-06-16 12:15:40 +02:00
### Attributed Content
Attributed content includes standard Yjs operations enhanced with attribution metadata:
```javascript
// Standard content
[{ insert: 'hello world' }]
// Attributed content
[
{ insert: 'hello', attribution: { insert: ['kevin'] } },
{ insert: ' world', attribution: { insert: ['alice'] } }
]
```
### Delete Attribution
Deleted content is represented in attributed results to maintain authorship information and proper position tracking:
```javascript
// Shows deleted content with attribution
[
{ insert: 'hello ', attribution: { delete: ['kevin'] } },
{ insert: 'world' }
]
```
## API Reference
2026-07-02 18:44:46 +02:00
### Y.Type
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
In Yjs v14 all shared types (text, array, map, xml) are instances of the unified
`Y.Type` .
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
#### `toDelta([{ renderer }])`
Returns the delta representation (lib0/delta) of the type's content, optionally
with attribution information.
2025-06-16 12:15:40 +02:00
**Parameters:**
2026-06-16 21:29:45 +02:00
- `renderer` (optional): The renderer instance
2025-06-16 12:15:40 +02:00
**Returns:**
2026-07-02 18:44:46 +02:00
- A `Delta` describing the content, with attribution metadata if `renderer` is provided
2025-06-16 12:15:40 +02:00
**Examples:**
```javascript
2026-07-02 18:44:46 +02:00
const ytext = ydoc.get()
2025-06-16 12:15:40 +02:00
// Content is inserted during collaborative editing
// Attribution is handled automatically by the server
// Without attribution
2026-07-02 18:44:46 +02:00
const d = ytext.toDelta()
2025-06-16 12:15:40 +02:00
// [{ insert: 'hello world' }]
// With attribution
2026-07-02 18:44:46 +02:00
const attributedDelta = ytext.toDelta({ renderer })
2025-06-16 12:15:40 +02:00
// [
// { insert: 'hello', attribution: { insert: ['kevin'] } },
// { insert: ' world', attribution: { insert: ['alice'] } }
// ]
```
2026-07-02 18:44:46 +02:00
#### `applyDelta(delta, [origin], [{ renderer }])`
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
Applies a delta (lib0/delta) on the shared type. The optional `origin` is stored
on the transaction (`transaction.origin` ) and forwarded verbatim on the emitted
`'delta'` event (lib0 RDT spec), so listeners can recognize — and skip — changes
they produced themselves. When a `renderer` is provided, positions in the delta
are interpreted relative to the attributed (rendered) content.
2025-06-16 12:15:40 +02:00
**Parameters:**
2026-07-02 18:44:46 +02:00
- `delta` : The changes to apply
- `origin` (optional): Origin of the transaction that applies this delta; defaults to `null`
2026-06-16 21:29:45 +02:00
- `renderer` (optional): The renderer instance
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
### YEvent
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
#### `getDelta([{ renderer, deep }])`
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
Returns the changes of an event as a delta, optionally rendered with attribution
information.
2025-06-16 12:15:40 +02:00
**Parameters:**
2026-06-16 21:29:45 +02:00
- `renderer` (optional): The renderer instance
2026-07-02 18:44:46 +02:00
- `deep` (optional): Render child types as deltas
2025-06-16 12:15:40 +02:00
**Returns:**
2026-07-02 18:44:46 +02:00
- A `Delta` describing the changes, with attribution metadata if `renderer` is provided
2025-06-16 12:15:40 +02:00
## Position Adjustments
When working with attributed content, position calculations must account for deleted content that appears in the attributed representation but not in the standard representation.
### Example: Position Adjustment
```javascript
// Standard content (length: 5)
ytext.toString() // "world"
// Attributed content (includes deleted content)
2026-07-02 18:44:46 +02:00
ytext.toDelta({ renderer })
2025-06-16 12:15:40 +02:00
// [
// { insert: 'hello ', attribution: { delete: ['kevin'] } }, // positions 0-5
// { insert: 'world' } // positions 6-10
// ]
// To insert after "world":
// - Standard position: 5 (after "world")
// - Attributed position: 11 (after "world" accounting for deleted "hello ")
```
## Use Cases
Events in Yjs are enhanced to work with attributed content, automatically adjusting positions when attribution is considered.
### Event Position Adjustment
2026-06-16 21:29:45 +02:00
When a `renderer` is used, event positions are automatically adjusted to account for deleted content.
2025-06-16 12:15:40 +02:00
**Example:**
```javascript
// Initial content: "hello world"
// User deletes "hello " (positions 0-6)
// Current visible content: "world"
ytext.observe((event, transaction) => {
// User wants to insert "!" after "world"
// Standard event (without attribution)
const standardDelta = event.getDelta()
// Shows insertion at position 5 (after "world" in visible content)
2026-06-16 21:29:45 +02:00
// Attributed event (with renderer)
const attributedDelta = event.getDelta({ renderer })
2025-06-16 12:15:40 +02:00
// Shows insertion at position 11 (accounting for deleted "hello ")
// [
// { insert: 'hello ', attribution: { delete: ['kevin'] } },
// { insert: 'world' },
// { insert: '!' } // inserted at attributed position 11
// ]
})
```
## Use Cases
### Authorship Visualization
Display content with visual indicators of who created each part:
```javascript
2026-06-16 21:29:45 +02:00
function renderWithAuthorship(ytext, renderer) {
2026-07-02 18:44:46 +02:00
const attributedDelta = ytext.toDelta({ renderer })
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
return attributedDelta.children.map(op => {
2025-06-16 12:15:40 +02:00
const author = op.attribution?.insert?.[0] || 'unknown'
const isDeleted = op.attribution?.delete
return {
content: op.insert,
author,
isDeleted,
className: `author-${author} ${isDeleted ? 'deleted' : ''}`
}
})
}
```
### Change Tracking
Track who made specific changes to content:
```javascript
2026-06-16 21:29:45 +02:00
function trackChanges(ytext, renderer) {
2025-06-16 12:15:40 +02:00
ytext.observe((event, transaction) => {
2026-07-02 18:44:46 +02:00
const changes = event.getDelta({ renderer })
2025-06-16 12:15:40 +02:00
2026-07-02 18:44:46 +02:00
changes.children.forEach(change => {
2025-06-16 12:15:40 +02:00
if (change.attribution) {
console.log(`Change by ${change.attribution.insert?.[0] || change.attribution.delete?.[0]}:` , change)
}
})
})
}
```
## Best Practices
2026-06-16 21:29:45 +02:00
### Renderer Lifecycle
2025-06-16 12:15:40 +02:00
2026-06-16 21:29:45 +02:00
- Create one renderer per document or collaboration session
- Ensure the renderer is consistently used across all operations
- Pass the same renderer instance to all methods that need attribution
2025-06-16 12:15:40 +02:00
## Migration Guide
### Upgrading Existing Code
To add attribution support to existing Yjs applications:
2026-06-16 21:29:45 +02:00
1. **Add renderer ** : Create and configure a renderer
2. **Update method calls ** : Add the renderer parameter to relevant method calls
2025-06-16 12:15:40 +02:00
3. **Handle attributed content ** : Update code to handle the new attribution metadata format
4. **Adjust position calculations ** : Update position calculations to account for deleted content
### Backward Compatibility
The Attribution feature is fully backward compatible:
2026-06-16 21:29:45 +02:00
- All existing methods work without the renderer parameter
2025-06-16 12:15:40 +02:00
- Existing code continues to work unchanged
- Attribution is opt-in and doesn't affect performance when not used