mrkeyoor.com_
Sat 08 Aug 15:45 UTC
npmInfraupdated 08 Aug 2026

firebase-admin

firebase-admin is Google's privileged server SDK for Firebase. It lets trusted Node.js code manage users, verify authentication and App Check tokens, read and write Firestore or Realtime Database data, send Firebase Cloud Messaging notifications, and access Cloud Storage without pretending to be an end user. It belongs only on servers, workers, and serverless functions you control because its credentials can bypass the client-side security boundary.

Verdict

Use firebase-admin when Firebase is already the backend and trusted Node code needs official privileged access. Do not install it in client code, on Node 20, or as a casual database wrapper when portability and a narrow dependency matter more than Firebase integration.

API stability4/5The modular service APIs are mature, but v14 was a real breaking release: it dropped Node 18 and 20, legacy namespaces, Instance ID, and legacy messaging types
Docs5/5The README clearly states the server-only boundary and supported Node version, then links focused setup, Auth, Database, Messaging, API reference, and release-note guides
Maintenance5/5Google maintains it, version 14.2.0 shipped in July 2026, the repository was pushed on August 6, and the latest release includes features, fixes, dependency updates, and security work
Ecosystem5/5It is the official privileged SDK across Firebase services and recorded 8,200,703 npm downloads in the measured week, with direct integration into Google credentials and Cloud client libraries

Use it if

  • Your Node.js backend must verify Firebase ID tokens or manage Firebase Authentication users and custom claims
  • A trusted worker or API needs administrative access to Firestore, Realtime Database, Cloud Storage, Remote Config, or Firebase Cloud Messaging
  • You run on Google Cloud and want Application Default Credentials instead of distributing service-account JSON keys
  • You need one officially maintained server SDK spanning several Firebase products under the same project identity
Skip it if

Setup reality

The npm install has no peer dependencies or native compilation, but that is the easy part. Version 14 needs Node 22+, and every real request needs Google credentials plus the right IAM roles and Firebase project settings. Application Default Credentials are cleanest on Google Cloud; elsewhere you must secure a service-account key, preserve private-key newlines in environment variables, and never ship it to a client. Realtime Database needs its URL, Storage needs the correct bucket, and local testing needs each service's emulator environment variable rather than one universal switch.

Patterns

Initialize with Application Default Credentialsinitialize-with-adc

import { applicationDefault, initializeApp } from 'firebase-admin/app';

export const app = initializeApp({
  credential: applicationDefault(),
});

ADC is automatic on supported Google Cloud runtimes. Elsewhere, set GOOGLE_APPLICATION_CREDENTIALS to a protected service-account JSON file or configure another ADC source.

Initialize from service-account environment variablesinitialize-service-account

import { cert, initializeApp } from 'firebase-admin/app';

const app = initializeApp({
  credential: cert({
    projectId: process.env.FIREBASE_PROJECT_ID,
    clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
    privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
  }),
});

Escaped newlines are the usual deployment failure. Keep the private key in a secret manager, never in source control or any client bundle.

Reuse the default app during hot reloadavoid-duplicate-apps

import { applicationDefault, getApp, initializeApp } from 'firebase-admin/app';

let app;
try {
  app = getApp();
} catch {
  app = initializeApp({ credential: applicationDefault() });
}

export { app };

Calling initializeApp again with an explicit Credential is not idempotent and throws. This matters in development servers and test runners that reload modules.

Verify a Firebase ID token on an API requestverify-id-token

import { getAuth } from 'firebase-admin/auth';

const header = req.headers.authorization ?? '';
const match = header.match(/^Bearer (.+)$/);
if (!match) throw new Error('missing bearer token');

const decoded = await getAuth().verifyIdToken(match[1], true);
console.log(decoded.uid);

The true argument checks revocation and disabled-user state with an extra backend request. Without it, signature and claims are checked but revocation is not.

Create, fetch, and update an Auth usermanage-auth-user

import { getAuth } from 'firebase-admin/auth';

