Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Handle File Uploads and Downloads in FlutterFlow

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.

What you'll learn

  • How to upload images using FlutterFlow's built-in Upload Media widget
  • How to upload any file type using a Custom Action with file_picker
  • How to track upload progress and display it to the user
  • How to store file metadata in Firestore for searchable file management
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner11 min read25-35 minFlutterFlow Free+March 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

storage.rules
1// Firebase Storage Security Rules
2rules_version = '2';
3service firebase.storage {
4 match /b/{bucket}/o {
5 // Users can read any file (adjust if files should be private)
6 match /{allPaths=**} {
7 allow read: if request.auth != null;
8 }
9 // Users can only write to their own folder
10 match /users/{userId}/{allPaths=**} {
11 allow write: if request.auth != null
12 && request.auth.uid == userId
13 && request.resource.size < 50 * 1024 * 1024
14 && request.resource.contentType.matches('image/.*|application/pdf|text/.*');
15 }
16 }
17}

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).

custom_actions/pick_and_upload_file.dart
1import 'package:file_picker/file_picker.dart';
2import 'package:firebase_storage/firebase_storage.dart';
3import 'package:firebase_auth/firebase_auth.dart';
4import 'package:mime/mime.dart';
5
6Future<Map<String, dynamic>> pickAndUploadFile(String uploadPath) async {
7 final result = await FilePicker.platform.pickFiles(
8 type: FileType.custom,
9 allowedExtensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt', 'zip'],
10 withData: true,
11 );
12 if (result == null || result.files.isEmpty) return {};
13
14 final file = result.files.first;
15 final bytes = file.bytes;
16 if (bytes == null) return {};
17
18 final uid = FirebaseAuth.instance.currentUser?.uid ?? 'unknown';
19 final fileName = file.name;
20 final path = 'users/$uid/$uploadPath/$fileName';
21 final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';
22
23 final ref = FirebaseStorage.instance.ref(path);
24 final uploadTask = ref.putData(bytes, SettableMetadata(contentType: mimeType));
25 final snapshot = await uploadTask.whenComplete(() {});
26 final downloadUrl = await snapshot.ref.getDownloadURL();
27
28 return {
29 'download_url': downloadUrl,
30 'file_name': fileName,
31 'file_size': file.size,
32 'mime_type': mimeType,
33 'storage_path': path,
34 };
35}

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.

custom_actions/upload_with_progress.dart
1import 'dart:async';
2import 'package:firebase_storage/firebase_storage.dart';
3
4Future<String> uploadWithProgress(
5 String storagePath,
6 List<int> fileBytes,
7 String mimeType,
8 void Function(double) onProgress,
9) async {
10 final ref = FirebaseStorage.instance.ref(storagePath);
11 final task = ref.putData(
12 Uint8List.fromList(fileBytes),
13 SettableMetadata(contentType: mimeType),
14 );
15 task.snapshotEvents.listen((snapshot) {
16 if (snapshot.totalBytes > 0) {
17 onProgress(snapshot.bytesTransferred / snapshot.totalBytes);
18 }
19 });
20 await task.whenComplete(() {});
21 return await ref.getDownloadURL();
22}

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 working example

custom_actions/file_management.dart
1import 'dart:typed_data';
2import 'package:file_picker/file_picker.dart';
3import 'package:firebase_storage/firebase_storage.dart';
4import 'package:firebase_auth/firebase_auth.dart';
5import 'package:cloud_firestore/cloud_firestore.dart';
6import 'package:mime/mime.dart';
7
8/// Pick a file and upload to Firebase Storage.
9/// Writes metadata to Firestore user_files collection.
10/// Returns the Firestore document ID or empty string on failure.
11Future<String> pickAndUploadFile(String folder) async {
12 final result = await FilePicker.platform.pickFiles(
13 type: FileType.custom,
14 allowedExtensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'txt'],
15 withData: true,
16 );
17 if (result == null || result.files.isEmpty) return '';
18
19 final file = result.files.first;
20 final bytes = file.bytes;
21 if (bytes == null) return '';
22
23 final uid = FirebaseAuth.instance.currentUser?.uid;
24 if (uid == null) return '';
25
26 final fileName = file.name;
27 final storagePath = 'users/$uid/$folder/$fileName';
28 final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';
29
30 try {
31 final ref = FirebaseStorage.instance.ref(storagePath);
32 final task = ref.putData(
33 Uint8List.fromList(bytes),
34 SettableMetadata(contentType: mimeType),
35 );
36 final snapshot = await task.whenComplete(() {});
37 final downloadUrl = await snapshot.ref.getDownloadURL();
38
39 final docRef = await FirebaseFirestore.instance
40 .collection('user_files')
41 .add({
42 'user_id': uid,
43 'file_name': fileName,
44 'download_url': downloadUrl,
45 'storage_path': storagePath,
46 'file_size_bytes': file.size,
47 'mime_type': mimeType,
48 'file_type': mimeType.startsWith('image/') ? 'image'
49 : mimeType == 'application/pdf' ? 'pdf'
50 : 'document',
51 'created_at': FieldValue.serverTimestamp(),
52 });
53 return docRef.id;
54 } catch (e) {
55 return '';
56 }
57}
58
59/// Delete a file from Storage and its Firestore metadata record.
60Future<bool> deleteFile(String fileDocId, String storagePath) async {
61 try {
62 await FirebaseStorage.instance.ref(storagePath).delete();
63 await FirebaseFirestore.instance
64 .collection('user_files')
65 .doc(fileDocId)
66 .delete();
67 return true;
68 } catch (e) {
69 return false;
70 }
71}
72
73/// Format file size for display.
74String formatFileSize(int bytes) {
75 if (bytes < 1024) return '$bytes B';
76 if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
77 return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
78}

Common mistakes when handling File Uploads and Downloads in FlutterFlow

Why it's a problem: Storing file metadata only in Firebase Storage custom metadata, not in Firestore

How to avoid: 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.

Why it's a problem: Using the same filename for every upload without a unique identifier

How to avoid: Prefix every storage path with a UUID or timestamp: users/uid/files/[uuid]-report.pdf. This guarantees uniqueness and prevents overwrites.

Why it's a problem: Not cleaning up Firebase Storage when a Firestore file document is deleted

How to avoid: 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.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a file management feature in a FlutterFlow app with Firebase Storage. Write a Dart Custom Action that: opens a file picker for PDF, Word, and Excel files, uploads the selected file to Firebase Storage under users/[uid]/files/[filename], writes metadata (file_name, download_url, storage_path, file_size_bytes, mime_type, user_id, created_at) to a Firestore user_files collection, and returns the Firestore document ID. Include Firebase Storage security rules that allow users to write only to their own UID path.

FlutterFlow Prompt

In FlutterFlow, I have a user_files Firestore collection with fields: file_name, download_url, mime_type, file_size_bytes, created_at. Build the Action Flow for a Delete button on a file list item that: shows a confirmation AlertDialog, if confirmed calls a deleteFile Custom Action with the document ID and storage_path, removes the item from the list, and shows a SnackBar confirming deletion.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.