npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mongoose-aes-encryption

v1.1.2

Published

Mongoose encryption plugin for MongoDB: field-level AES-256-GCM with tamper detection

Readme

License: MIT npm version node version Build Status Coverage Status

mongoose-aes-encryption

Mongoose encryption plugin for MongoDB providing field-level AES-256-GCM encryption-at-rest with built-in tamper detection.

Secure sensitive fields such as passwords, PII, tokens, and secrets while keeping your application logic unchanged.

Installation

npm install mongoose-aes-encryption

Quick Example

const mongoose = require('mongoose');
const createAESPlugin = require('mongoose-aes-encryption');

const plugin = createAESPlugin({ key: process.env.ENCRYPTION_KEY });

const userSchema = new mongoose.Schema({
    token: { type: String, encrypted: true },
    pin: { type: Number, encrypted: true }
});

userSchema.plugin(plugin);

MongoDB stores only ciphertext — your application reads and writes plain values.

Already have an existing MongoDB with plaintext data or want to upgrade from another encryption plugin? See the migration section.

What this package does

✅ Field-level encryption for Mongoose schemas
✅ Transparent encryption on save, decryption on read
✅ AES-256-GCM authenticated encryption
✅ Tamper detection for encrypted values
✅ Works with nested sub-documents, sub-schemas, and arrays
✅ Zero production dependencies — uses Node.js built-in crypto module only

❌ Not full-database encryption
❌ Not a replacement for MongoDB Atlas encryption at rest

Usage

Suppose you have the following Mongoose schema with sensitive fields:

const schema = new mongoose.Schema({
    username:     { type: String },
    email:        { type: String },
    salary:       { type: Number },
    phoneNumbers: { type: [String] }
});

To encrypt email, salary, and phoneNumbers at rest using AES-GCM, add two lines of setup and one flag per field:

const createAESPlugin = require('mongoose-aes-encryption');
const plugin = createAESPlugin({ key: process.env.ENCRYPTION_KEY });

const schema = new mongoose.Schema({
    username:     { type: String },
    email:        { type: String,   encrypted: true },
    salary:       { type: Number,   encrypted: true },
    phoneNumbers: { type: [String], encrypted: true }
});
schema.plugin(plugin);

That's it — the rest of your code is unchanged:

const User = mongoose.model('User', schema);

const user = new User({ username: 'alice', email: '[email protected]', salary: 75000, phoneNumbers: ['+1-555-0100', '+1-555-0101'] });
await user.save();
// MongoDB stores:
// { username: 'alice', email: '<iv|ciphertext|authTag>', salary: '<iv|ciphertext|authTag>',
//   phoneNumbers: ['<iv|ciphertext|authTag>', '<iv|ciphertext|authTag>'] }

const found = await User.findOne({ username: 'alice' });
// Result: found.email        === '[email protected]'                    (transparently decrypted)
// Result: found.salary       === 75000                                  (transparently decrypted)
// Result: found.phoneNumbers deep-equals ['+1-555-0100', '+1-555-0101'] (each element transparently decrypted)

Inline nested sub-documents

Encrypted fields inside inline nested objects work automatically.

const schema = new mongoose.Schema({
    id: { type: String, required: true },
    address: {
        street: { type: String, encrypted: true },
        city: { type: String }
    }
});

schema.plugin(plugin);

Separate sub-schemas

Apply the plugin to both the parent schema and the sub-schema.

const contactSchema = new mongoose.Schema({
    email: { type: String, encrypted: true },
    phone: { type: String }
});

contactSchema.plugin(plugin);

const employeeSchema = new mongoose.Schema({
    name: { type: String, encrypted: true },
    contacts: [contactSchema]
});

employeeSchema.plugin(plugin);

Lean queries

Mongoose .lean() bypasses getters and returns the raw ciphertext stored in MongoDB. To decrypt manually, use the exported decrypt function directly:

const { decrypt } = require('mongoose-aes-encryption');
const key = process.env.ENCRYPTION_KEY;

const doc = await User.findOne({ username: 'alice' }).lean();