const auth = getAuth();
const created = await auth.createUser({
  email: 'ada@example.com',
  displayName: 'Ada',
  emailVerified: false,
});

const user = await auth.getUser(created.uid);
await auth.updateUser(user.uid, { disabled: true });

Auth operations need sufficient IAM permissions. createUser rejects duplicate email addresses, so handle auth/email-already-exists as an expected application error.

Assign authorization claimsset-custom-claims

import { getAuth } from 'firebase-admin/auth';

await getAuth().setCustomUserClaims(uid, {
  role: 'editor',
  paid: true,
});

This replaces the user's existing custom claims rather than merging them. New claims appear after the client obtains a fresh ID token; they are not a database for profile data.

Exchange a fresh ID token for a session cookiecreate-session-cookie

import { getAuth } from 'firebase-admin/auth';

const decoded = await getAuth().verifyIdToken(idToken);
if (Date.now() / 1000 - decoded.auth_time > 5 * 60) {
  throw new Error('recent sign-in required');
}

const expiresIn = 5 * 24 * 60 * 60 * 1000;
const sessionCookie = await getAuth().createSessionCookie(idToken, { expiresIn });

Validate a CSRF token before this exchange, then send the result as an HttpOnly, Secure cookie. Verify the session cookie on later requests with verifySessionCookie.

Write and read a Firestore documentfirestore-read-write

import { FieldValue, getFirestore } from 'firebase-admin/firestore';

const db = getFirestore();
const ref = db.collection('users').doc(uid);
await ref.set({
  displayName: 'Ada',
  updatedAt: FieldValue.serverTimestamp(),
}, { merge: true });

const snapshot = await ref.get();
const user = snapshot.exists ? snapshot.data() : null;

Admin Firestore access uses IAM and bypasses Firebase client Security Rules. Validate request data in your server before writing it.

Update Firestore data in a transactionfirestore-transaction

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

const db = getFirestore();
const ref = db.collection('counters').doc('orders');

const next = await db.runTransaction(async (tx) => {
  const snapshot = await tx.get(ref);
  const value = (snapshot.data()?.value ?? 0) + 1;
  tx.set(ref, { value });
  return value;
});

Firestore may retry the callback after contention. Keep it deterministic and do not send email, charge cards, or perform other outside side effects inside it.

Apply an atomic Realtime Database updateupdate-realtime-database

import { getDatabase, ServerValue } from 'firebase-admin/database';

const db = getDatabase();
await db.ref().update({
  [`users/${uid}/displayName`]: 'Ada',
  [`audit/${uid}/updatedAt`]: ServerValue.TIMESTAMP,
});

Configure databaseURL during app initialization or use getDatabaseWithUrl. Admin access is privileged by default, so Security Rules do not protect these writes.

Send an FCM notification to an installationsend-push-notification

import { getMessaging } from 'firebase-admin/messaging';

const messageId = await getMessaging().send({
  fid: installationId,
  notification: {
    title: 'Order shipped',
    body: 'Your package is on the way',
  },
  data: { orderId },
});

In v14.1+, fid is the current target field. Registration-token targets still exist in v14 but are deprecated, so do not start new code on token.

Verify and consume an App Check tokenverify-app-check-token

import { getAppCheck } from 'firebase-admin/app-check';

const result = await getAppCheck().verifyToken(appCheckToken, {
  consume: true,
});
if (result.alreadyConsumed) {
  throw new Error('replayed App Check token');
}
console.log(result.appId);

Replay protection makes an additional backend call and can increase attestation usage. Reserve consume: true for low-volume, security-sensitive, or expensive operations.

Alternatives

PackageRegistryPick it when
firebasenpmBrowser, mobile-web, or other untrusted code that must obey Firebase Security Rules instead of holding admin privileges
@supabase/supabase-jsnpmA Postgres-based hosted backend with an open-source stack and a more portable data model fits better than Firebase
node-appwritenpmYou want an Appwrite server SDK and the option to self-host the backend
parse-servernpmYou want to run a Parse-compatible backend yourself and accept operating the server as part of the tradeoff