2026-05-25 23:40:27.533521+00:00

Firebase Cloud Functions v2 represents a massive leap forward for the serverless Firebase ecosystem. Built entirely on top of Google Cloud Run and Eventarc, v2 introduces game-changing capabilities such as concurrent request handling (up to 1,000 requests per container instance), regional endpoints, custom secrets management, and robust event-driven triggers.

However, migrating from Cloud Functions v1 to v2 is not a simple package update. The API surface has been completely redesigned.

This article details the real-world lessons, syntax shifts, and architectural patterns of migrating Firestore Event Triggers from v1 to v2, using actual code patterns implemented in the Tetangga App codebase.


πŸ—οΈ The Architectural Evolution: v1 vs v2

Before diving into the code, it is important to understand the underlying infrastructure shift:

πŸ”„ Syntax Breakdown: Moving from v1 to v2

Let’s look at the absolute changes when writing a Firestore Document Creation listener.

The Legacy v1 Code (Deprecated)

import * as functions from "firebase-functions";

export const onTopicCreatedV1 = functions.firestore
  .document("topics/{topicId}")
  .onCreate(async (snap, context) => {
    const topic = snap.data();
    const topicId = context.params.topicId; // v1 uses 'context' for wildcards
    console.log(`New topic created: ${topicId}`);
  });

The Modern v2 Code

import { onDocumentCreated } from "firebase-functions/v2/firestore";

export const onTopicCreatedV2 = onDocumentCreated(
  { 
    document: "topics/{topicId}", 
    database: "production-db", // v2 allows specifying multi-databases!
    region: "asia-southeast2",  // Configure region directly inside the options object
  },
  async (event) => {
    const snap = event.data;    // v2 uses a single 'event' object
    if (!snap) return;
    
    const topic = snap.data();
    const topicId = event.params.topicId; // Wildcard parameters are now inside 'event.params'
    console.log(`New topic created: ${topicId}`);
  }
);

πŸ”‘ Key Real-World Lessons from the Tetangga Codebase

During the migration of the Tetangga neighborhood portal triggers (such as onTopicCreatedV2 and onTopicReplyCreatedV2), several critical lessons were learned.

1. The Single Parameter Paradigm Shift

In v1, your callbacks always took two arguments: (snapshot, context). In v2, your callbacks receive a single unified event object of type FirestoreEvent.

This leads to the following key mappings:

2. Multi-Database Support is Now Native

If your project utilizes Firebase's multi-database Firestore feature, v1 made targeting non-default databases highly complex. In v2, you can specify the target database directly inside the options configuration block:

export const onTopicCreatedV2 = onDocumentCreated(
  { document: 'topics/{topicId}', database: 'aldianapps' },
  async (event) => {
    // This listener only targets the 'aldianapps' Firestore database!
  }
);

3. Exposing Secrets Safely

If your triggers communicate with external APIs (like sending FCM notifications, or sending Twilio WhatsApp alerts like in Tetangga), you must not expose secret tokens in your code. v2 allows you to bind Google Cloud Secrets Manager secrets directly to your triggers:

import { onDocumentCreated } from "firebase-functions/v2/firestore";

export const onNotificationTrigger = onDocumentCreated(
  { 
    document: "notifications/{id}",
    secrets: ["TWILIO_AUTH_TOKEN", "FCM_SERVER_KEY"] // Auto-bind secrets safely
  },
  async (event) => {
    const token = process.env.TWILIO_AUTH_TOKEN; // Accessible securely in runtime
    // Send alert safely...
  }
);

4. Resolving the AppRegistryNotReady Initialization Order

When importing v2 triggers alongside Twilio, sendgrid, or custom database configurations, ensure you initialize your Firebase Admin SDK first, before exporting any triggers or importing services that depend on firestore instances:

import * as admin from "firebase-admin";
import { getFirestore } from "firebase-admin/firestore";

// Initialize Firebase Admin first
const app = admin.initializeApp();
const db = getFirestore(app);

// NOW import / export trigger scripts
import { onDocumentCreated } from "firebase-functions/v2/firestore";

πŸš€ Step 5: Summary of Migration Gains

By refactoring your Firestore database triggers to Firebase Functions v2, you achieve:

  1. Dramatic Cost Reductions: Up to 10x less container instantiation overhead via Cloud Run request concurrency.
  2. Improved Maintainability: Strict TypeScript types for the event payload and wildcard parameters.
  3. Native Multi-Database Listeners: Effortless scaling across Firestore shards.
  4. State-of-the-Art Secrets Security: Zero-leak environment configurations bound directly to Cloud Secrets Manager.