# How to Schedule Background Functions in Firebase

- Tool: Firebase
- Difficulty: Intermediate
- Time required: 15-20 min
- Compatibility: Firebase Cloud Functions v2, Blaze plan required, Node.js 18/20/22
- Last updated: March 2026

## TL;DR

Schedule background functions in Firebase using Cloud Functions v2 with the onSchedule() trigger and a cron expression. The function runs automatically at the specified interval using Google Cloud Scheduler. Common use cases include nightly data cleanup, daily email digests, periodic API syncs, and usage report generation. Scheduled functions require the Blaze pay-as-you-go plan because Cloud Scheduler is a paid Google Cloud service.

## Scheduling Background Functions in Firebase

Firebase Cloud Functions v2 lets you schedule functions to run at regular intervals using cron expressions. Under the hood, Firebase creates a Google Cloud Scheduler job that triggers your function via Pub/Sub. This tutorial covers creating scheduled functions, writing cron expressions, handling timezones, managing long-running tasks, and monitoring executions in production.

## Before you start

- A Firebase project on the Blaze pay-as-you-go plan
- Firebase CLI installed and logged in
- Cloud Functions initialized in your project (firebase init functions)
- Node.js 18, 20, or 22 installed locally

## Step-by-step guide

### 1. Create a basic scheduled function with onSchedule()

Import onSchedule from firebase-functions/v2/scheduler and define your function. The first argument is the schedule configuration including the cron expression. The second argument is the async handler function. The handler receives a ScheduledEvent object with the schedule time. Deploy with firebase deploy --only functions and Firebase automatically creates the Cloud Scheduler job.

```
import { onSchedule } from 'firebase-functions/v2/scheduler'
import { logger } from 'firebase-functions'

// Run every day at midnight UTC
export const dailyCleanup = onSchedule('every day 00:00', async (event) => {
  logger.info('Running daily cleanup', { scheduledTime: event.scheduleTime })

  // Your cleanup logic here
  await deleteExpiredSessions()
  await archiveOldRecords()

  logger.info('Daily cleanup completed')
})
```

**Expected result:** After deploying, the function runs automatically every day at midnight UTC. You can see executions in the Cloud Functions logs.

### 2. Write cron expressions for common schedules

Cron expressions have five fields: minute, hour, day-of-month, month, and day-of-week. Firebase also supports the more readable App Engine format. Here are common scheduling patterns you can use directly in your onSchedule() definition.

```
import { onSchedule } from 'firebase-functions/v2/scheduler'

// Every 5 minutes
export const frequentCheck = onSchedule('*/5 * * * *', async () => {
  // Runs at :00, :05, :10, :15, etc.
})

// Every hour at minute 30
export const hourlySync = onSchedule('30 * * * *', async () => {
  // Runs at 0:30, 1:30, 2:30, etc.
})

// Every Monday at 9:00 AM
export const weeklyReport = onSchedule('0 9 * * 1', async () => {
  // 1 = Monday
})

// First day of every month at 6:00 AM
export const monthlyBilling = onSchedule('0 6 1 * *', async () => {
  // Day 1 of each month
})

// App Engine format alternatives
export const everyHour = onSchedule('every 1 hours', async () => {})
export const every15Min = onSchedule('every 15 minutes', async () => {})
```

**Expected result:** Each function runs at its specified interval. Cloud Scheduler manages the timing automatically.

### 3. Configure timezone and function options

By default, scheduled functions use UTC. Pass a configuration object instead of a string to set the timezone, memory, timeout, and retry behavior. Use the IANA timezone identifier (like 'America/New_York') to schedule functions in a specific timezone. Set timeoutSeconds for functions that need more than the default 60 seconds.

```
import { onSchedule } from 'firebase-functions/v2/scheduler'

export const morningDigest = onSchedule(
  {
    schedule: 'every day 08:00',
    timeZone: 'America/New_York',
    timeoutSeconds: 300, // 5 minutes
    memory: '512MiB',
    retryCount: 3
  },
  async (event) => {
    // Runs at 8:00 AM Eastern Time every day
    // Automatically handles EST/EDT transitions
    await sendDailyDigestEmails()
  }
)
```

