mongodb/node-mongodb-native · error · MongoAPIError
Cursor returned a `null` document, but the cursor is not…
Error message
Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.
What it means
Thrown as a MongoAPIError from transformDocument() when the configured transform/map function returns exactly null for a document while the cursor is not exhausted. The cursor uses null internally to signal end-of-stream (next() returns null and for-await stops), so a user transform that maps real documents to null would silently truncate iteration. To prevent data loss the driver detects a null transform result for a non-terminal document and throws, then closes the cursor. Falsy but non-null values (0, '', false) are allowed.
Solutions
- Never return null from a map/transform; wrap nullable values in an object, e.g. doc => ({ value: doc.value ?? null }).
- Return undefined instead of null if you want a 'no value' that does not end iteration, though mapping to undefined is fragile too.
- Filter out documents you do not want with a $match stage rather than mapping them to null.
- Use a Readable stream with its own end semantics if you need null as a legitimate data value.
Example fix
// before: map returns null for some docs
const cursor = collection.find({}).map(doc => doc.optionalField ?? null);
// after: wrap the nullable value so the transform never returns null
const cursor = collection.find({}).map(doc => ({ value: doc.optionalField ?? null })); Defensive patterns
Strategy: validation
Validate before calling
// Reject transforms that can return null before assigning them
function safeMap<T, R>(cursor: FindCursor<T>, fn: (doc: T) => R): FindCursor<NonNullable<R>> {
return cursor.map(doc => {
const r = fn(doc);
if (r === null) throw new TypeError('cursor transform returned null; wrap nullable values in an object');
return r as NonNullable<R>;
});
} Try / catch
try {
await cursor.toArray();
} catch (err) {
if (err instanceof MongoAPIError && /Mapping documents to `null`/.test(err.message)) {
// fix the transform to never return null, then re-run
} else {
throw err;
}
} Prevention
- Never return null from map(); wrap nullable values in an object.
- Filter unwanted documents with $match instead of mapping them to null.
- Prefer returning undefined or a sentinel object over null.
When it happens
Trigger: cursor.map(doc => doc ? doc.value : null); cursor.map(() => null); a transform that returns null for a missing optional field; a projection-style map that produces null for some rows.
Common situations: Using map() to extract a nullable field without wrapping in an object; mapping to a value that happens to be null for the first document; refactoring a transform that previously returned undefined (allowed) into one that returns null (not allowed).
Related errors
- Cursor is already initialized
- Cursor is exhausted
- A collection name must be determined before getMore
- A collection name must be determined before killCursors
- Argument for maxTimeMS must be a number
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/e2ab728239dc2e46.
Report an issue: GitHub.
Appendix: source
Thrown at src/cursor/abstract_cursor.ts:1076
// @ts-expect-error: CursorEvents is generic so Parameters<CursorEvents["close"]> may not be assignable to `[]`. Not sure how to require extenders do not add parameters.
this.emit('close');
}
} finally {
this.hasEmittedClose = true;
}
}
/** @internal */
private async transformDocument(document: NonNullable<TSchema>): Promise<NonNullable<TSchema>> {
if (this.transform == null) return document;
try {
const transformedDocument = this.transform(document);
// eslint-disable-next-line no-restricted-syntax
if (transformedDocument === null) {
const TRANSFORM_TO_NULL_ERROR =
'Cursor returned a `null` document, but the cursor is not exhausted. Mapping documents to `null` is not supported in the cursor transform.';
throw new MongoAPIError(TRANSFORM_TO_NULL_ERROR);
}
return transformedDocument;
} catch (transformError) {
try {
await this.close();
} catch (closeError) {
squashError(closeError);
}
throw transformError;
}
}
/** @internal */
protected throwIfInitialized() {
if (this.initialized) throw new MongoCursorInUseError();
}
}
View on GitHub (pinned to dce7939f86)