Mongoose: How to use ‘id’ instead of ‘_id’

Updated: December 30, 2023 By: Guest Contributor Post a comment


Working with MongoDB typically involves dealing with _id field, which is a BSON type provided by MongoDB to uniquely identify documents. Mongoose, a popular Object Document Mapper (ODM) for MongoDB, abides by this convention but also offers a virtual id field to make working with the identifiers in application code more comfortable and familiar, especially for those coming from JSON-centric systems or ORMs that use id without an underscore. This tutorial provides an in-depth look into configuring Mongoose schemas to work with a virtual id field.

Configuring the Mongoose Schema

Before diving into advanced configurations, let’s start with a basic Mongoose model example:

const mongoose = require('mongoose');

const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;

const exampleSchema = new Schema({
  name: { type: String, required: true },
  // Other fields...

const Example = mongoose.model('Example', exampleSchema);

module.exports = Example;

As you can see, the schema defines a model for documents that will have a name field. By default, Mongoose adds an _id field of ObjectId type to the schema to act as the primary identifier.

To make the id without an underscore accessible, we can use Mongoose’s built-in virtual properties. These virtuals are not persisted to MongoDB — they only exist logically and can be used as if they were fields in the model.

Let’s modify the schema to include a virtual id:

exampleSchema.virtual('id').get(function () {
  return this._id.toHexString();

exampleSchema.set('toJSON', {
  virtuals: true

exampleSchema.set('toObject', {
  virtuals: true

By adding these lines of code, we tell Mongoose to convert the _id field to string and provide it as id. Also, we configure the schema to include virtuals when we convert documents to JSON or plain objects.

Considerations with Saving References

When dealing with related collections, it’s typical to save references to other documents via their _id. If you are using the id getter in your application logic, you should be mindful that operations expecting an _id, such as populate() or querying, will still work with a string representation thanks to type coercion in BSON.

Handling Virtuals with Mongoose Methods

Not all methods treat virtuals the same, hence when using methods as lean(), you must explicitly state that virtuals should be included, as such:

Example.find().lean({ virtuals: true }).then(examples => {
  examples.forEach(example => console.log(; // Now 'id' is accessible
}).catch(err => {
  console.error('Query failed', err);

Advanced Use Cases

Advanced use cases might involve using the Mongoose alias feature or manipulating the ObjectID field itself. In some cases, development teams might want to use their own custom logic to generate unique keys that replace the traditional _id for specific reasons.

For more sophisticated solutions custom modifications using pre and post hooks or middleware might be necesary. Some utilities of these would include automatic translation of id to _id during update operations, or pre-saving unique identifier generation that differs from the default ObjectId styles.

Sample Middleware Injection

Below is an example of a pre-save hook to inject custom IDs before document saving:

exampleSchema.pre('save', function(next) {
  if (!this.isNew) {
  this._id = generateCustomId(); // Assumed to be implemented elsewhere


While Mongoose automatically works with MongoDB’s _id field on the back end, developers can definitely streamline their front-end code by using virtual fields to reference model identifiers as id. This tutorial has touched upon how one can configure their Mongoose schemas to include such virtual fields and has explored the extent and limitations of their application within various areas of Mongoose’s feature set.

It is highly encouraged that application developers understand not just the how, but also the when and why of overriding default behaviors. Leveraging the Mongoose library effectively ensures your application’s data models are both robust and intuitive.