# How to Handle File Uploads and Downloads in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 25-35 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## TL;DR

FlutterFlow's Upload Media widget handles image and video uploads to Firebase Storage. For other file types (PDFs, spreadsheets, ZIPs), use a Custom Action with the file_picker package. Always store file metadata — name, URL, size, MIME type — in a Firestore collection so you can query, filter, and search files. Download by launching the Firebase Storage download URL.

## Complete File Management in FlutterFlow

FlutterFlow provides a built-in Upload Media action for images and videos that writes directly to Firebase Storage with a single action in the Action Flow. For all other file types — PDFs, Word documents, CSVs, ZIPs — you need a Custom Action using the file_picker package. The most critical mistake developers make is storing file metadata only in Firebase Storage (as custom metadata on the file object). Firestore cannot query Firebase Storage metadata — you cannot list 'all PDFs uploaded by Alice' or 'files larger than 5MB' from Storage alone. Always write a matching Firestore document for every uploaded file. This tutorial covers both upload paths, progress tracking, and a complete file management collection schema.

## Before you start

- FlutterFlow project with Firebase connected
- Firebase Storage bucket enabled in Firebase console
- Firebase Storage security rules configured to allow authenticated writes
- file_picker package added to pubspec.yaml (^8.0.0) for non-image uploads
- Basic familiarity with FlutterFlow Action Flows and Custom Actions

## Step-by-step guide

### 1. Configure Firebase Storage security rules

Before any uploads work, Firebase Storage needs security rules that allow authenticated users to read and write their own files. Open Firebase console > Storage > Rules and set rules that allow read to any authenticated user and write only to paths under the user's own UID. A good starting pattern is: allow read if request.auth != null, allow write if request.auth.uid == [path segment matching UID]. Also set a maximum upload size rule to prevent oversized files: allow write if request.resource.size < 50 * 1024 * 1024 (50MB limit). In FlutterFlow, go to Project Settings > Firebase > Storage and confirm the bucket URL is connected. Without correct security rules, uploads will silently fail with a 403 permission error.

```
// Firebase Storage Security Rules
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    // Users can read any file (adjust if files should be private)
    match /{allPaths=**} {
      allow read: if request.auth != null;
    }
    // Users can only write to their own folder
    match /users/{userId}/{allPaths=**} {
      allow write: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 50 * 1024 * 1024
        && request.resource.contentType.matches('image/.*|application/pdf|text/.*');
    }
  }
}
```

**Expected result:** Firebase Storage Rules show the updated rules; a test upload from the app succeeds without a 403 error.

### 2. Upload images using the built-in Upload Media widget

In FlutterFlow, select any Button or IconButton on your page and open the Action Flow. Add an Upload Photo/Video action (under Utilities > Upload Data). Set the storage path to users/[Current User UID]/images/[timestamp]. Choose whether to allow the camera, gallery, or both. Store the returned upload URL in a page state variable imageUrl. After the upload completes, add a Create Document action to write a Firestore document in a user_files collection with: user_id (Current User UID), file_name (the original filename), download_url (imageUrl page state), file_type (image), file_size_bytes (from the Upload action output), created_at (server timestamp). This two-step pattern — upload then record metadata — is the foundation of all file management in FlutterFlow.

**Expected result:** Selecting an image from the gallery uploads it to Firebase Storage and creates a matching Firestore document with the download URL.

### 3. Upload any file type using a Custom Action

For PDFs, Word documents, and other non-image files, create a Custom Action named pickAndUploadFile. It takes uploadPath (String) and returns a Map with keys: download_url, file_name, file_size, mime_type. Inside the action, use file_picker to open the file selector (filtering to allowed extensions), read the file bytes, upload to Firebase Storage using firebase_storage's putData method, retrieve the download URL, and return all metadata. In the FlutterFlow Action Flow, call this action on a button tap, then use the returned values to create a Firestore document in user_files. Show a CircularProgressIndicator while the action is running (set a page state isUploading to true before the action and false after).

```
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:mime/mime.dart';

Future<Map<String, dynamic>> pickAndUploadFile(String uploadPath) async {
  final result = await FilePicker.platform.pickFiles(
    type: FileType.custom,
    allowedExtensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt', 'zip'],
    withData: true,
  );
  if (result == null || result.files.isEmpty) return {};

  final file = result.files.first;
  final bytes = file.bytes;
  if (bytes == null) return {};

  final uid = FirebaseAuth.instance.currentUser?.uid ?? 'unknown';
  final fileName = file.name;
  final path = 'users/$uid/$uploadPath/$fileName';
  final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';

  final ref = FirebaseStorage.instance.ref(path);
  final uploadTask = ref.putData(bytes, SettableMetadata(contentType: mimeType));
  final snapshot = await uploadTask.whenComplete(() {});
  final downloadUrl = await snapshot.ref.getDownloadURL();

  return {
    'download_url': downloadUrl,
    'file_name': fileName,
    'file_size': file.size,
    'mime_type': mimeType,
    'storage_path': path,
  };
}
```

