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

What is a Subdocument?

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 Subdocument

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 Subdocs 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)

Subdocs 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');

Parents of Subdocs

Sometimes, you need to get the parent of a subdoc. You can access the parent using the parent() function.

const schema = new Schema({
  docArr: [{ name: String }],
  singleNested: new Schema({ name: String })
const Model = mongoose.model('Test', schema);

const doc = new Model({
  docArr: [{ name: 'foo' }],
  singleNested: { name: 'bar' }

doc.singleNested.parent() === doc; // true
doc.docArr[0].parent() === doc; // true

If you have a deeply nested subdoc, you can access the top-level document using the ownerDocument() function.

const schema = new Schema({
  level1: new Schema({
    level2: new Schema({
      test: String
const Model = mongoose.model('Test', schema);

const doc = new Model({ level1: { level2: 'test' } });

doc.level1.level2.parent() === doc; // false
doc.level1.level2.parent() === doc.level1; // true
doc.level1.level2.ownerDocument() === doc; // true

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 Subdocuments, let's take a look at querying.