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

zod-firebase-admin

v2.1.1

Published

zod firebase-admin schema

Downloads

2,311

Readme

zod-firebase-admin

npm version License: MIT CI codecov

Type-safe Firestore collections and documents using Zod schemas for the Firebase Admin SDK.

Installation

Peer dependencies: firebase-admin and zod.

npm install zod-firebase-admin zod firebase-admin

Node.js >= 22 is recommended (matches the library engines field). ESM and CJS bundles are provided.

Usage

Basic Setup

First, define your document schemas using Zod:

import { z } from 'zod'
import { collectionsBuilder } from 'zod-firebase-admin'

// Define your document schemas
const UserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().optional(),
  tags: z.array(z.string()).optional().default([]),
})

const PostSchema = z.object({
  title: z.string(),
  content: z.string(),
  authorId: z.string(),
  publishedAt: z.date(),
  likes: z.number().default(0),
})

// Define your collection schema
const schema = {
  users: {
    zod: UserSchema,
  },
  posts: {
    zod: PostSchema,
  },
} as const

// Build type-safe collections
const collections = collectionsBuilder(schema)

CRUD Operations

// Create a new user
const userRef = await collections.users.add({
  name: 'John Doe',
  email: '[email protected]',
  age: 30,
})

// Get a user by ID
const user = await collections.users.findByIdOrThrow(userRef.id)
console.log(user._id, user.name, user.email) // Fully typed!

// Update a user
await collections.users.update(userRef.id, {
  age: 31,
})

// Query users
const adults = await collections.users.findMany({
  name: 'adults',
  where: [['age', '>=', 18]],
})

// Delete a user
await collections.users.delete(userRef.id)

Fallback reads

Return a document if it exists or a validated fallback when it does not.

// Multi-document collection: findByIdWithFallback(id, fallback)
const post = await collections.posts.findByIdWithFallback('post123', {
  title: 'Untitled',
  content: '',
  authorId: 'anonymous',
  publishedAt: new Date(),
  likes: 0,
})
// If the document exists, you get its data; otherwise you get:
// { _id: 'post123', title: 'Untitled', content: '', ... }

// Single-document collection: findWithFallback(fallback)
const userId = 'user123'
const profile = await collections.users(userId).profile.findWithFallback({
  bio: 'This user has not set up a bio yet',
  avatar: undefined,
})
// If the document does not exist, you get { _id: 'profile', ...fallback }

When your schema validates document IDs (includeDocumentIdForZod), the fallback is validated with the injected _id:

const UserWithIdSchema = z.discriminatedUnion('_id', [
  z.object({
    _id: z.literal('admin'),
    name: z.string(),
    role: z.literal('administrator'),
  }),
  z.object({
    _id: z.string(),
    name: z.string(),
    role: z.literal('user'),
  }),
])

const schema = {
  users: {
    zod: UserWithIdSchema,
    includeDocumentIdForZod: true,
  },
} as const

const collections = collectionsBuilder(schema)

// Fallback excludes _id; it will be injected and validated by Zod
const admin = await collections.users.findByIdWithFallback('admin', {
  name: 'System',
  role: 'administrator',
})

Sub-Collections

You can define nested sub-collections with full type safety:

const schema = {
  users: {
    zod: UserSchema,
    posts: {
      zod: PostSchema,
      // Sub-sub-collections
      comments: {
        zod: z.object({
          text: z.string(),
          authorId: z.string(),
          createdAt: z.date(),
        }),
      },
    },
    // Single document sub-collection
    profile: {
      zod: z.object({
        bio: z.string(),
        avatar: z.string().optional(),
      }),
      singleDocumentKey: 'profile', // Fixed document ID
    },
  },
} as const

const collections = collectionsBuilder(schema)

// Working with sub-collections
const userId = 'user123'

// Add a post to user's posts sub-collection
const postRef = await collections.users(userId).posts.add({
  title: 'My First Post',
  content: 'Hello world!',
  authorId: userId,
  publishedAt: new Date(),
})

// Add a comment to the post
await collections.users(userId).posts(postRef.id).comments.add({
  text: 'Great post!',
  authorId: 'commenter123',
  createdAt: new Date(),
})

// Work with single document sub-collection
await collections.users(userId).profile.set({
  bio: 'Software developer',
  avatar: 'https://example.com/avatar.jpg',
})