**Expected result:** Tapping the upload button opens the file picker; selecting a PDF uploads it to Storage and the returned download URL is valid.

### 4. Track upload progress and show a progress bar

The basic file upload Custom Action in Step 3 uses whenComplete() which only returns after the upload finishes. For large files, users need a progress indicator. Create a second Custom Action named uploadWithProgress that accepts the same parameters plus an onProgress callback (void Function(double percent)). Inside, use UploadTask.snapshotEvents stream to listen for progress updates, calling onProgress with (snapshot.bytesTransferred / snapshot.totalBytes * 100). In FlutterFlow, this requires a Custom Widget or a page state variable approach: update a page state variable uploadProgress (double 0.0 to 1.0) from the action, and bind a LinearProgressIndicator's value property to this page state variable. Display the percentage as text beside the progress bar.

```
import 'dart:async';
import 'package:firebase_storage/firebase_storage.dart';

Future<String> uploadWithProgress(
  String storagePath,
  List<int> fileBytes,
  String mimeType,
  void Function(double) onProgress,
) async {
  final ref = FirebaseStorage.instance.ref(storagePath);
  final task = ref.putData(
    Uint8List.fromList(fileBytes),
    SettableMetadata(contentType: mimeType),
  );
  task.snapshotEvents.listen((snapshot) {
    if (snapshot.totalBytes > 0) {
      onProgress(snapshot.bytesTransferred / snapshot.totalBytes);
    }
  });
  await task.whenComplete(() {});
  return await ref.getDownloadURL();
}
```

**Expected result:** A LinearProgressIndicator fills from left to right as the file uploads; the percentage label updates in real time.

### 5. Download files by launching the stored URL

Firebase Storage download URLs are permanent (unless the file is deleted or the URL is revoked). To trigger a download in FlutterFlow, use the Launch URL action on any button, passing the download_url field from the Firestore user_files document. On mobile, this opens the file in the device's default handler — PDFs open in the PDF viewer, images open in the gallery. For in-app file viewing, use a Custom Widget with webview_flutter to display PDFs and images inside the app without leaving. To show a file list, create a ListView bound to the user_files Firestore collection filtered by user_id equals Current User UID, showing file_name, file_type icon (based on mime_type), formatted file size using a Custom Function, and the upload date.

**Expected result:** Tapping a file in the list launches the download URL, opening the file in the appropriate device app.

## Complete code example

File: `custom_actions/file_management.dart`

```dart
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mime/mime.dart';

/// Pick a file and upload to Firebase Storage.
/// Writes metadata to Firestore user_files collection.
/// Returns the Firestore document ID or empty string on failure.
Future<String> pickAndUploadFile(String folder) async {
  final result = await FilePicker.platform.pickFiles(
    type: FileType.custom,
    allowedExtensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt'],
    withData: true,
  );
  if (result == null || result.files.isEmpty) return '';

  final file = result.files.first;
  final bytes = file.bytes;
  if (bytes == null) return '';

  final uid = FirebaseAuth.instance.currentUser?.uid;
  if (uid == null) return '';

  final fileName = file.name;
  final storagePath = 'users/$uid/$folder/$fileName';
  final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';

  try {
    final ref = FirebaseStorage.instance.ref(storagePath);
    final task = ref.putData(
      Uint8List.fromList(bytes),
      SettableMetadata(contentType: mimeType),
    );
    final snapshot = await task.whenComplete(() {});
    final downloadUrl = await snapshot.ref.getDownloadURL();

    final docRef = await FirebaseFirestore.instance
        .collection('user_files')
        .add({
      'user_id': uid,
      'file_name': fileName,
      'download_url': downloadUrl,
      'storage_path': storagePath,
      'file_size_bytes': file.size,
      'mime_type': mimeType,
      'file_type': mimeType.startsWith('image/') ? 'image'
          : mimeType == 'application/pdf' ? 'pdf'
          : 'document',
      'created_at': FieldValue.serverTimestamp(),
    });
    return docRef.id;
  } catch (e) {
    return '';
  }
}

/// Delete a file from Storage and its Firestore metadata record.
Future<bool> deleteFile(String fileDocId, String storagePath) async {
  try {
    await FirebaseStorage.instance.ref(storagePath).delete();
    await FirebaseFirestore.instance
        .collection('user_files')
        .doc(fileDocId)
        .delete();
    return true;
  } catch (e) {
    return false;
  }
}

/// Format file size for display.
String formatFileSize(int bytes) {
  if (bytes < 1024) return '$bytes B';
  if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
  return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
```

