Fork me on GitHub

Sub Docs

Subdocuments are documents embedded in other documents. In Mongoose, this means you can nest schemas in other schemas. Mongoose has two distinct notions of subdocuments: arrays of subdocuments and single nested subdocuments.

var childSchema = new Schema({ name: 'string' });

var parentSchema = new Schema({
  // Array of subdocuments
  children: [childSchema],
  // Single nested subdocuments. Caveat: single nested subdocs only work
  // in mongoose >= 4.2.0
  child: childSchema

Subdocuments are similar to normal documents. Nested schemas can have middleware, custom validation logic, virtuals, and any other feature top-level schemas can use. The major difference is that subdocuments are not saved individually, they are saved whenever their top-level parent document is saved.

var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';

// `parent.children[0].save()` is a no-op, it triggers middleware but
// does **not** actually save the subdocument. You need to save the parent
// doc.;

Subdocuments have save and validate middleware just like top-level documents. Calling save() on the parent document triggers the save() middleware for all its subdocuments, and the same for validate() middleware.

childSchema.pre('save', function (next) {
  if ('invalid' == {
    return next(new Error('#sadpanda'));

var parent = new Parent({ children: [{ name: 'invalid' }] }); (err) {
  console.log(err.message) // #sadpanda

Subdocuments' pre('save') and pre('validate') middleware execute before the top-level document's pre('save') but after the top-level document's pre('validate') middleware. This is because validating before save() is actually a piece of built-in middleware.

// Below code will print out 1-4 in order
var childSchema = new mongoose.Schema({ name: 'string' });

childSchema.pre('validate', function(next) {

childSchema.pre('save', function(next) {

var parentSchema = new mongoose.Schema({
  child: childSchema,
parentSchema.pre('validate', function(next) {

parentSchema.pre('save', function(next) {

Finding a sub-document

Each subdocument has an _id by default. Mongoose document arrays have a special id method for searching a document array to find a document with a given _id.

var doc =;

Adding sub-docs to arrays

MongooseArray methods such as push, unshift, addToSet, and others cast arguments to their proper types transparently:

var Parent = mongoose.model('Parent');
var parent = new Parent;

// create a comment
parent.children.push({ name: 'Liesl' });
var subdoc = parent.children[0];
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
subdoc.isNew; // true (err) {
  if (err) return handleError(err)

Sub-docs may also be created without adding them to the array by using the create method of MongooseArrays.

var newdoc = parent.children.create({ name: 'Aaron' });

Removing subdocs

Each subdocument has it's own remove method. For an array subdocument, this is equivalent to calling .pull() on the subdocument. For a single nested subdocument, remove() is equivalent to setting the subdocument to null.

// Equivalent to `parent.children.pull(_id)`;
// Equivalent to `parent.child = null`
parent.child.remove(); (err) {
  if (err) return handleError(err);
  console.log('the subdocs were removed');

Alternate declaration syntax for arrays

If you create a schema with an array of objects, mongoose will automatically convert the object to a schema for you:

var parentSchema = new Schema({
  children: [{ name: 'string' }]
// Equivalent
var parentSchema = new Schema({
  children: [new Schema({ name: 'string' })]

Next Up

Now that we've covered Sub-documents, let's take a look at querying.