const profile = await collections.users(userId).profile.findOrThrow()

Collection Group Queries

Query across all sub-collections of the same type:

// Count all posts across all users
const postCount = await collections.users.posts.group.count({
  name: 'all-posts',
})

// Count all comments across all posts
const commentCount = await collections.users.posts.comments.group.count({
  name: 'all-comments',
})

Advanced Schema Options

Custom Error Handling

const collections = collectionsBuilder(schema, {
  zodErrorHandler: (error, snapshot) => {
    console.error(`Validation error for document ${snapshot.id}:`, error)
    return new Error(`Invalid document: ${snapshot.id}`)
  },
})

Data Transformation

Handle Firebase-specific data types like Timestamps:

import { Timestamp } from 'firebase-admin/firestore'

const collections = collectionsBuilder(schema, {
  snapshotDataConverter: (snapshot) => {
    const data = snapshot.data()
    // Convert Firestore Timestamps to JavaScript Dates
    return Object.fromEntries(
      Object.entries(data).map(([key, value]) => [key, value instanceof Timestamp ? value.toDate() : value]),
    )
  },
})

Document ID Validation

Include document IDs in Zod validation:

const UserWithIdSchema = z.discriminatedUnion('_id', [
  z.object({
    _id: z.literal('admin'),
    name: z.string(),
    role: z.literal('administrator'),
  }),
  z.object({
    _id: z.string(),
    name: z.string(),
    role: z.literal('user'),
  }),
])

const schema = {
  users: {
    zod: UserWithIdSchema,
    includeDocumentIdForZod: true,
  },
} as const

Read-Only Documents

Mark collections as read-only to prevent accidental modifications:

const schema = {
  config: {
    zod: ConfigSchema,
    readonlyDocuments: true,
  },
} as const

// This collection will only have read operations available
const collections = collectionsBuilder(schema)

Transactions and Batches

Work with Firebase Admin transactions and batch writes:

import { getFirestore } from 'firebase-admin/firestore'

const db = getFirestore()

// Using transactions
await db.runTransaction(async (transaction) => {
  const userSnap = await transaction.get(collections.users.read.doc('user123'))
  if (!userSnap.exists) return
  transaction.update(collections.users.write.doc('user123'), { age: FieldValue.increment(1) })
})

// Using batch writes
const batch = db.batch()
batch.create(collections.posts.write.doc('post123'), {
  title: 'Batch Post',
  authorId: 'user123',
})
await batch.commit()

Firebase Admin SDK Features

Take advantage of Firebase Admin SDK server-side capabilities:

// Precondition checks with last update time
const user = await collections.users.findByIdOrThrow('user123', {
  _updateTime: true,
})

await collections.users.update(
  'user123',
  {
    name: 'Updated Name',
  },
  {
    lastUpdateTime: user._updateTime, // Prevents concurrent modifications
  },
)

// Server timestamps and field values
import { FieldValue } from 'firebase-admin/firestore'

await collections.users.update('user123', {
  lastLoginAt: FieldValue.serverTimestamp(),
  loginCount: FieldValue.increment(1),
})

Query Features

Advanced Queries

// Complex queries with multiple conditions
const recentPopularPosts = await collections.posts.findMany({
  where: [
    ['publishedAt', '>=', new Date('2024-01-01')],
    ['likes', '>=', 100],
  ],
  orderBy: [['likes', 'desc']],
  limit: 10,
})

// Prepared queries for reuse
const popularPrepared = collections.posts.prepare({
  name: 'popular',
  where: [['likes', '>=', 100]],
  orderBy: [['likes', 'desc']],
})
const snapshot = await popularPrepared.get()
const results = snapshot.docs.map((d) => d.data())

QuerySpecification

The QuerySpecification accepted by prepare, query, find*, and count supports:

  • name: A label for your query, used in error messages
  • where?: Either an array of tuples [field, op, value] or an Admin Filter
  • orderBy?: Array of tuples [field] or [field, 'asc' | 'desc']
  • limit?: Maximum number of results
  • limitToLast?: Returns the last N results; requires a matching orderBy
  • offset?: Skip the first N results
  • startAt? | startAfter? | endAt? | endBefore?: Cursor boundaries, each can be a document snapshot or an array of field values
    • Arrays are forwarded as individual arguments (e.g. startAt(...values)). Ensure the order matches your orderBy
    • Docs: Query cursors