**Expected result:** The function runs at 8:00 AM Eastern Time regardless of DST changes. Failed executions are retried up to 3 times.

### 4. Interact with Firestore from a scheduled function

Scheduled functions commonly perform database maintenance tasks. Use the Firebase Admin SDK to access Firestore without security rule restrictions. The Admin SDK is automatically initialized in Cloud Functions — just import it and use it. Common patterns include deleting expired documents, aggregating daily stats, and archiving old data.

```
import { onSchedule } from 'firebase-functions/v2/scheduler'
import { logger } from 'firebase-functions'
import { getFirestore, Timestamp } from 'firebase-admin/firestore'
import { initializeApp } from 'firebase-admin/app'

initializeApp()
const db = getFirestore()

export const cleanupExpiredSessions = onSchedule(
  {
    schedule: 'every 1 hours',
    timeoutSeconds: 120,
    memory: '256MiB'
  },
  async () => {
    const now = Timestamp.now()
    const expiredQuery = db
      .collection('sessions')
      .where('expiresAt', '<', now)
      .limit(500) // Batch to stay within limits

    const snapshot = await expiredQuery.get()

    if (snapshot.empty) {
      logger.info('No expired sessions to clean up.')
      return
    }

    const batch = db.batch()
    snapshot.docs.forEach((doc) => {
      batch.delete(doc.ref)
    })

    await batch.commit()
    logger.info(`Deleted ${snapshot.size} expired sessions.`)
  }
)
```

**Expected result:** Every hour, the function checks for expired sessions and deletes them in batches, logging the number of documents removed.

### 5. Deploy and monitor scheduled functions

Deploy your scheduled functions with the Firebase CLI. After deployment, Firebase creates the Cloud Scheduler job automatically. Monitor executions in the Firebase Console under Functions > Logs, or in Google Cloud Console under Cloud Scheduler to see job history and next run times. You can also manually trigger a scheduled function from Cloud Scheduler for testing.

```
# Deploy all functions
firebase deploy --only functions

# Deploy a specific function
firebase deploy --only functions:dailyCleanup

# View function logs
firebase functions:log --only dailyCleanup

# List Cloud Scheduler jobs (via gcloud)
gcloud scheduler jobs list

# Manually trigger for testing
gcloud scheduler jobs run firebase-schedule-dailyCleanup-us-central1
```

**Expected result:** The function is deployed and Cloud Scheduler shows the job with its next run time. Logs confirm successful executions.

## Complete code example

File: `scheduled-functions.ts`

```typescript
import { onSchedule } from 'firebase-functions/v2/scheduler'
import { logger } from 'firebase-functions'
import { initializeApp } from 'firebase-admin/app'
import { getFirestore, Timestamp, FieldValue } from 'firebase-admin/firestore'

initializeApp()
const db = getFirestore()

// Daily cleanup: delete expired sessions and old notifications
export const dailyCleanup = onSchedule(
  {
    schedule: 'every day 02:00',
    timeZone: 'UTC',
    timeoutSeconds: 300,
    memory: '512MiB',
    retryCount: 2
  },
  async () => {
    const now = Timestamp.now()
    let totalDeleted = 0

    // Delete expired sessions in batches
    let hasMore = true
    while (hasMore) {
      const snapshot = await db
        .collection('sessions')
        .where('expiresAt', '<', now)
        .limit(500)
        .get()

      if (snapshot.empty) {
        hasMore = false
        break
      }

      const batch = db.batch()
      snapshot.docs.forEach((doc) => batch.delete(doc.ref))
      await batch.commit()
      totalDeleted += snapshot.size

      if (snapshot.size < 500) hasMore = false
    }

    logger.info(`Daily cleanup: deleted ${totalDeleted} expired sessions`)
  }
)

// Hourly stats aggregation
export const hourlyStats = onSchedule(
  {
    schedule: '0 * * * *',
    timeoutSeconds: 120,
    memory: '256MiB'
  },
  async () => {
    const oneHourAgo = Timestamp.fromMillis(Date.now() - 60 * 60 * 1000)

    const snapshot = await db
      .collection('events')
      .where('createdAt', '>', oneHourAgo)
      .get()

    const today = new Date().toISOString().split('T')[0]
    await db.doc(`stats/${today}`).set(
      {
        hourlyEvents: FieldValue.increment(snapshot.size),
        lastUpdated: Timestamp.now()
      },
      { merge: true }
    )

    logger.info(`Hourly stats: ${snapshot.size} events in the last hour`)
  }
)
```

