# How to Set Up Automatic Data Backups in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 20-25 min
- Compatibility: FlutterFlow Pro+ (Cloud Functions required)
- Last updated: March 2026

## TL;DR

Automate Firestore backups using a scheduled Cloud Function that calls the Firestore Admin export API daily at 2 AM UTC, writing snapshots to a Cloud Storage bucket organized by date. A retention function deletes backups older than 30 days. A backup_log collection tracks each backup's status, size, and duration. The FlutterFlow admin page displays backup history, lets admins trigger manual backups, and documents the restore procedure for disaster recovery.

## Setting Up Automatic Firestore Backups in FlutterFlow

Data loss can be catastrophic. Whether from accidental deletions, buggy code, or security breaches, having automated backups is essential for any production app. This tutorial implements scheduled daily Firestore exports to Cloud Storage, automatic cleanup of old backups, monitoring and alerting, and an admin interface for managing the backup process.

## Before you start

- A FlutterFlow project on the Pro plan or higher
- Firebase project on the Blaze plan (pay-as-you-go, required for Cloud Functions and Storage exports)
- Cloud Firestore Admin API enabled in Google Cloud Console
- A Cloud Storage bucket for backups (create one at gs://your-project-backups)

## Step-by-step guide

### 1. Create the scheduled Cloud Function for daily Firestore exports

Create a Cloud Function named scheduledBackup triggered by Cloud Scheduler to run daily at 2 AM UTC. The function uses the Firestore Admin API to export all collections to a Cloud Storage bucket path organized by date: gs://your-project-backups/YYYY-MM-DD/. Use the google-cloud/firestore module's exportDocuments method with the project ID and output URI. After the export completes, create a document in a `backup_log` collection with fields: timestamp, status ('success' or 'failed'), bucketPath, and duration in seconds. Wrap the export call in a try-catch to log failures.

```
// Cloud Function: scheduledBackup
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firestore = require('@google-cloud/firestore');
admin.initializeApp();

const client = new firestore.v1.FirestoreAdminClient();
const bucket = 'gs://your-project-backups';

exports.scheduledBackup = functions.pubsub
  .schedule('every day 02:00')
  .timeZone('UTC')
  .onRun(async () => {
    const projectId = process.env.GCLOUD_PROJECT;
    const db = `projects/${projectId}/databases/(default)`;
    const date = new Date().toISOString().split('T')[0];
    const outputUri = `${bucket}/${date}`;
    const startTime = Date.now();

    try {
      const [response] = await client.exportDocuments({
        name: db,
        outputUriPrefix: outputUri,
        collectionIds: [], // empty = all collections
      });

      await response.promise();
      const duration = Math.round(
        (Date.now() - startTime) / 1000
      );

      await admin.firestore()
        .collection('backup_log').add({
          timestamp: admin.firestore.FieldValue
            .serverTimestamp(),
          status: 'success',
          bucketPath: outputUri,
          durationSeconds: duration,
        });
    } catch (err) {
      await admin.firestore()
        .collection('backup_log').add({
          timestamp: admin.firestore.FieldValue
            .serverTimestamp(),
          status: 'failed',
          error: err.message,
          bucketPath: outputUri,
        });
    }
  });
```

**Expected result:** Firestore data exports to Cloud Storage daily at 2 AM UTC with a log entry recording the result.

### 2. Add a retention function to delete backups older than 30 days

Create a second scheduled Cloud Function named cleanupOldBackups that runs weekly. The function lists objects in the backup bucket, groups them by date folder, and deletes folders older than 30 days. Use the Google Cloud Storage SDK to list and delete objects. Also clean up corresponding backup_log documents older than 90 days to keep the log manageable. This ensures storage costs stay controlled while maintaining a rolling 30-day recovery window.

```
// Cloud Function: cleanupOldBackups
const { Storage } = require('@google-cloud/storage');
const storage = new Storage();

exports.cleanupOldBackups = functions.pubsub
  .schedule('every monday 03:00')
  .timeZone('UTC')
  .onRun(async () => {
    const bucketName = 'your-project-backups';
    const bucket = storage.bucket(bucketName);
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - 30);

    const [files] = await bucket.getFiles();
    const toDelete = files.filter((f) => {
      const dateStr = f.name.split('/')[0];
      return new Date(dateStr) < cutoff;
    });

    for (const file of toDelete) {
      await file.delete();
    }

    console.log(`Deleted ${toDelete.length} old files`);
  });
```

**Expected result:** Backup files older than 30 days are automatically deleted weekly, keeping storage costs predictable.

### 3. Set up failure alerting via email notification

Extend the scheduledBackup function to send an alert when a backup fails. In the catch block, after logging the failure to backup_log, use a Cloud Function HTTP call to a notification service (SendGrid, Mailgun, or Firebase Extensions for email). Alternatively, create a simple Cloud Function triggered by Firestore onCreate on backup_log that checks if the new document has status 'failed' and sends an email to the admin address. Include the error message, timestamp, and a link to the Google Cloud Console for debugging.

**Expected result:** Admins receive an email notification immediately when a backup fails so they can investigate and fix the issue.

### 4. Build the admin backup management page in FlutterFlow

Create a BackupAdmin page gated by role check (Conditional Visibility: currentUser.role == 'admin'). At the top, show the last backup status in a prominent Container: green with checkmark for success, red with warning icon for failure, including the timestamp and bucket path. Below, add a ListView bound to a Backend Query on backup_log ordered by timestamp descending. Each item shows the date, status badge (green/red), duration, and bucket path. Add a 'Trigger Manual Backup' Button that calls an HTTP-triggered Cloud Function (a variant of scheduledBackup without the scheduler). Add a Switch to enable/disable automatic backups by updating a config document that the scheduled function checks before running.

**Expected result:** Admins see a complete backup history, can trigger manual backups on demand, and can toggle automatic backups on or off.

### 5. Document and test the restore procedure

Restoring from a backup uses the Firestore Admin import API. Create a Cloud Function named restoreBackup that accepts a bucketPath parameter and calls client.importDocuments with the input URI set to the backup path. Gate this function behind an admin auth check. IMPORTANT: test the restore process by importing to a separate Firestore project (not production) to verify backup integrity without overwriting live data. On the admin page, add a 'Restore' Button on each backup log item that opens a confirmation dialog explaining that restoring will overwrite current data. Only enable this for backup entries with status 'success'.

**Expected result:** Admins can restore Firestore from any successful backup. The process is tested and documented for disaster recovery.

## Complete code example

File: `Backup Cloud Functions`

```javascript
// Cloud Functions: Firestore Backup System
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const firestore = require('@google-cloud/firestore');
const { Storage } = require('@google-cloud/storage');

admin.initializeApp();
const client = new firestore.v1.FirestoreAdminClient();
const storage = new Storage();
const db = admin.firestore();
const BUCKET = 'gs://your-project-backups';

// Daily backup at 2 AM UTC
exports.scheduledBackup = functions.pubsub
  .schedule('every day 02:00')
  .timeZone('UTC')
  .onRun(async () => {
    const config = await db.doc('config/backup').get();
    if (config.exists && !config.data().enabled) return;

    const projectId = process.env.GCLOUD_PROJECT;
    const dbPath =
      `projects/${projectId}/databases/(default)`;
    const date = new Date().toISOString().split('T')[0];
    const outputUri = `${BUCKET}/${date}`;
    const start = Date.now();

    try {
      const [op] = await client.exportDocuments({
        name: dbPath,
        outputUriPrefix: outputUri,
        collectionIds: [],
      });
      await op.promise();

      await db.collection('backup_log').add({
        timestamp: admin.firestore.FieldValue
          .serverTimestamp(),
        status: 'success',
        bucketPath: outputUri,
        durationSeconds:
          Math.round((Date.now() - start) / 1000),
      });
    } catch (err) {
      await db.collection('backup_log').add({
        timestamp: admin.firestore.FieldValue
          .serverTimestamp(),
        status: 'failed',
        error: err.message,
        bucketPath: outputUri,
      });
    }
  });

// Weekly cleanup of backups older than 30 days
exports.cleanupOldBackups = functions.pubsub
  .schedule('every monday 03:00')
  .timeZone('UTC')
  .onRun(async () => {
    const bucket = storage.bucket(
      'your-project-backups'
    );
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - 30);
    const [files] = await bucket.getFiles();

    let deleted = 0;
    for (const file of files) {
      const dateStr = file.name.split('/')[0];
      if (new Date(dateStr) < cutoff) {
        await file.delete();
        deleted++;
      }
    }
    console.log(`Cleaned up ${deleted} old files`);
  });

// Manual backup trigger (HTTP)
exports.triggerManualBackup = functions.https
  .onCall(async (data, context) => {
    if (!context.auth) throw new functions.https
      .HttpsError('unauthenticated', 'Login required');
    // Trigger the same export logic
    // ... same as scheduledBackup body
    return { success: true };
  });
```

## Common mistakes

- **Not testing the restore process until an actual disaster occurs** — Backups are useless if the restore does not work. Corrupted exports, permission errors, or schema changes can all prevent a successful restore, and you will not discover this until the worst possible moment. Fix: Run a test restore to a separate Firestore project quarterly. Verify that all collections, documents, and subcollections are present and intact. Document the restore steps so any team member can execute them.
- **Exporting only specific collections instead of all collections** — If you hardcode collection names, any new collections added later will not be included in backups. A partial backup leads to partial recovery with missing data. Fix: Pass an empty collectionIds array to exportDocuments, which tells Firestore to export ALL collections. This future-proofs the backup against schema additions.
- **Not monitoring backup failures and assuming they always succeed** — Cloud Functions can fail silently due to permission changes, quota limits, or bucket configuration issues. Without monitoring, you may have weeks of failed backups and not realize it until data loss occurs. Fix: Log every backup result to backup_log. Send email alerts on failure. Set up Google Cloud Monitoring alerts on the function's error rate as a second safety net.

## Best practices

- Run backups during off-peak hours (2-4 AM UTC) to minimize impact on app performance
- Organize backup files by date (gs://bucket/YYYY-MM-DD/) for easy identification and cleanup
- Keep a 30-day rolling retention window to balance recovery options with storage costs
- Store the backup bucket in a different Google Cloud region than your primary Firestore for geographic redundancy
- Log every backup result including duration so you can track trends and detect degradation
- Gate the restore function behind multiple confirmations and admin-only access to prevent accidental data overwrites
- Test restores quarterly to a non-production project to verify backup integrity

## Frequently asked questions

### How much does it cost to run daily Firestore backups?

Firestore exports are billed at the same rate as reads: about $0.06 per 100K documents. Cloud Storage costs about $0.02/GB/month. For a database with 100K documents, daily backups cost roughly $2-3/month including storage.

### Can I back up only specific collections instead of the entire database?

Yes. Pass an array of collection IDs to the exportDocuments method. However, backing up all collections (empty array) is recommended to ensure nothing is missed when new collections are added.

### How long does a Firestore export take?

It depends on database size. Small databases (under 100K documents) export in under a minute. Large databases (millions of documents) can take 10-30 minutes. The Cloud Function times out at 9 minutes, but the export continues as a background operation.

### Does restoring from a backup overwrite existing data?

Yes. Firestore import overwrites documents with matching IDs and creates new ones for non-matching IDs. It does NOT delete documents that exist in the current database but not in the backup. Always test restores on a separate project first.

### Can I automate backups without Cloud Functions?

Yes. Google Cloud offers a native Firestore scheduled backup feature (in preview) that can be configured directly in the console without writing any code. Check the Firebase documentation for current availability.

### Can RapidDev help set up a production backup and disaster recovery system?

Yes. RapidDev can implement multi-region backup strategies, point-in-time recovery, automated restore testing, compliance-grade retention policies, and monitoring dashboards.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-automatic-data-backups-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-set-up-automatic-data-backups-in-flutterflow