const email    = decrypt(doc.email, { key });                    // → string
const salary   = parseFloat(decrypt(doc.salary, { key }));       // → number
const dob      = new Date(decrypt(doc.birthDate, { key }));      // → Date
const mfaEnabled = decrypt(doc.mfaEnabled, { key }) === 'true';      // → boolean

Update method compatibility

Encryption is done automatically when a value is assigned through the Mongoose document lifecycle (new/save() or findOne() + mutate + save()). Operations that write directly to the database — updateOne, updateMany, findOneAndUpdate, bulkWrite, and atomic operators like $inc/$push — bypass the lifecycle and require manual use of the exported encrypt function.

| Operation | Support | Notes | |---|---|---| | new Model({ field: v }); doc.save() | Automatic | Full getter/setter round-trip. Standard path. | | Model.create({ field: v }) | Automatic | Equivalent to new + save(). | | doc.field = v; doc.save() (after findOne()) | Automatic | Full getter/setter round-trip. | | .lean() query | Manual | Getter does not fire; use decrypt(doc.field, { key }) on each ciphertext field. | | Model.findOneAndUpdate(…, { $set: { field: v } }) | Manual | Bypasses document lifecycle; use encrypt(String(v), { key }) and pass the result as the $set value. | | Model.updateOne(…, { $set: { field: v } }) | Manual | Same as above. | | Model.updateMany(…, { $set: { field: v } }) | Manual | Same as above — pre-encrypt each value with encrypt() before passing to $set. | | Model.findOneAndUpdate(…, { $inc: { field: n } }) | Manual | Cannot $inc ciphertext. Use findOne()doc.field += ndoc.save() instead. | | Model.findOneAndUpdate(…, { $push: { field: v } }) | Manual | Cannot $push plaintext into an encrypted array. Use findOne()doc.arr.push(v)doc.save(), or pre-encrypt v with encrypt(String(v), { key }) and pass to $push. | | Model.bulkWrite() with updateOne/updateMany ops | Manual | Same as updateOne/updateMany — pre-encrypt each value with encrypt() before building the bulk operations. |

Example — manual $set with pre-encryption:

const { encrypt } = require('mongoose-aes-encryption');
const key = process.env.ENCRYPTION_KEY;

const cipher = encrypt(String(newPrice), { key });
await Product.updateOne({ id: 'p-1' }, { $set: { price: cipher } });

Example — manual increment workaround:

const doc = await Product.findOne({ id: 'p-1' });
doc.stock += 1;
await doc.save();

API Reference

createAESPlugin(options)

Creates and returns a Mongoose plugin function that encrypts and decrypts schema fields. Call this once — before defining any schema that uses encrypted fields — and apply the returned plugin to each schema with schema.plugin().

Parameters:

  • options (Object): Configuration object.
    • options.key (string): 64-character hex string (32 bytes). Required.
    • options.algorithm (string, optional): Encryption algorithm. 'aes-256-gcm' (default) or 'aes-256-cbc'.

Returns: Function — Mongoose plugin function, ready to pass to schema.plugin().

Example:

const mongoose = require('mongoose');
const createAESPlugin = require('mongoose-aes-encryption');

const plugin = createAESPlugin({ key: process.env.ENCRYPTION_KEY });

const schema = new mongoose.Schema({
    name:       { type: String },
    email:      { type: String,  encrypted: true },
    birthDate:  { type: Date,    encrypted: true },
    salary:     { type: Number,  encrypted: true },
    mfaEnabled: { type: Boolean, encrypted: true }
});
schema.plugin(plugin);

encrypt(value, options)

Encrypts a plaintext string and returns the ciphertext in wire format (iv|ciphertext|authTag for GCM, iv|ciphertext for CBC).

Parameters:

  • value (string): Plaintext to encrypt. Pass null or undefined to get null back unchanged.
  • options (Object):
    • options.key (string): 64-character hex key. Required.
    • options.algorithm (string, optional): 'aes-256-gcm' (default) or 'aes-256-cbc'.

Returns: string — encrypted ciphertext, or null/undefined if value was nullish.

Example:

const { encrypt } = require('mongoose-aes-encryption');
const key = process.env.ENCRYPTION_KEY;

// Pre-encrypt before a $set that bypasses Mongoose middleware
const cipher = encrypt(String(newPrice), { key });
await Product.updateOne({ id: 'p-1' }, { $set: { price: cipher } });