## Common mistakes

- **Trying to deploy scheduled functions on the Spark free plan, which does not support Cloud Scheduler** — undefined Fix: Upgrade to the Blaze pay-as-you-go plan. Cloud Scheduler and Cloud Functions are Blaze-only services. Cloud Scheduler costs $0.10 per job per month.
- **Not handling the case where a scheduled function takes longer than the default 60-second timeout** — undefined Fix: Set timeoutSeconds in the function options. V2 scheduled functions support up to 1800 seconds (30 minutes). For longer tasks, break the work into smaller batches.
- **Creating infinite loops by writing to a collection that triggers another Cloud Function, which writes back** — undefined Fix: Use conditional checks in Firestore triggers to prevent re-processing. For scheduled functions, write to a separate output collection or use a processed flag to avoid loops.
- **Assuming the function runs at the exact scheduled second — Cloud Scheduler has a delivery tolerance** — undefined Fix: Cloud Scheduler guarantees at-least-once delivery but may trigger slightly late (usually within a few seconds). Do not rely on exact-second timing. Use the event.scheduleTime property for the intended execution time.

## Best practices

- Always set a timezone explicitly for user-facing schedules to avoid confusion with UTC and daylight saving time
- Set appropriate timeoutSeconds based on the expected duration of your task — do not rely on the default
- Use batched operations when processing large datasets to stay within Firestore's 500-operation batch limit
- Log the start and completion of scheduled tasks for monitoring and debugging
- Set retryCount to at least 2 for critical tasks like billing or email sends to handle transient failures
- Use gcloud scheduler jobs run to test scheduled functions manually during development
- Keep scheduled functions idempotent — they should produce the same result if run twice with the same data
- Monitor Cloud Scheduler job history in Google Cloud Console to catch silent failures

## Frequently asked questions

### Do scheduled functions require the Blaze plan?

Yes. Cloud Scheduler is a paid Google Cloud service, and Cloud Functions require the Blaze plan. Cloud Scheduler costs $0.10 per job per month. The Blaze plan includes a generous free tier for Cloud Functions (2 million invocations per month).

### What is the maximum timeout for a scheduled function?

V2 scheduled functions support a maximum timeout of 1800 seconds (30 minutes). V1 functions are limited to 540 seconds (9 minutes). If your task needs more than 30 minutes, break it into smaller sub-tasks triggered in sequence.

### How do I test a scheduled function without waiting for the schedule?

Use the Firebase Emulator Suite for local testing. For deployed functions, use gcloud scheduler jobs run followed by the job name to trigger the function manually. The job name follows the pattern firebase-schedule-functionName-region.

### Can I schedule a function to run every second or every 10 seconds?

No. The minimum interval for Cloud Scheduler is 1 minute. For sub-minute intervals, consider using a Pub/Sub-triggered function with a Cloud Tasks queue, or run a persistent process on a reserved VM.

### What happens if a scheduled function fails?

Cloud Scheduler retries the delivery based on your retryCount setting. The default is 0 retries. Set retryCount in the function options to enable automatic retries. Each retry is a separate function invocation.

### Can RapidDev help me set up scheduled background tasks in Firebase?

Yes. RapidDev can design and implement scheduled Cloud Functions for data cleanup, report generation, API synchronization, and other recurring tasks, including monitoring and error handling.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-schedule-background-functions-in-firebase
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-schedule-background-functions-in-firebase
