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

@prmichaelsen/firebase-admin-sdk-v8

v2.9.0

Published

Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs

Readme

Firebase Admin SDK v8

Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs

npm version License: MIT Unit Tests E2E Tests codecov

This library provides Firebase Admin SDK functionality for Cloudflare Workers and other edge runtimes. It uses REST APIs and JWT token generation instead of the Node.js Admin SDK, making it compatible with environments that don't support Node.js.

✨ Features

  • Zero Dependencies - No external dependencies, pure Web APIs (crypto.subtle, fetch)
  • JWT Token Generation - Service account authentication
  • ID Token Verification - Verify Firebase ID tokens (supports v9 and v10 formats)
  • Session Cookies - Create and verify long-lived session cookies (up to 14 days)
  • User Management - Create, read, update, delete users via Admin API
  • Firebase v10 Compatible - Supports both old and new token issuer formats
  • Firestore REST API - Full CRUD operations via REST
  • Field Value Operations - increment, arrayUnion, arrayRemove, serverTimestamp, delete
  • Advanced Queries - where, orderBy, limit, offset, cursor pagination
  • Batch Operations - Atomic multi-document writes
  • Merge Operations - Merge documents with existing data
  • OAuth Access Tokens - Generate admin API access tokens
  • Token Caching - Automatic token refresh before expiry
  • TypeScript - Full type definitions included

📦 Installation

npm install @prmichaelsen/firebase-admin-sdk-v8

🚀 Quick Start

1. Initialize the SDK

Option A: Cloudflare Workers / Edge Runtimes (Recommended)

import { initializeApp, verifyIdToken } from '@prmichaelsen/firebase-admin-sdk-v8';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Initialize with env variables
    initializeApp({
      serviceAccount: env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY,
      projectId: env.FIREBASE_PROJECT_ID
    });

    // Now use the SDK
    const token = request.headers.get('authorization')?.split('Bearer ')[1];
    const user = await verifyIdToken(token);
    
    return new Response(JSON.stringify({ user }));
  }
};

Option B: Node.js / Traditional Environments

import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';

// Option 1: Explicit initialization
initializeApp({
  serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
  projectId: process.env.FIREBASE_PROJECT_ID
});

// Option 2: Auto-detect from process.env (no initialization needed)
// The SDK will automatically use process.env if initializeApp() is not called

Environment Variables (if not using initializeApp):

FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{"type":"service_account",...}'
FIREBASE_PROJECT_ID=your-project-id

2. Verify ID Tokens

import { verifyIdToken, getUserFromToken } from '@prmichaelsen/firebase-admin-sdk-v8';

const authHeader = request.headers.get('authorization');
const idToken = authHeader?.split('Bearer ')[1];

try {
  const user = await getUserFromToken(idToken);
  console.log('User:', user.email, user.displayName);
} catch (error) {
  return new Response('Invalid token', { status: 401 });
}

3. Session Cookies (Long-Lived Sessions)

import { createSessionCookie, verifySessionCookie } from '@prmichaelsen/firebase-admin-sdk-v8';

// Create 14-day session cookie from ID token
const sessionCookie = await createSessionCookie(idToken, {
  expiresIn: 60 * 60 * 24 * 14 * 1000
});

// Set as HTTP-only cookie
response.headers.set('Set-Cookie',
  `session=${sessionCookie}; Max-Age=1209600; HttpOnly; Secure; SameSite=Strict`
);

// Verify session cookie
const user = await verifySessionCookie(cookie);

4. Basic Firestore Operations