decrypt(value, options)

Decrypts a ciphertext string previously produced by encrypt and returns the plaintext.

Parameters:

  • value (string): Ciphertext in wire format. Pass null or undefined to get null back unchanged.
  • options (Object):
    • options.key (string): 64-character hex key. Required.

Returns: string — decrypted plaintext, or null/undefined if value was nullish.

Example:

const { decrypt } = require('mongoose-aes-encryption');
const key = process.env.ENCRYPTION_KEY;

// Manually decrypt fields from a lean() query
const doc = await Product.findOne({ id: 'p-1' }).lean();
const price = parseFloat(decrypt(doc.price, { key }));

Setup

  1. Generate a 32-byte encryption key:

    openssl rand -hex 32
  2. Store the key securely — an environment variable or a secrets manager. Never hardcode it.

    export ENCRYPTION_KEY=<your-64-char-hex-key>
  3. Call createAESPlugin() once, before any schema that uses encrypted fields is defined:

    const createAESPlugin = require('mongoose-aes-encryption');
    const plugin = createAESPlugin({ key: process.env.ENCRYPTION_KEY });
  4. Apply the plugin to each schema and mark sensitive fields with encrypted: true:

    const schema = new mongoose.Schema({
        name:  { type: String },
        email: { type: String, encrypted: true }
    });
    schema.plugin(plugin);

Security

AES-256-GCM — authenticated encryption with tamper detection

By default, mongoose-aes-encryption uses AES-256-GCM, an authenticated encryption mode. Every encrypted value is stored in MongoDB as a pipe-delimited string:

iv|ciphertext|authTag

The authTag is a cryptographic MAC computed over the ciphertext. On every read the authentication tag is verified before decryption. If the stored value has been modified in any way — bit-flip, truncation, or wholesale substitution — the tag check fails and decryption throws immediately. Corrupted or tampered ciphertext can never be silently read back as incorrect plaintext.

AES-256-CBC (available as algorithm: 'aes-256-cbc' for backwards compatibility) uses the wire format iv|ciphertext and provides no tamper detection.

Lean queries expose raw ciphertext

.lean() results bypass Mongoose getters entirely. The raw iv|ciphertext|authTag string is returned as-is. If your application uses lean queries on collections that contain encrypted fields, treat those fields as opaque ciphertext and decrypt them explicitly using the exported decrypt function — see Lean queries and Update method compatibility.

Null values

null fields are stored as null in MongoDB without encryption. Do not rely on null values being confidential.

Migration

mongoose-aes-encryption-migrate is a companion CLI and programmatic tool for migrating existing MongoDB collections to mongoose-aes-encryption safely and idempotently. It processes documents in configurable batches, supports a --dry-run mode, and skips documents that are already encrypted so it can be re-run without side effects.

Supported migration sources:

| Source | Package | |---|---| | Plaintext (no prior encryption) | — | | Field-level CBC encryption | mongoose-field-encryption | | Document-level CBC + HMAC | mongoose-encryption |

npx mongoose-aes-encryption-migrate --source plaintext --model User
npx mongoose-aes-encryption-migrate --source mongoose-field-encryption --model User
npx mongoose-aes-encryption-migrate --source mongoose-encryption --model User

How it compares to other popular Mongoose encryption plugins

| | mongoose-field-encryption | mongoose-encryption | mongoose-aes-encryption | |---|---|---|---| | Maintenance status | Active | Last release Nov 2021 | Active | | Default algorithm | AES-256-CBC | AES-256-CBC | AES-256-GCM | | Tamper detection | No | Via separate HMAC-SHA-512 | GCM auth tag (built-in) | | Encryption granularity | Per field | Whole document (_ct blob) | Per field | | Supported field types | String, Number, Date, Boolean | All (JSON-serialised into blob) | String, Number, Date, Boolean, arrays, nested docs | | Schema pollution | Yes — __enc_* marker fields | Yes — _ct, _ac fields | No | | lean() decrypt helper | No | No | Yes — exported decrypt() | | Migration tool available | No | No | Yes — mongoose-aes-encryption-migrate |

License

MIT