Related:

Metadata Access

Access Firestore metadata when needed:

// Get document with metadata
const userWithMeta = await collections.users.findByIdOrThrow(userId, {
  _createTime: true,
  _updateTime: true,
})

console.log(userWithMeta._createTime, userWithMeta._updateTime)

API Reference

Types

  • DocumentInput<Z>: Input type inferred with z.input<Z> from your Zod schema Z (data you write). See Zod’s input/output docs
  • DocumentOutput<Z, Options>: Output type inferred with z.output<Z> from your Zod schema Z, plus optional metadata based on Options:
    • _id (string) included by default unless { _id: false }
    • _createTime / _updateTime available via operation options when reading, _metadata for web parity is not used here
    • When { readonly: true }, the data portion is deeply readonly
  • SchemaDocumentInput<TCollectionSchema>: Input type for a collection built from a Zod schema; accepts either the input type or a deeply readonly version of it
  • SchemaDocumentOutput<TCollectionSchema, Options>: Output type for a collection built from a Zod schema, mirroring DocumentOutput behavior and honoring readonlyDocuments in the collection schema

Examples:

import { z } from 'zod'

const User = z.object({
  name: z.string(),
  admin: z.boolean().default(false),
})

type UserInput = DocumentInput<typeof User>
type UserOutput = DocumentOutput<typeof User>
type ReadonlyUserOutput = DocumentOutput<typeof User, { readonly: true }>

// With collectionsBuilder
const schema = {
  users: { zod: User },
} as const

type UsersInput = SchemaDocumentInput<typeof schema.users>
// { name: string; admin?: boolean } | ReadonlyDeep<{ name: string; admin?: boolean }>

type UsersOutput = SchemaDocumentOutput<typeof schema.users>
// { _id: string; name: string; admin: boolean }

// Include Admin metadata in outputs
type UsersOutputWithTimes = SchemaDocumentOutput<typeof schema.users, { _createTime: true; _updateTime: true }>
// { _id: string; _createTime: Timestamp; _updateTime: Timestamp; name: string; admin: boolean }

// Readonly collection
const readonlySchema = {
  users: { zod: User, readonlyDocuments: true },
} as const

type ReadonlyUsersOutput = SchemaDocumentOutput<typeof readonlySchema.users>
// ReadonlyDeep<{ name: string; admin: boolean }> & { _id: string }

Collection Methods

  • add(data) - Add a new document with auto-generated ID
  • create(id, data) - Create a document with specific ID
  • set(id, data, options?) - Set document data (overwrites)
  • update(id, data, options?) - Update document fields
  • delete(id, options?) - Delete a document
  • findById(id, options?) - Find document by ID (returns undefined if not found)
  • findByIdOrThrow(id, options?) - Find document by ID (throws if not found)
  • findMany(query) - Query multiple documents
  • count(query) - Count documents matching query
  • prepare(query) - Prepare a query for reuse

Sub-Collection Access

  • collection(parentId).subCollection - Access sub-collection
  • collection.subCollection.group - Access collection group

Configuration Options

  • zodErrorHandler - Custom error handling for validation failures
  • snapshotDataConverter - Transform document data before validation
  • includeDocumentIdForZod - Include document ID in Zod validation
  • readonlyDocuments - Mark collection as read-only
  • singleDocumentKey - Create single-document sub-collections

Operation Options

  • transaction - Run operation within a transaction
  • batch - Add operation to a batch write
  • lastUpdateTime - Precondition check for updates
  • _createTime / _updateTime - Include metadata in results

Firebase Admin vs Web SDK

This package is designed for server-side applications using the Firebase Admin SDK. Key differences from the web SDK version (zod-firebase):

  • Server Environment: Runs in Node.js with admin privileges
  • No Authentication: Admin SDK bypasses Firebase Auth
  • Transactions: Full transaction support with preconditions
  • Batch Operations: Atomic batch writes
  • Server Timestamps: Access to server-side timestamp operations
  • Metadata Access: Read/write times and other document metadata

For client-side applications, use zod-firebase instead.

License

MIT

Contributing

See the main repository at valian-ca/zod-firebase-admin for contributing guidelines.