import { setDocument, getDocument, updateDocument, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';

// Set a document (create or overwrite)
await setDocument('users', 'user123', {
  name: 'John Doe',
  email: '[email protected]',
  createdAt: FieldValue.serverTimestamp(),
});

// Get a document
const user = await getDocument('users', 'user123');

// Update with field values
await updateDocument('users', 'user123', {
  loginCount: FieldValue.increment(1),
  lastLogin: FieldValue.serverTimestamp(),
});

4. Advanced Queries

import { queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';

const activeUsers = await queryDocuments('users', {
  where: [
    { field: 'active', op: '==', value: true },
    { field: 'age', op: '>=', value: 18 }
  ],
  orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
  limit: 10
});

📚 API Reference

Authentication

verifyIdToken(idToken: string): Promise<DecodedIdToken>

Verify a Firebase ID token and return the decoded token.

const decoded = await verifyIdToken(idToken);
console.log('User ID:', decoded.uid);

getUserFromToken(idToken: string): Promise<UserInfo>

Get user information from a verified ID token.

const user = await getUserFromToken(idToken);
// Returns: { uid, email, emailVerified, displayName, photoURL }

User Management

getUserByEmail(email: string): Promise<UserRecord | null>

Look up a Firebase user by email address.

const user = await getUserByEmail('[email protected]');
if (user) {
  console.log('User ID:', user.uid);
  console.log('Email verified:', user.emailVerified);
}

getUserByUid(uid: string): Promise<UserRecord | null>

Look up a Firebase user by UID.

const user = await getUserByUid('user123');
if (user) {
  console.log('Email:', user.email);
  console.log('Display name:', user.displayName);
}

createUser(properties: CreateUserRequest): Promise<UserRecord>

Create a new Firebase user.

const newUser = await createUser({
  email: '[email protected]',
  password: 'securePassword123',
  displayName: 'New User',
  emailVerified: false,
});
console.log('Created user:', newUser.uid);

updateUser(uid: string, properties: UpdateUserRequest): Promise<UserRecord>

Update an existing Firebase user.

const updatedUser = await updateUser('user123', {
  displayName: 'Updated Name',
  photoURL: 'https://example.com/photo.jpg',
  emailVerified: true,
});

deleteUser(uid: string): Promise<void>

Delete a Firebase user.

await deleteUser('user123');

listUsers(maxResults?: number, pageToken?: string): Promise<ListUsersResult>

List all users with pagination.

// List first 100 users
const result = await listUsers(100);
console.log('Users:', result.users.length);

// Get next page
if (result.pageToken) {
  const nextPage = await listUsers(100, result.pageToken);
}

setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>

Set custom claims on a user's ID token for role-based access control.

// Set custom claims
await setCustomUserClaims('user123', {
  role: 'admin',
  premium: true,
  permissions: ['read', 'write', 'delete'],
});

// Clear custom claims
await setCustomUserClaims('user123', null);

Firestore - Basic Operations

setDocument(collectionPath, documentId, data, options?): Promise<void>

Create or overwrite a document. Supports merge options.

// Overwrite
await setDocument('users', 'user123', { name: 'John', age: 30 });

// Merge with existing
await setDocument('users', 'user123', { age: 31 }, { merge: true });

// Merge specific fields
await setDocument('users', 'user123', { age: 31, city: 'NYC' }, { 
  mergeFields: ['age'] 
});

addDocument(collectionPath, data, documentId?): Promise<DocumentReference>

Add a document with auto-generated or custom ID. Returns a DocumentReference with id and path properties.

const docRef = await addDocument('posts', { title: 'Hello' });
console.log('Created:', docRef.id); // Auto-generated ID

const customDocRef = await addDocument('posts', { title: 'Hi' }, 'custom-id');
console.log('Created:', customDocRef.id); // 'custom-id'

getDocument(collectionPath, documentId): Promise<DataObject | null>

Get a document by ID.

const user = await getDocument('users', 'user123');

updateDocument(collectionPath, documentId, data): Promise<void>

Update specific fields in a document.

await updateDocument('users', 'user123', { lastLogin: new Date() });

deleteDocument(collectionPath, documentId): Promise<void>

Delete a document.

await deleteDocument('users', 'user123');

Firestore - Field Values

FieldValue.serverTimestamp()

Set field to server timestamp.

await setDocument('posts', 'post1', {
  createdAt: FieldValue.serverTimestamp()
});

FieldValue.increment(n)

Increment a numeric field.

await updateDocument('users', 'user1', {
  loginCount: FieldValue.increment(1),
  points: FieldValue.increment(10)
});

FieldValue.arrayUnion(...elements)

Add elements to an array (no duplicates).

await updateDocument('posts', 'post1', {
  tags: FieldValue.arrayUnion('javascript', 'typescript')
});

FieldValue.arrayRemove(...elements)

Remove elements from an array.

await updateDocument('posts', 'post1', {
  tags: FieldValue.arrayRemove('outdated')
});

FieldValue.delete()

Delete a field from a document.

await updateDocument('users', 'user1', {
  temporaryField: FieldValue.delete()
});

Firestore - Queries

queryDocuments(collectionPath, options?): Promise<Array<{ id, data }>>

Query documents with advanced filtering.

Query Options:

  • where: Array of filters { field, op, value }
  • orderBy: Array of orders { field, direction }
  • limit: Maximum number of results
  • offset: Number of results to skip
  • startAt, startAfter, endAt, endBefore: Cursor pagination

Where Operators:

  • ==, !=, <, <=, >, >=
  • array-contains, array-contains-any
  • in, not-in
// Simple query
const users = await queryDocuments('users');

// With filters
const activeAdults = await queryDocuments('users', {
  where: [
    { field: 'active', op: '==', value: true },
    { field: 'age', op: '>=', value: 18 }
  ]
});

// With ordering and limit
const topUsers = await queryDocuments('users', {
  where: [{ field: 'active', op: '==', value: true }],
  orderBy: [{ field: 'points', direction: 'DESCENDING' }],
  limit: 10
});

// Cursor pagination
const results = await queryDocuments('users', {
  orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],
  startAfter: [lastCreatedAt],
  limit: 20
});

Firestore - Batch Operations

batchWrite(operations): Promise<BatchWriteResult>

Perform multiple write operations atomically.

await batchWrite([
  {
    type: 'set',
    collectionPath: 'users',
    documentId: 'user1',
    data: { name: 'John' }
  },
  {
    type: 'update',
    collectionPath: 'users',
    documentId: 'user2',
    data: { lastLogin: FieldValue.serverTimestamp() }
  },
  {
    type: 'delete',
    collectionPath: 'users',
    documentId: 'user3'
  }
]);

Token Generation

getAdminAccessToken(): Promise<string>

Get an OAuth access token for Firebase Admin API. Automatically cached and refreshed.

const token = await getAdminAccessToken();

clearTokenCache(): void

Clear the cached access token.

clearTokenCache();

Storage - Resumable Uploads

uploadFileResumable(bucket, path, data, contentType, options?): Promise<FileMetadata>

Upload large files with resumable upload support. Suitable for files >10MB, unreliable networks, or when progress tracking is needed.

Features:

  • ✅ Chunked uploads (configurable chunk size)
  • ✅ Progress tracking with callbacks
  • ✅ Resume interrupted uploads
  • ✅ Memory efficient (doesn't load entire file at once)
  • ✅ Automatic retry on chunk failure
import { uploadFileResumable } from '@prmichaelsen/firebase-admin-sdk-v8';

// Upload large file with progress tracking
const data = await fetch('https://example.com/large-video.mp4');
const buffer = await data.arrayBuffer();

const metadata = await uploadFileResumable(
  'my-bucket.appspot.com',
  'videos/large.mp4',
  buffer,
  'video/mp4',
  {
    chunkSize: 512 * 1024, // 512KB chunks (default: 256KB)
    onProgress: (uploaded, total) => {
      const percent = (uploaded / total * 100).toFixed(2);
      console.log(`Upload progress: ${percent}%`);
    },
    metadata: { userId: '123', category: 'videos' },
  }
);

console.log('Upload complete:', metadata);

Resume interrupted upload:

let sessionUri: string;

try {
  const metadata = await uploadFileResumable(
    bucket,
    path,
    data,
    contentType,
    {
      onProgress: (uploaded, total) => {
        // Save session URI for resume
        sessionUri = /* get from response */;
      },
    }
  );
} catch (error) {
  // Resume from where it left off
  const metadata = await uploadFileResumable(
    bucket,
    path,
    data,
    contentType,
    {
      resumeToken: sessionUri, // Resume from previous session
    }
  );
}

When to use:

  • Files larger than 10MB
  • Unreliable network conditions
  • Need progress reporting
  • Files that may exceed memory limits

When to use simple uploadFile() instead:

  • Small files (<10MB)
  • Reliable network
  • No progress tracking needed
  • Edge runtime with memory constraints

💡 Examples

See EXAMPLES.md for comprehensive examples including:

  • Authentication patterns
  • Field value operations
  • Complex queries
  • Batch operations
  • Real-world use cases

🔧 Advanced Usage

Cloudflare Workers Example

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY = env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY;
    process.env.PUBLIC_FIREBASE_PROJECT_ID = env.PUBLIC_FIREBASE_PROJECT_ID;

    const authHeader = request.headers.get('authorization');
    const idToken = authHeader?.split('Bearer ')[1];

    if (!idToken) {
      return new Response('Unauthorized', { status: 401 });
    }

    try {
      const user = await getUserFromToken(idToken);
      
      // Track user activity
      await updateDocument('users', user.uid, {
        lastSeen: FieldValue.serverTimestamp(),
        visitCount: FieldValue.increment(1)
      });

      return new Response(JSON.stringify({ user }), {
        headers: { 'Content-Type': 'application/json' },
      });
    } catch (error) {
      return new Response('Error: ' + error.message, { status: 500 });
    }
  },
};

