Mongoose _id exclusion from JSON serialization without removing document identity
0 reputation · 05 Apr 2021, 21:07 UTC
0 reputation · 05 Apr 2021, 21:07 UTC
Mongoose includes the _id field in query results and in output from toObject() and toJSON() by default. The goal is to keep _id available for internal document tracking and middleware while preventing it from being serialized into public API responses.
Other fields support select: false, but that option does not apply to _id. Setting { _id: false } at the schema level removes the identifier from the document entirely, which may break plugins or middleware that depend on document identity. No schema-level option appears to exclude _id only from JSON serialization while preserving it in memory.
Is there a supported global transformation or schema option that hides _id from toJSON() output without removing it from the document? Does defining a custom toJSON transform at the schema level affect middleware that reads _id before serialization? Are there behavior differences between Mongoose 6.x and 7.x regarding this serialization boundary?
Yes. Define a schema‑level toJSON (and optionally toObject) transform that deletes _id from the returned object. The original document retains its _id for internal tracking and middleware.
{ _id: false } at the schema level removes the identifier from the document itself, which can break plugins that expect _id to exist.toJSON runs after all document middleware (e.g., pre('save'), post('save')), so middleware still sees the full document with _id intact.toJSON/toObject transform API has not changed between Mongoose 6.x and 7.x; behavior is identical in both versions.toJSON options object with a transform function:const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
name: String,
email: String,
// other fields...
}, {
toJSON: {
transform: (doc, ret) => {
delete ret._id; // remove _id from serialized output
return ret;
}
},
// optional: also hide _id from toObject() if you use it elsewhere
toObject: { transform: (doc, ret) => { delete ret._id; return ret; } }
});
module.exports = mongoose.model('User', userSchema);
const User = require('./user');
(async () => {
const user = new User({ name: 'Ada', email: '[contact removed]' });
await user.save();
// Middleware still sees _id
user.pre('save', function(next) {
console.log('pre‑save _id:', this._id); // <- present
next();
});
// Serialized output lacks _id
const json = user.toJSON();
console.log('toJSON output:', json); // <- no _id field
console.log('document _id still:', user._id); // <- present
})();
toJSON transform to every schema via a plugin, ormongoose.Schema.set('toJSON', { transform: ... }) before defining schemas.The transform function receives the raw document (doc) and a plain object (ret) that Mongoose will return. By deleting _id from ret only, you alter the serialized representation without mutating the source document. Because the transform executes after all middleware hooks, any middleware that reads _id continues to operate on the unchanged document.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.