Cloud Functions for Firebase Gen 2 runs on Cloud Run and Eventarc rather than the original Functions infrastructure, and the differences are not cosmetic: per-instance concurrency, longer timeouts, larger instances, and traffic-managed deploys change how you should design an event-driven backend. Google has kept Gen 1 running, but new capabilities land in Gen 2, and most estates we see are better off migrated deliberately than accreting a mixed estate by accident.
Here is what actually changes, and the migration order that avoids the traps.
What Gen 2 changes underneath you
Concurrency. Gen 1 instances handle one request at a time. Gen 2 defaults to concurrent requests per instance (up to 1,000 for HTTP functions). This slashes cold-start impact for bursty HTTP traffic — one warm instance absorbs a burst that Gen 1 answered with a fleet of cold starts — but it means your function code must now be concurrency-safe. Module-level mutable state that was effectively request-scoped in Gen 1 (a lazily-initialized per-request cache, a global "current user") becomes shared state under load. Audit globals before flipping concurrency on; keep genuinely shared things (SDK clients, connection pools) global and everything request-specific inside the handler.
Timeouts and sizing. HTTP functions can run up to 60 minutes (event-driven ones remain capped lower), with instances up to 32GB/8vCPU. Work you previously chunked awkwardly to fit 9 minutes can often become one straightforward job — or better, move to Cloud Tasks with a Gen 2 handler.
Eventarc delivery. Background triggers arrive via Eventarc, which delivers at least once and can deliver more than once, occasionally out of order. Gen 1 had the same formal contract, but Gen 2's retry and delivery behavior makes duplicates more visible in practice. Every event handler needs an idempotency strategy: an idempotency key derived from the event ID, an "already processed" check against a ledger document, or naturally idempotent writes (set with merge rather than increment).
Traffic splitting. Because Gen 2 functions are Cloud Run services, deploys can shift traffic gradually and roll back instantly. This is the most underused benefit of the migration: HTTP functions gain canary deploys essentially for free.
The migration mechanics
The SDK surface moved from firebase-functions/v1 namespaces to typed v2 modules:
// Gen 1
const functions = require('firebase-functions');
exports.onOrderCreated = functions.firestore
.document('orders/{orderId}')
.onCreate(handler);
// Gen 2
const { onDocumentCreated } = require('firebase-functions/v2/firestore');
exports.onOrderCreated = onDocumentCreated('orders/{orderId}', (event) => {
const data = event.data.data(); // note: snapshot moved under event.data
// ...
});
Details that bite during the port:
- The event shape changed.
snapshotandcontextmerge into a singleevent; params areevent.params; the "before/after" pair on updates isevent.data.before/event.data.after. Mechanical, but every handler needs the edit. - Configuration moved.
functions.config()does not exist in Gen 2. Migrate todefineSecret/defineStringparams backed by Secret Manager and environment configuration. Do this first, in Gen 1, where both mechanisms work — it decouples the config migration from the runtime migration. - Function names are load-bearing. A Gen 1 function cannot be upgraded in place to Gen 2 under the same name for most trigger types; you deploy a new function and retire the old. For HTTP functions this means a new default URL — put functions behind Hosting rewrites or a load balancer before migrating so client apps never learn function URLs.
- Scheduled and callable functions port cleanly (
onSchedule,onCallin v2), but callable functions enforce App Check options explicitly — decide your enforcement posture as part of the port, not after.
Sequencing for a low-drama migration
- Inventory and classify. HTTP, callable, Firestore/Auth/Storage triggers, scheduled. Note which have side effects that would be dangerous if double-executed during a cutover window.
- Move configuration to params/secrets while still on Gen 1.
- Port HTTP/callable functions first behind Hosting rewrites, canary with traffic splitting, retire the Gen 1 URL.
- Port event triggers one at a time. For each: deploy the Gen 2 function alongside Gen 1, make the handler idempotent, let both run briefly if the handler tolerates duplicates (or gate with a ledger check if not), verify parity in logs, then delete the Gen 1 trigger. Never leave both running unattended — that is a double-execution generator.
- Scheduled functions last, cutting each job over at a moment when a missed or doubled run is safe.
Where teams get stuck
The stalls we see are rarely about the SDK. They are the pre-existing debts the migration surfaces: handlers that were never idempotent, config sprawled across functions.config() with no record of what is secret, client apps hard-coding function URLs, and no staging project to rehearse in. Which is the honest framing of a Gen 2 migration: it is a forcing function for the operational hygiene the Functions estate should have had anyway. Budget for that work, not just the port.