Leaderboard Example

import { queryDocuments, updateDocument, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';

async function getTopPlayers(limit = 10) {
  return await queryDocuments('players', {
    where: [{ field: 'active', op: '==', value: true }],
    orderBy: [{ field: 'score', direction: 'DESCENDING' }],
    limit
  });
}

async function updatePlayerScore(playerId: string, points: number) {
  await updateDocument('players', playerId, {
    score: FieldValue.increment(points),
    lastPlayed: FieldValue.serverTimestamp()
  });
}

Bulk Operations Example

import { batchWrite, FieldValue } from '@prmichaelsen/firebase-admin-sdk-v8';

async function bulkUpdateUsers(userIds: string[], updates: any) {
  const operations = userIds.map(userId => ({
    type: 'update' as const,
    collectionPath: 'users',
    documentId: userId,
    data: {
      ...updates,
      updatedAt: FieldValue.serverTimestamp()
    }
  }));
  
  // Process in chunks of 500 (Firestore batch limit)
  for (let i = 0; i < operations.length; i += 500) {
    const chunk = operations.slice(i, i + 500);
    await batchWrite(chunk);
  }
}

🆚 Comparison with Node.js Admin SDK

| Feature | Node.js Admin SDK | This Library | |---------|------------------|--------------| | Environment | Node.js only | Cloudflare Workers, Edge, Deno, Bun | | Auth | Admin SDK methods | JWT + REST API | | Firestore | Native SDK | REST API | | Field Values | ✅ Full support | ✅ Full support | | Queries | ✅ Full support | ✅ Full support | | Batch Writes | ✅ Full support | ✅ Full support | | Token Verification | Built-in | firebase-auth-cloudflare-workers | | Dependencies | Heavy (Node.js) | Lightweight (Web APIs) | | Cold Starts | Slower | Faster | | Bundle Size | Large | Small |

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT

🔗 Related Projects

📝 Notes

  • This library is designed for server-side use only (admin operations)
  • For client-side Firebase, use the official Firebase JS SDK
  • Service account credentials should be kept secure and never exposed to clients
  • Token caching is automatic and refreshes 1 minute before expiry
  • Batch operations support up to 500 operations per batch (Firestore limit)

🐛 Troubleshooting

"FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY not set"

Make sure you've set the environment variable with your service account JSON:

process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY = JSON.stringify(serviceAccount);

"Failed to verify ID token"

Ensure the token is:

  1. A valid Firebase ID token (not an access token)
  2. Not expired
  3. From the correct Firebase project

TypeScript Errors

Make sure you have the required dev dependencies:

npm install -D @types/node typescript

Query Performance

For better query performance:

  • Create composite indexes for multi-field queries
  • Use cursor pagination instead of offset for large datasets
  • Limit query results to reasonable sizes

📊 Feature Comparison Table

| Feature | Supported | Notes | |---------|-----------|-------| | ID Token Verification | ✅ | Supports v9 and v10 token formats | | Custom Token Creation | ✅ | createCustomToken() | | Custom Token Exchange | ✅ | signInWithCustomToken() | | User Management | ✅ | Create, read, update, delete, list users | | Firestore CRUD | ✅ | Full support | | Firestore Queries | ✅ | where, orderBy, limit, cursors | | Firestore Batch | ✅ | Up to 500 operations | | Firestore Transactions | ❌ | Not yet implemented | | Realtime Listeners | | See explanation below | | Field Values | ✅ | increment, arrayUnion, serverTimestamp, etc. | | Realtime Database | ❌ | Not planned | | Cloud Storage | ✅ | Upload, download, delete, signed URLs | | Cloud Messaging | ❌ | Not yet implemented |

⚠️ Realtime Listeners Not Supported

This library does not support Firestore realtime listeners (onSnapshot()). Here's why:

Technical Limitation

This library uses the Firestore REST API, which is:

  • ✅ Stateless (request/response only)
  • ✅ Compatible with edge runtimes (Cloudflare Workers, Vercel Edge)
  • No persistent connections
  • No server-push capabilities
  • No streaming support

Realtime listeners require:

  • Persistent connections (WebSocket or gRPC)
  • Bidirectional streaming
  • Server-push architecture

The Firestore REST API simply doesn't provide these capabilities.

Why Not Implement gRPC?

While Firestore does offer a gRPC API with streaming support, implementing it would require:

  1. Complex Protocol Implementation

    • HTTP/2 framing
    • gRPC message framing
    • Protobuf encoding/decoding
    • Authentication flow
    • Reconnection logic
    • ~100+ hours of development
  2. Runtime Limitations

    • Cloudflare Workers doesn't support full gRPC (only gRPC-Web)
    • gRPC-Web requires a proxy server
    • Can't connect directly to Firestore's gRPC endpoint
    • Would only work in Durable Objects, not regular Workers
  3. Maintenance Burden

    • Must keep up with Firestore protocol changes
    • Complex debugging and error handling
    • High ongoing maintenance cost

Alternatives

If you need realtime updates, consider these approaches:

1. Polling (Simple)

// Poll for changes every 5 seconds
setInterval(async () => {
  const doc = await getDocument('users', 'user123');
  // Handle updates
}, 5000);

Pros: Simple, works everywhere Cons: 5-second delay, polling costs

2. Durable Objects + Polling (Better)

// Durable Object polls once, broadcasts to many clients
export class FirestoreSync {
  async poll() {
    const doc = await getDocument('users', 'user123');
    // Broadcast to all connected WebSocket clients
    for (const ws of this.sessions) {
      ws.send(JSON.stringify(doc));
    }
  }
}

Pros: One poll serves many clients, WebSocket push to clients Cons: Still polling-based, Cloudflare-specific

3. Hybrid Architecture (Best)

// Use firebase-admin-node for realtime in Node.js
import admin from 'firebase-admin';

admin.firestore().collection('users').doc('user123')
  .onSnapshot((snapshot) => {
    // True realtime updates
    console.log('Update:', snapshot.data());
  });

// Use this library for CRUD in edge functions
import { getDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
const doc = await getDocument('users', 'user123');

Pros: True realtime where needed, edge performance for CRUD Cons: Requires separate Node.js service

4. Firebase Client SDK (Frontend)

// Use Firebase Client SDK in browser/mobile
import { onSnapshot, doc } from 'firebase/firestore';

onSnapshot(doc(db, 'users', 'user123'), (snapshot) => {
  console.log('Update:', snapshot.data());
});

Pros: True realtime, built-in, well-supported Cons: Client-side only, requires Firebase Auth

Recommendation

  • For edge runtimes: Use polling or Durable Objects pattern
  • For true realtime: Use firebase-admin-node in Node.js
  • For client apps: Use Firebase Client SDK
  • For hybrid: Use this library for CRUD + Node.js for realtime

Related

🗺️ Roadmap

  • [x] Custom token creation ✅ (v2.2.0)
  • [x] Custom token exchange ✅ (v2.2.0)
  • [x] Cloud Storage operations ✅ (v2.2.0)
  • [ ] User management (create, update, delete users)
  • [ ] Firestore transactions
  • [ ] More comprehensive error handling
  • [ ] Rate limiting helpers
  • [ ] Retry logic for failed operations
  • [ ] Storage unit tests (currently only e2e)