meteor/meteor · error · Error
Can't change a doc's _id while updating
Error message
Can't change a doc's _id while updating
What it means
Thrown by LocalCollection._updateInResultsSync when, during an ordered/unordered observe on a local cache, a doc produced by applying a change has an _id that is not EJSON.equals to the previous doc's _id. The observe machinery keys documents by _id; an _id change mid-update would corrupt the result-set index, so it is rejected. This is an internal invariant of the reactive cache, not a direct user-facing API.
Source
Thrown at packages/minimongo/local_collection.js:1758
};
// Is this selector just shorthand for lookup by _id?
LocalCollection._selectorIsId = selector =>
typeof selector === 'number' ||
typeof selector === 'string' ||
selector instanceof MongoID.ObjectID
;
// Is the selector just lookup by _id (shorthand or not)?
LocalCollection._selectorIsIdPerhapsAsObject = selector =>
LocalCollection._selectorIsId(selector) ||
LocalCollection._selectorIsId(selector && selector._id) &&
Object.keys(selector).length === 1
;
LocalCollection._updateInResultsSync = (query, doc, old_doc) => {
if (!EJSON.equals(doc._id, old_doc._id)) {
throw new Error('Can\'t change a doc\'s _id while updating');
}
const projectionFn = query.projectionFn;
const changedFields = DiffSequence.makeChangedFields(
projectionFn(doc),
projectionFn(old_doc)
);
if (!query.ordered) {
if (Object.keys(changedFields).length) {
query.changed(doc._id, changedFields);
query.results.set(doc._id, doc);
}
return;
}
const old_idx = LocalCollection._findInOrderedResults(query, doc);View on GitHub (pinned to 5076d2f818)
Solutions
- Ensure transforms do not alter the _id (return a new object but copy _id verbatim).
- Make sure added and changed messages for the same logical doc use EJSON-equal _id values, including matching id type (string vs ObjectID).
- Remove _id from any 'changed' fields your publish function emits; _id must be immutable per document.
- If you are seeing this on a normal subscription, file a bug against the publish function — the client cache treats _id as immutable.
Defensive patterns
Strategy: type-guard
Type guard
// For transforms: ensure _id is preserved verbatim.
function safeTransform(fn) {
return (doc) => {
const out = fn(doc);
if (!EJSON.equals(out._id, doc._id)) {
throw new TypeError('Transform must not change _id');
}
return out;
};
}
cursor.observe({ /* ... */ }, { transform: safeTransform(myTransform) }); Prevention
- Never include _id in a 'changed' payload on the server side.
- Keep id types consistent: pick string OR MongoID.ObjectID for a given collection and stick to it.
- Verify any transform copies _id without modification.
- Add a fixture that asserts EJSON.equals(before._id, after._id) for every changed message.
When it happens
Trigger: A transform function passed to observe mutates the _id; a server 'changed' message includes an _id field that differs from the added doc's _id; a projection that rewrites _id; two different server docs sharing an id collision after a transform. Also reproducible by manually poking the cache or by a buggy publish that sends changed{_id: newId} for an existing doc.
Common situations: Custom transform on a cursor that returns a new object whose _id is computed/normalized differently; EJSON id type mismatch (string vs ObjectID vs MongoID.ObjectID) where EJSON.equals returns false even though the values look identical as strings; a publication that mistakenly sends _id in changed fields.
Related errors
- Unknown id for changed: ${id}
- Must use an ordered observe with skip or limit (i.e. 'addedB
- Please specify only one of added() and addedAt()
- Please specify only one of changed() and changedAt()
- Please specify only one of removed() and removedAt()
AI-assisted analysis of meteor/meteor@5076d2f818 (2026-08-13).
Data as JSON: /api/errors/46696087748915a7.
Report an issue: GitHub.