Launch Week Day 3: Introducing Organization Notifications in Socket.Learn More
Socket
Book a DemoSign in
Socket

@palmetto/zod-mongoose-schema

Package Overview
Dependencies
Maintainers
7
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@palmetto/zod-mongoose-schema

latest
Source
npmnpm
Version
1.0.0
Version published
Maintainers
7
Created
Source

@palmetto/zod-mongoose-schema

Provides a builder to convert zod schemas to mongoose schemas.

Installation

First install peer dependencies:

yarn add zod

This package requires zod v4 schemas, you can either install v4 directly. or: import { z } from "zod/v4" in version 3.25.76+ if you still have dependencies that require zod v3 schemas. Note: zod@^4.1.5 or later is required for mongodb encryption to work correctly

yarn add @palmetto/zod-mongoose-schema

Getting started

  • Create a SchemaBuilder instance

    import { SchemaBuilder } from "@palmetto/zod-mongoose-schema";
    
    export const schemaBuilder = new SchemaBuilder();
    

Usage

Create a simple schema

import { z } from "zod";

import { SchemaWithId } from "@palmetto/zod-mongoose-schema";

export const UserEntitySchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
  // zod validations will be checked by mongoose when saving
  email: z.email(),
  // optional fields are supported
  phoneNumber: z.string().optional(),
  // default values are also supported
  loginCount: z.number().int().default(0),
});

/*
 * { firstName: string,
 *   lastName: string,
 *   email: string,
 *   phoneNumber?: string,
 *   loginCount: number }
 */
export type User = SchemaWithId<typeof UserEntitySchema>;

// it's often useful to create a type without auto-generated fields
export type NewUser = Omit<User, "_id", "loginCount">;

/*
 * Schema<User, Model<User>>
 */
export const UserSchema = schemaBuilder.build(UserEntitySchema);

Create re-usable schemas

import { z } from "zod";

import { SchemaWithId } from "@palmetto/zod-mongoose-schema";

const MetaEntitySchema = z
  .object({
    createdAt: z.date(),
    createdBy: z.string(),
  })
  .default(() => ({
    createdAt: new Date(),
    createdBy: "anonymous",
  }));

export const QuoteEntitySchema = z.object({
  // ...
  meta: MetaEntitySchema,
});

// ...

export const QuoteSchema = schemaBuilder.build(QuoteEntitySchema);

Create a discriminated Schema

SchemaBuilder supports both top-level and subdocument Schema discriminators using z.discriminatedUnion.

Some rules must be followed to allow SchemaBuilder to generate these Schemas automatically.

  • Each option of the z.discriminatedUnion must be a ZodObject.

  • Each value of the discriminator key in options must be a ZodLiteral with a string (or string enum) value.

export enum PricingProductType {
  PowerPurchase = "POWER_PURCHASE",
  Loan = "LOAN",
}

export const BaseQuotePricingEntitySchema = z.object({
  firstYearMonthlyPayment: z.number(),
  optionId: z.string(),
  productId: z.string(),
  provider: z.enum(PricingProvider),
});

export const QuotePricingEntitySchema = z.discriminatedUnion("productType", [
  BaseQuotePricingEntitySchema.extend({
    productType: z.literal(PricingProductType.PowerPurchase),
    rateEscalator: z.number(),
    kwhRate: z.number(),
    ppwRate: z.number(),
    totalEpcPayment: z.number(),
  }),
  BaseQuotePricingEntitySchema.extend({
    productType: z.literal(PricingProductType.Loan),
    apr: z.number(),
    termInMonths: z.number(),
  }),
]);

export type QuotePricing = z.infer<typeof QuotePricingEntitySchema>;

export type PowerPurchaseQuotePricing = Extract<
  QuotePricing,
  { productType: PricingProductType.PowerPurchase }
>;
export type LoanQuotePricing = Extract<
  QuotePricing,
  { productType: PricingProductType.Loan }
>;

// usage as a top-level schema works
const QuotePricingSchema = schemaBuilder.build(QuotePricingEntitySchema);

// usage as a subdocument also works
const QuoteSchema = schemaBuilder.build(
  z.object({
    // ...
    pricing: QuotePricingSchema.optional(),
  }),
);

Please note for libraries that also provide discriminator support (eg. NestJS), those options do not need to be used and should be ignored. The Schema built by SchemaBuilder already has the discriminators pre-configured.

Registries

SchemaBuilder uses zod v4 registries to manage schema customization. The builder ships with built-in registries for common mongoose types, and also allows adding your own.

Create an ObjectId field

Use idRegistry

The idRegistry built-in registry marks a field as an ObjectId and optionally allows passing a ref.

import { idRegistry } from "@palmetto/zod-mongoose-schema";
import { Types } from "mongoose";

export const QuoteEntitySchema = z.object({
  // ...
  assignedUserId: z
    .instanceof(Types.ObjectId)
    .register(idRegistry, { ref: "User" })
    .optional(),
});

Please note, the .register MUST be chained off the instanceof schema, if you chain off something else like .optional you will get a Typescript error. For more information, see: Metadata and Registries: Constraining Schema Types.

Use ObjectIdSchema

The ObjectIdSchema creates a zod field that's an ObjectId and registered to the idRegistry. Note: You lose the ability to specify the ref.

import { ObjectIdSchema } from "@palmetto/zod-mongoose-schema";

export const QuoteEntitySchema = z.object({
  // ...
  assignedUserId: ObjectIdSchema.optional(),
});

Create a fully custom Schema field

The schemaRegistry built-in registry allows passing in a fully customized mongoose Schema as-is to use as a subdocument.

import { schemaRegistry } from "@palmetto/zod-mongoose-schema";
import { Schema } from "mongoose";

const MetaSchema = new Schema(/* ... */);
type Meta = {
  /* ... */
};

export const QuoteEntitySchema = z.object({
  meta: z.custom<Meta>().register(schemaRegistry, { schema: MetaSchema }),
});

Create and customize subdocuments

The subSchemaRegistry built-in registry allows customizing schema options for subdocuments.

import { subSchemaRegistry } from "@palmetto/zod-mongoose-schema";

export const QuoteEntitySchema = z.object({
  // ...
  submissions: z
    .object({
      status: z.enum(QuoteSubmissionStatus),
      // ...
    })
    // _id is false by default for subdocuments
    .register(subSchemaRegistry, { _id: true })
    .array()
    .optional(),
});

Create a custom registry

import { RegistryEntry } from "zod-mongoose-schema";
import { z } from "zod";

export type BigIntRegistryMetadata = undefined;
export type BigIntRegistryTarget = z.$ZodBigInt;

export const bigIntRegistry = z.registry<
  BigIntRegistryMetadata,
  BigIntRegistryTarget
>();

schemaBuilder.addRegistry({
  registry: bigIntRegistry,
  filter: (type) => type._zod.def.type === "bigint",
  buildSchema: (type, _meta, context) => ({
    type: BigInt,
    required: !(context.fieldOptions?.isOptional ?? false),
    default: context.fieldOptions?.defaultValue,
    validate: [
      {
        validator: (input: unknown) => z.safeParse(type, input).success,
      },
    ],
  }),
});

const EntitySchema = z.object({
  foo: z.bigint().register(bigIntRegistry),
});

Encryption

You can enable client-side field-level encryption for many zod schema types. When using client-side encryption you must also set the autoEncryption settings when creating the mongo connection so that encryption happens automatically within the mongodb driver.

For details, see @palmetto/mongodb-encryption

Specifying an encrypted field

const ModelSchema = z.object({
    ...
  name: z.string(),
  ssn: z
    .string()
    .register(encryptedRegistry, { algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random', keyId: ssnDataEncryptionKeyId })
    .optional(),
  ssnLast4: z
    .string()
    .register(encryptedRegistry, { algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic', keyId: ssnLast4DataEncryptionKeyId })
    .optional(),
    ...
});

const ModelMongoSchema = schemaBuilder.build(ModelSchema, {
  versionKey: false,
  encryptionType: "csfle", // use csfle here
});

const Model = mongoose.model('collection-name', ModelMongoSchema);

const model = await Model.create({
  name: 'John Doe',
  ssn: '123-45-6789',
  ssnLast4: '6789',
});

const found = await Model.findOne({_id: model._id});

expect(found).toBeDefined();
expect(found.ssn).toBe('123-45-6789'); // encrypted fields are stored in mongodb using a binary data field: `binData(6, 'AgbwGsOriEfxuKzgTmH...')`
expect(found.ssnLast4).toBe('6789');

// cannot query by a field using Random encryption
const findBySsn = await Model.findOne({ssn: '123-45-6789'});
expect(findBySsn).toBeUndefined();

// can query by a field using Deterministic encryption
const findBySsnLast4 = await Model.findOne({ssnLast4:'6789'});
expect(findBySsnLast4).toBeDefined();

Supported zod types for encryption

The following zod types can be encrypted:

  • z.object()
  • z.discriminatedUnion()
  • z.string()
  • z.date()
  • z.boolean()
  • z.int()
  • z.int32()
  • z.float32()
  • z.float64()
  • z.instanceof(Buffer)
  • z.literal()
  • z.enum()
  • z.map()
  • z.array()
  • z.instanceof(mongoose.Types.ObjectId).register(idRegistry)

Encryption options

See EncryptedMetaData in encryption-registry.ts

  • algorithm: AEAD_AES_256_CBC_HMAC_SHA_512-Random | AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic (default: AEAD_AES_256_CBC_HMAC_SHA_512-Random)
    • when AEAD_AES_256_CBC_HMAC_SHA_512-Random:
      • the same plain value will encrypt to different binData each time
      • more secure storage since all cipher text is different
      • unable to be queried against
    • when AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic:
      • the same plain value will encrypt to the same cipher text
      • this is less secure since it can show that two or more records have the same plain value when both cipher text are the same
      • however, the property can be queried against with normal mongodb filters
  • keyId: UUID
    • This property contains the UUID of the data encryption key created with mongodb's ClientEncryption object.
    • You must know the keyId before creating the schema object, so you must also manage the keys on your own.
    • Or, leave this field out and let @palmetto/mongdb-encryption manage the keys for you.

More details about the algorithm at MongoDB encryption algorithms) For more information about encrypted schemas at Encryption Schemas

More documentation

FAQs

Package last updated on 15 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts