# How to Build a Custom File Uploader With Progress Tracking in FlutterFlow

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

## TL;DR

Build a multi-file uploader that shows real-time progress per file using Firebase Storage upload task streams. A Custom Action picks files via the file_picker package, uploads them in parallel with putFile(), and streams progress updates to a Page State Map that drives per-file LinearPercentIndicator widgets. Download URLs are collected after all uploads complete and saved to Firestore.

## Building a Multi-File Uploader With Real-Time Progress in FlutterFlow

The built-in FlutterFlowUploadButton handles single-file uploads but offers no progress feedback and no multi-file support. This tutorial builds a Custom Action that picks multiple files, uploads them to Firebase Storage in parallel, streams progress to the UI, and stores the resulting download URLs in a Firestore document.

## Before you start

- A FlutterFlow project with Firebase configured and Storage enabled
- FlutterFlow Pro plan or higher for Custom Actions
- Basic understanding of Page State variables in FlutterFlow
- Firebase Storage rules allowing authenticated writes

## Step-by-step guide

### 1. Create a Custom Action to pick multiple files with file_picker

Open Custom Code in FlutterFlow and create a new Custom Action named pickMultipleFiles. Add the file_picker dependency. The action calls FilePicker.platform.pickFiles(allowMultiple: true, type: FileType.any) and returns the list of PlatformFile objects. Add an Action Output of type JSON List so the parent page can receive the file names and paths. Call this action from a Select Files button tap.

```
// Custom Action: pickMultipleFiles
import 'package:file_picker/file_picker.dart';

Future<List<dynamic>> pickMultipleFiles() async {
  final result = await FilePicker.platform.pickFiles(
    allowMultiple: true,
    type: FileType.any,
  );
  if (result == null) return [];
  return result.files.map((f) => {
    'name': f.name,
    'path': f.path,
    'size': f.size,
  }).toList();
}
```

**Expected result:** Tapping Select Files opens the system file picker and returns a list of file metadata to the page.

### 2. Display selected files in a ListView with progress indicators

Add a Page State variable selectedFiles (JSON List) and uploadProgress (JSON, default empty map). When pickMultipleFiles returns, set selectedFiles to the result. Add a ListView with Generate Dynamic Children bound to selectedFiles. Each child row contains a Row with: Text showing the file name, Text showing file size formatted in KB/MB, a LinearPercentIndicator bound to uploadProgress[fileName] defaulting to 0.0, and a status icon (check or spinner).

**Expected result:** Selected files appear in a list, each with a filename label, size, and a progress bar starting at zero.

### 3. Create a Custom Action that uploads files with progress streaming

Create a Custom Action named uploadFilesWithProgress that accepts the selected files list as a parameter. For each file, create a Firebase Storage reference at users/{uid}/uploads/{fileName}, call ref.putFile(File(path)), and listen to uploadTask.snapshotEvents. On each snapshot, calculate progress as bytesTransferred / totalBytes and update the Page State uploadProgress map entry for that file. Use Future.wait to run all uploads in parallel. After each upload completes, call ref.getDownloadURL() and collect all URLs into a return list.

```
// Custom Action: uploadFilesWithProgress
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';

Future<List<String>> uploadFilesWithProgress(
  List<dynamic> files,
  Future<void> Function(String fileName, double progress) onProgress,
) async {
  final storage = FirebaseStorage.instance;
  final uid = currentUserUid;
  final futures = files.map((file) async {
    final name = file['name'] as String;
    final path = file['path'] as String;
    final ref = storage.ref('users/$uid/uploads/$name');
    final task = ref.putFile(File(path));
    task.snapshotEvents.listen((snap) {
      final pct = snap.bytesTransferred / snap.totalBytes;
      onProgress(name, pct);
    });
    await task;
    return await ref.getDownloadURL();
  });
  return Future.wait(futures);
}
```

**Expected result:** Files upload in parallel and each progress bar updates in real time as bytes transfer to Firebase Storage.

### 4. Wire the Upload All button to trigger uploads and update progress

Add an Upload All button below the file list. On Tap, call the uploadFilesWithProgress Custom Action passing the selectedFiles Page State. Inside the action callback, update uploadProgress by setting the key for each filename to the current progress value. This drives the LinearPercentIndicator widgets in the ListView. When the action returns the list of download URLs, store them in a Page State variable downloadUrls.

**Expected result:** Tapping Upload All starts parallel uploads and every progress bar animates from 0 to 100 percent in real time.

### 5. Save download URLs to a Firestore document after all uploads complete

After uploadFilesWithProgress completes and downloadUrls is populated, create or update a Firestore document. For example, in an uploads collection create a document with fields: userId (current user), fileUrls (the downloadUrls list), uploadedAt (server timestamp), and fileCount (number of files). Show a success SnackBar confirming all files uploaded. Optionally reset the selectedFiles and uploadProgress Page State variables to clear the UI for the next batch.

**Expected result:** A Firestore document stores all download URLs and the UI shows a success message with the option to upload more files.

## Complete code example

File: `upload_files_with_progress.dart`

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

/// Pick multiple files from the device
Future<List<Map<String, dynamic>>> pickMultipleFiles() async {
  final result = await FilePicker.platform.pickFiles(
    allowMultiple: true,
    type: FileType.any,
  );
  if (result == null) return [];
  return result.files
      .map((f) => {'name': f.name, 'path': f.path ?? '', 'size': f.size})
      .toList();
}

/// Upload files in parallel with per-file progress callback
Future<List<String>> uploadFilesWithProgress(
  List<Map<String, dynamic>> files,
  void Function(String fileName, double progress) onProgress,
) async {
  final storage = FirebaseStorage.instance;
  final uid = FirebaseAuth.instance.currentUser!.uid;

  final futures = files.map((file) async {
    final name = file['name'] as String;
    final filePath = file['path'] as String;
    final ref = storage.ref('users/$uid/uploads/$name');
    final task = ref.putFile(File(filePath));

    task.snapshotEvents.listen((snapshot) {
      final progress = snapshot.bytesTransferred / snapshot.totalBytes;
      onProgress(name, progress);
    });

    await task;
    return await ref.getDownloadURL();
  });

  return Future.wait(futures);
}

/// Save completed upload URLs to Firestore
Future<void> saveUploadRecord(List<String> urls) async {
  final uid = FirebaseAuth.instance.currentUser!.uid;
  await FirebaseFirestore.instance.collection('uploads').add({
    'userId': uid,
    'fileUrls': urls,
    'fileCount': urls.length,
    'uploadedAt': FieldValue.serverTimestamp(),
  });
}
```

## Common mistakes

- **Uploading files sequentially instead of in parallel** — Sequential uploads block the user while each file finishes before the next begins, making total upload time the sum of all files rather than the duration of the largest file. Fix: Use Future.wait() to run all upload tasks simultaneously. Each task streams its own progress independently.
- **Using the built-in FlutterFlowUploadButton for multi-file needs** — FlutterFlowUploadButton only supports one file at a time and provides no progress feedback. Users have no idea how long the upload will take. Fix: Build a Custom Action with file_picker for multi-file selection and Firebase Storage snapshotEvents for progress tracking.
- **Not handling upload failures for individual files** — If one file fails in a parallel batch, Future.wait throws and you lose the URLs of files that succeeded. The user must re-upload everything. Fix: Wrap each upload in a try-catch. Collect results as either a URL or an error per file so succeeded uploads are preserved and only failed files need retry.

## Best practices

- Upload files in parallel with Future.wait for faster total upload time
- Show per-file progress bars so users see activity on each file independently
- Set file size limits before upload to prevent Storage quota issues
- Store download URLs in Firestore immediately after upload completes
- Use unique file paths per user to prevent filename collisions across accounts
- Add a Cancel button that calls uploadTask.cancel() for long uploads
- Clear the file list and progress state after successful batch upload

## Frequently asked questions

### Can I limit the file types users can upload?

Yes. In the file_picker call, set type: FileType.custom and allowedExtensions: ['pdf', 'jpg', 'png'] to restrict selectable file types.

### How do I set a maximum file size?

After picking files, check each file's size property. Filter out files exceeding your limit and show a SnackBar explaining which files were too large.

### Does this work on FlutterFlow web apps?

Yes, but use file bytes instead of file paths since web browsers do not expose file system paths. Set withData: true in pickFiles and use putData instead of putFile.

### Can I pause and resume uploads?

Yes. Firebase Storage upload tasks support pause(), resume(), and cancel(). Store the UploadTask reference and call these methods from Pause and Resume buttons.

### How do I handle upload failures for a single file in a batch?

Wrap each individual upload in a try-catch block. Collect successful URLs and failed filenames separately. Show the user which files failed and offer a retry button for just those files.

### Can RapidDev help build a file management system with upload tracking?

Yes. RapidDev can build full file management systems with multi-file upload, progress tracking, file previews, organization folders, and storage quota management.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-custom-file-uploader-with-progress-tracking-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-custom-file-uploader-with-progress-tracking-in-flutterflow
