Skip to content

Cloud Function Triggers: Firestore, Auth, Storage, PubSub & HTTPS Events

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Cloud Function Triggers: Firestore, Auth, Storage, PubSub & HTTPS Events. We cover key concepts, practical examples, and best practices to help you master this topic.

Cloud Functions trigger on Firebase and Google Cloud events — each trigger type responds to a specific event source, providing context about what changed and why.

What You'll Learn

How to use each Cloud Functions trigger type: Firestore document triggers, Auth triggers, Storage triggers, PubSub scheduled functions, HTTPS/callable functions, and choosing the right trigger.

Why It Matters

Choosing the wrong trigger leads to missed events, excessive invocations, or complex workarounds. DodaTech uses Firestore triggers for data validation, Auth triggers for user onboarding, and scheduled triggers for cleanup jobs.

Real-World Use

A complete event-driven system: Auth trigger creates a user profile, Firestore trigger processes scan results, Storage trigger analyzes uploaded files, scheduled trigger archives old data.

flowchart TD
    A["Auth Create\nonUserCreated"] --> B["Init Profile\nFirestore"]
    C["Firestore Write\nonScanCreated"] --> D["Process Scan\nAnalyze Threats"]
    E["Storage Upload\nonFileUploaded"] --> F["Virus Scan\nFile Analysis"]
    G["PubSub Schedule\nDaily Cleanup"] --> H["Archive Old\nScan Records"]
    H --> I["Remove Temp\nFiles"]
    style A fill:#dbeafe,stroke:#2563eb
    style C fill:#fef3c7,stroke:#d97706
    style E fill:#bbf7d0,stroke:#16a34a
    style G fill:#fce7f3,stroke:#ec4899

Firestore Triggers

exports.onScanUpdated = functions.firestore
  .document("scans/{scanId}")
  .onUpdate(async (change, context) => {
    const before = change.before.data();
    const after = change.after.data();
    const scanId = context.params.scanId;

    console.log("Scan updated:", scanId);
    console.log("Status changed:", before.status, "->", after.status);

    if (after.status === "completed" && before.status !== "completed") {
      console.log("Scan completed — triggering post-scan actions");
      // Send notification, update dashboard, etc.
    }
    return null;
  });

exports.onScanDeleted = functions.firestore
  .document("scans/{scanId}")
  .onDelete(async (snap, context) => {
    const scan = snap.data();
    console.log("Scan deleted:", context.params.scanId);
    console.log("User:", scan.userId);
    // Clean up related data
    return null;
  });

Auth Triggers

// Triggered when a user account is deleted
exports.onUserDeleted = functions.auth
  .user()
  .onDelete(async (user) => {
    console.log("User deleted:", user.uid);

    // Clean up user data
    const batch = admin.firestore().batch();

    // Delete user profile
    batch.delete(admin.firestore().doc(`users/${user.uid}`));

    // Delete user devices
    const devices = await admin.firestore()
      .collection("devices")
      .where("userId", "==", user.uid)
      .get();
    devices.forEach((doc) => batch.delete(doc.ref));

    await batch.commit();
    console.log("User data cleaned up for:", user.uid);
    return null;
  });

Storage Triggers

exports.onFileUploaded = functions.storage
  .object()
  .onFinalize(async (object) => {
    const filePath = object.name;
    const contentType = object.contentType;
    const size = object.size;

    console.log("File uploaded:", filePath);
    console.log("Type:", contentType, "Size:", size);

    // Extract user ID from path: users/{userId}/samples/{filename}
    const pathParts = filePath.split("/");
    const userId = pathParts[1];

    // Store file metadata in Firestore
    await admin.firestore().collection("files").add({
      userId,
      filePath,
      contentType,
      size,
      uploadedAt: admin.firestore.FieldValue.serverTimestamp()
    });

    console.log("File metadata stored for:", filePath);
    return null;
  });

PubSub Scheduled Triggers

// Every hour — check for pending scans older than 1 hour
exports.checkStuckScans = functions.pubsub
  .schedule("0 * * * *")
  .onRun(async (context) => {
    const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);

    const stuckScans = await admin.firestore()
      .collection("scans")
      .where("status", "==", "pending")
      .where("createdAt", "<", oneHourAgo)
      .get();

    console.log("Found", stuckScans.size, "stuck scans");

    stuckScans.forEach(async (doc) => {
      await doc.ref.update({
        status: "failed",
        error: "Timed out after 1 hour",
        resolvedAt: admin.firestore.FieldValue.serverTimestamp()
      });
    });

    return null;
  });

Choosing the Right Trigger

Trigger When to Use Latency
Firestore onCreate Process new data Near real-time
Firestore onUpdate React to changes Near real-time
Auth onCreate User onboarding Near real-time
Storage onFinalize File processing Near real-time
PubSub schedule Cron Jobs Scheduled
HTTPS onCall Client request Request-response
HTTPS onRequest REST API endpoint Request-response

Common Mistakes

1. Mutating the Same Document in a Trigger

Writing to the same document that triggered the function can cause infinite loops. Use a flag field or write to a different path.

2. Not Handling Multiple Triggers on the Same Event

Multiple functions can trigger on the same event. Design each function to be independent and idempotent.

3. Overusing Auth Triggers

Auth triggers fire for admin SDK operations too. Admin.createUser() triggers onCreate. Be aware of this when provisioning accounts.

4. Ignoring Event Order

Firestore events are not guaranteed to arrive in order. An onDelete may arrive before the onCreate processed fully. Use idempotent operations.

5. Forgetting to Set Timeout for Scheduled Functions

Scheduled functions have a 60-second default timeout. Long cleanup jobs need increased timeout configuration.

Practice Questions

  1. What is the difference between onCreate and onWrite Firestore triggers?
  2. How do you prevent infinite loops with Firestore triggers?
  3. What events can Auth triggers respond to?
  4. How do you schedule a function to run every 30 minutes?

Answers:

  1. onCreate fires only when a document is created. onWrite fires on create, update, and delete events.
  2. Add a triggeredByFunction field or use a separate _processed field to detect and skip function-originated writes.
  3. Auth triggers respond to onCreate (user signed up) and onDelete (user deleted).
  4. Use functions.pubsub.schedule("*/30 * * * *").onRun() with a cron expression.

Challenge: Build a complete trigger system: Auth onCreate creates user profile, Firestore onCreate processes threat alerts, Storage onFinalize analyzes uploaded files, and a 24-hour cleanup removes resolved alerts.

FAQ

Can multiple functions trigger on the same event?

Yes. Multiple functions can subscribe to the same event. Each function runs independently and is billed separately.

What is the maximum execution time for a function?

The default is 60 seconds, configurable up to 540 seconds (9 minutes). For longer tasks, use Workflows or Cloud Tasks.

How do I test triggers locally?

Use the Firebase Emulator Suite. Run firebase emulators:start and trigger events through the Emulator UI or SDK.

Do triggers work in the emulator?

Yes, the emulator supports all trigger types. Events from emulated services trigger functions running in the emulator.

What happens if a trigger function throws an error?

The function retries up to 3 times for background functions. For HTTPS functions, the error is returned to the caller.

Mini Project

Build an event-driven system: Auth trigger initializes user profile + default device, Firestore trigger processes scan completions and awards badges, Storage trigger logs file uploads, scheduled trigger sends weekly digests.

What's Next

Callable Functions — build client-callable HTTPS functions with auth integration.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro