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
Configure Firebase Storage security rules
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.
1// Firebase Storage Security Rules2rules_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 folder10 match /users/{userId}/{allPaths=**} {11 allow write: if request.auth != null12 && request.auth.uid == userId13 && request.resource.size < 50 * 1024 * 102414 && 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.
Upload images using the built-in Upload Media widget
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.
Upload any file type using a Custom Action
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).
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';56Future<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 {};1314 final file = result.files.first;15 final bytes = file.bytes;16 if (bytes == null) return {};1718 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';2223 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();2728 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.
Track upload progress and show a progress bar
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.
1import 'dart:async';2import 'package:firebase_storage/firebase_storage.dart';34Future<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.
Download files by launching the stored URL
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
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';78/// 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 '';1819 final file = result.files.first;20 final bytes = file.bytes;21 if (bytes == null) return '';2223 final uid = FirebaseAuth.instance.currentUser?.uid;24 if (uid == null) return '';2526 final fileName = file.name;27 final storagePath = 'users/$uid/$folder/$fileName';28 final mimeType = lookupMimeType(fileName) ?? 'application/octet-stream';2930 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();3839 final docRef = await FirebaseFirestore.instance40 .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}5859/// 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.instance64 .collection('user_files')65 .doc(fileDocId)66 .delete();67 return true;68 } catch (e) {69 return false;70 }71}7273/// 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation