yjs/yjs · error · Error
Exceeded content range
Error message
Exceeded content range
What it means
YType.formatText (invoked via applyDelta with retain/format attributes) walks the text while consuming the retained length. If, after consuming the whole content, remaining format length is still > 0, the delta tried to format more characters than the type contains.
Source
Thrown at src/ytype.js:217
if (length < 0 || (length === 0 && i !== contents.length)) {
const c = contents[--i]
getItemCleanStart(transaction, createID(item.id.client, c.clock + c.content.getLength() + length))
}
} else {
// plain content: split directly at the offset
getItemCleanStart(transaction, createID(item.id.client, item.id.clock + length))
length = 0
}
} else {
length -= rightLen
}
break
}
}
this.forward()
}
if (length > 0) {
throw new Error('Exceeded content range')
}
insertNegatedFormats(transaction, parent, this, negatedFormats)
}
}
/**
* Negate applied formats
*
* @param {Transaction} transaction
* @param {YType} parent
* @param {ItemTextListPosition} currPos
* @param {Map<string,any>} negatedFormats
*
* @private
* @function
*/
const insertNegatedFormats = (transaction, parent, currPos, negatedFormats) => {
// check if we really need to remove formatsView on GitHub (pinned to 567af9b41f)
Solutions
- Check ytext.length (or the delta target's length) before applying and clamp retain so retain + insert <= length.
- Re-fetch a fresh delta/snapshot after remote changes instead of reusing a stale one.
- Catch the error and fall back to computing a new delta against current content.
Example fix
// before
ytext.applyDelta([{ retain: 100, attributes: { bold: true } }]) // throws if shorter
// after
if (100 <= ytext.length) ytext.applyDelta([{ retain: 100, attributes: { bold: true } }])
else ytext.applyDelta([{ retain: ytext.length, attributes: { bold: true } }]) Defensive patterns
Strategy: validation
Validate before calling
const total = delta.reduce((n, op) => n + (op.retain || 0) + (op.insert?.length || op.insert || 0), 0)
if (total > ytext.length) throw new Error('Delta exceeds text length')
ytext.applyDelta(delta) Type guard
const deltaFits = (delta, len) => delta.reduce((n, op) => n + (typeof op.retain === 'number' ? op.retain : 0) + (typeof op.insert === 'string' ? op.insert.length : (typeof op.insert === 'number' ? 1 : 0)), 0) <= len
Try / catch
try {
ytext.applyDelta(delta)
} catch (e) {
if (e.message === 'Exceeded content range') {
ytext.applyDelta(delta.slice(0, Math.max(0, ytext.length))) // recompute against fresh state
} else throw e
} Prevention
- Always base deltas on a freshly read ytext.length
- Recompute deltas after any remote update (observe events) instead of caching them
- Test formatting code against empty and single-character texts to catch off-by-one retains
When it happens
Trigger: Calling ytext.applyDelta(delta) where a retain value (plus any inserted length) exceeds the text length, e.g. { retain: 100, attributes: { bold: true } } on a shorter text.
Common situations: Porting Quill deltas computed against a stale editor snapshot; concurrent edits shrank the text before the delta was applied; off-by-one retain values in loops that format text in chunks.
Related errors
- Garbage-collection must be disabled in `originDoc`!
- Unexpected content type in insert operation
- Unexpected content type
- [yjs#509] Not same Y.Doc
- Invalid access: Add Yjs type to a document before reading da
AI-assisted analysis of yjs/yjs@567af9b41f (2026-09-01).
Data as JSON: /api/errors/d66eb5ce48154429.
Report an issue: GitHub.