## Common mistakes

- **Storing file metadata only in Firebase Storage custom metadata, not in Firestore** — Firebase Storage does not support queries. You cannot list 'all PDFs', 'files uploaded this week', or 'files shared with a specific user' using Storage alone. Storage is a dumb object store. Fix: Always write a Firestore document for every uploaded file with file_name, download_url, mime_type, file_size_bytes, user_id, and created_at. Use the Firestore collection as the source of truth for all file queries and lists.
- **Using the same filename for every upload without a unique identifier** — If a user uploads two files named 'report.pdf', the second upload overwrites the first in Storage. The Firestore record points to the new file, but the old file URL is now broken. Fix: Prefix every storage path with a UUID or timestamp: users/uid/files/[uuid]-report.pdf. This guarantees uniqueness and prevents overwrites.
- **Not cleaning up Firebase Storage when a Firestore file document is deleted** — Deleting the Firestore document removes the metadata but leaves the actual file in Storage. Over time, the Storage bucket fills with orphaned files that incur storage costs with no way to identify or clean them up. Fix: Always delete the Storage object when deleting the Firestore document. Use the storage_path field stored in Firestore to look up the Storage reference for deletion.

## Best practices

- Store file metadata in Firestore for every uploaded file — Storage alone cannot be queried.
- Use UUIDs or timestamps in storage paths to prevent filename collisions.
- Always delete both the Storage file and the Firestore metadata document together.
- Set Firebase Storage security rules to restrict write access to the user's own UID path.
- Show a progress indicator for files over 1MB — users will think the app is frozen without feedback.
- Validate file types and sizes on the client before uploading to avoid rejected requests.
- Store the storage_path in Firestore (not just the download URL) so you can delete the file later.
- Use signed URLs with short expiry for sensitive documents instead of permanent public download URLs.

## Frequently asked questions

### What is the maximum file size I can upload to Firebase Storage from FlutterFlow?

Firebase Storage has no hard upload limit on the server side. However, FlutterFlow's built-in Upload Media action may have practical limits depending on device memory. For large files (over 100MB), use a Custom Action with resumable uploads (Firebase Storage's putFile with a file path rather than putData with bytes in memory). Also check your Firebase Storage security rules — the request.resource.size limit in your rules caps uploads at the configured size.

### How do I display a PDF inside the app instead of launching an external viewer?

Use the flutter_pdfview or pdfx package as a Custom Widget. Pass the download URL to the PDFView widget which renders the PDF inline. Note that flutter_pdfview requires downloading the file first — use the http package to download to a local temp file, then pass the file path to PDFView. For web, an iframe or webview pointing to the download URL works more simply.

### Can I upload multiple files at once in FlutterFlow?

Yes, using a Custom Action. Set FilePicker's allowMultiple: true parameter. This returns a FilePickerResult with a list of PlatformFile objects. Loop through the list, uploading each file individually and creating a Firestore document for each. Display a progress bar that shows completion count (e.g., '2 of 5 uploaded'). FlutterFlow's built-in Upload Media action handles only one file at a time.

### How do I generate a temporary download link that expires?

Firebase Storage download URLs are permanent by default. For temporary access, use the Firebase Admin SDK in a Cloud Function to generate a signed URL with an expiry: bucket.file(path).getSignedUrl({ action: 'read', expires: Date.now() + 24 * 60 * 60 * 1000 }). Call this Cloud Function from FlutterFlow when a user requests a download, and redirect them to the signed URL. This is useful for sensitive documents that should not be permanently accessible.

### Why does my upload succeed but the download URL returns a 403 error?

A 403 on a download URL means Firebase Storage security rules are blocking read access. Check your Storage rules — if you have allow read: if request.auth != null, the user must be signed in. If the file is being accessed from a web browser without Firebase Auth context, use a signed URL instead of the default download URL. Also confirm the Firebase Storage bucket is not in a region that requires a specific CORS configuration for web access.

### How do I let users share files with other users?

Add a shared_with array field to the Firestore user_files document containing UIDs of users who have access. Update Firebase Storage rules to allow read if the requester's UID is in the shared_with array (this requires a Firestore lookup in rules, which is supported). In the app, show a Share button that adds a target user's UID to the shared_with array. The target user's file list query should include a secondary query: user_files where shared_with array-contains Current User UID.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-handle-file-uploads-and-downloads-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-handle-file-uploads-and-downloads-in-flutterflow
