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

How to Implement a Custom File Management System in FlutterFlow

Build a complete file management system in FlutterFlow by storing file metadata in Firestore (not just Firebase Storage paths). Create separate `files` and `folders` collections with parent references for nested hierarchy. This lets you rename, move, search, and delete files without re-uploading — things Firebase Storage paths alone cannot support.

What you'll learn

  • Set up Firestore `files` and `folders` collections with parent-child hierarchy
  • Build folder navigation with breadcrumb trail using page state
  • Implement rename, move, and delete operations via Firestore document updates
  • Add file search and per-user storage quota tracking
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner11 min read45-60 minFlutterFlow Pro+ (code export required for custom Cloud Functions)March 2026RapidDev Engineering Team
TL;DR

Build a complete file management system in FlutterFlow by storing file metadata in Firestore (not just Firebase Storage paths). Create separate `files` and `folders` collections with parent references for nested hierarchy. This lets you rename, move, search, and delete files without re-uploading — things Firebase Storage paths alone cannot support.

Why Firestore Metadata Is the Key to a Real File Manager

Firebase Storage is excellent for storing binary files, but it has no concept of folders, filenames, or metadata beyond what you embed in storage paths. If you want to let users rename a file, you would need to download it and re-upload it to a new path — there is no rename API. The solution is to treat Firebase Storage as a raw byte store and keep all user-facing metadata — filename, parent folder, owner, size, MIME type, created/modified timestamps — in Firestore documents. Your FlutterFlow UI reads and writes Firestore for navigation and management operations, while the actual bytes live in Storage. This pattern gives you full CRUD semantics on files and folders without touching Storage at all for most operations.

Prerequisites

  • FlutterFlow project connected to Firebase (Firestore + Storage enabled)
  • Firebase Authentication set up with at least email/password sign-in
  • Familiarity with Firestore collections and document fields in FlutterFlow
  • FlutterFlow Pro plan for custom Cloud Functions (delete operations)

Step-by-step guide

1

Create the Firestore `folders` and `files` collections

In your Firebase console, you do not need to manually create collections — FlutterFlow will generate them from your schema. In FlutterFlow, go to Firestore > Create Collection. Name the first collection `folders`. Add fields: `name` (String), `ownerId` (String), `parentId` (String, nullable — null means root), `createdAt` (Timestamp), `path` (String, stores slash-joined ancestor IDs for breadcrumbs). Create a second collection named `files` with fields: `name` (String), `ownerId` (String), `folderId` (String — parent folder doc ID), `storageUrl` (String), `storagePath` (String), `mimeType` (String), `sizeBytes` (Integer), `createdAt` (Timestamp), `updatedAt` (Timestamp). Set Firestore Security Rules so only `request.auth.uid == resource.data.ownerId` can read/write each document.

Expected result: Two Firestore collections appear in your FlutterFlow schema panel with all fields defined.

2

Build the File Manager page with folder list and file list

Create a new page called `FileManager`. Add two Page State variables: `currentFolderId` (String, default empty — means root) and `breadcrumbs` (JSON Array, default `[]`). Add a Column widget at the top for the breadcrumb bar — use a Row with a ListView of breadcrumb items, each showing the folder name with a tap action that sets `currentFolderId` to that folder's ID. Below that, add a ListView for subfolders: query the `folders` collection where `parentId == currentFolderId` and `ownerId == currentUser.uid`, ordered by `name`. Add a second ListView for files: query `files` where `folderId == currentFolderId` and `ownerId == currentUser.uid`. Each list tile should show name, size (formatted), and a three-dot menu for Rename, Move, and Delete.

Expected result: The page shows an empty root folder on first load. The two lists appear side by side or stacked depending on your layout.

3

Implement folder creation and navigation

Add a FloatingActionButton with a + icon. On tap, show a bottom sheet dialog with a TextField for folder name and a Create button. The Create button action should: validate the name is not empty, then run a Firestore Create Document action on the `folders` collection setting `name` to the TextField value, `ownerId` to `currentUser.uid`, `parentId` to `currentFolderId` page state, `createdAt` to current time, and `path` to a concatenation of the current folder's path + `/` + the new doc ID (use a Custom Function for this). For navigation, wire the folder list tile's on-tap to: update `currentFolderId` page state to the tapped folder's document ID, and append `{id, name}` to the `breadcrumbs` page state array. Tapping a breadcrumb item should slice the array back to that index and update `currentFolderId`.

Expected result: Users can create folders, tap into them, and the breadcrumb bar updates to show the current hierarchy.

4

Add file upload with Firestore metadata write

Add an Upload File action (FlutterFlow built-in) triggered by a button or FAB option. After the upload completes, the action returns the download URL and storage path. Chain a second action: Firestore Create Document on `files` with `name` set to the extracted filename (use a Custom Function to parse the filename from the storage path), `ownerId` = currentUser.uid, `folderId` = currentFolderId page state, `storageUrl` = returned URL, `storagePath` = returned storage path, `mimeType` = detected MIME type (Custom Function from file extension), `sizeBytes` = file size in bytes (available from the upload result), `createdAt` and `updatedAt` = current time. The file list query will automatically refresh because you are using a live Firestore query widget.

Expected result: After uploading a file, it appears immediately in the file list with name, size, and timestamp.

5

Implement rename and move via Firestore document updates

For Rename: show a dialog with a pre-filled TextField containing the current name. On confirm, run a Firestore Update Document action on the specific file or folder document, setting only the `name` field (and `updatedAt` for files). No Storage operation is needed — the bytes do not move. For Move: show a folder picker (a new bottom sheet that queries `folders` where `ownerId == currentUser.uid` as a ListView). On folder selection, run Firestore Update Document on the file's document, setting `folderId` to the selected folder's ID. For folders, set `parentId` to the new parent's ID and update `path`. Neither operation touches Firebase Storage at all.

Expected result: Files and folders can be renamed inline. Files appear in the new folder immediately after a move without re-uploading.

6

Add search and storage quota display

For search: add a SearchBar widget at the top of the page. Wire its `onTextChanged` event to set a page state variable `searchQuery`. Add a conditional: if `searchQuery` is not empty, show a third ListView that queries `files` where `ownerId == currentUser.uid` and `name` contains `searchQuery` (Firestore `>=` / `<=` trick or array-contains on a `nameTokens` field). For quota: add a subtitle row showing `usedMB / totalMB`. Create a Cloud Function (callable) that queries all files for the current user, sums `sizeBytes`, and returns the total. Call this function on page load and store the result in a page state variable. Display a LinearProgressIndicator showing used vs. a configurable quota limit (store the limit in a Firestore `userSettings` document).

Expected result: The search bar filters files across all folders. The quota bar updates after each upload or delete.

7

Implement delete with Storage cleanup via Cloud Function

Deleting a file requires two steps: remove the Firestore document AND delete the object from Firebase Storage. FlutterFlow's built-in Firestore Delete action handles the Firestore side. For Storage deletion, create a Firebase Cloud Function (callable) named `deleteFile` that accepts `storagePath`, verifies the caller owns that file by checking Firestore, then calls `admin.storage().bucket().file(storagePath).delete()`. In FlutterFlow, wire the Delete menu option to: first call the `deleteFile` Cloud Function with the file's `storagePath`, then run Firestore Delete Document on the `files` document. For folder deletion, recursively delete all child files and folders in the Cloud Function before deleting the folder document.

functions/index.js
1// Firebase Cloud Function — index.js
2const { onCall } = require('firebase-functions/v2/https');
3const { getFirestore } = require('firebase-admin/firestore');
4const { getStorage } = require('firebase-admin/storage');
5
6exports.deleteFile = onCall(async (request) => {
7 const uid = request.auth?.uid;
8 if (!uid) throw new Error('Unauthenticated');
9
10 const { storagePath, fileDocId } = request.data;
11
12 // Verify ownership
13 const db = getFirestore();
14 const fileDoc = await db.collection('files').doc(fileDocId).get();
15 if (!fileDoc.exists || fileDoc.data().ownerId !== uid) {
16 throw new Error('Not authorized');
17 }
18
19 // Delete from Storage
20 await getStorage().bucket().file(storagePath).delete();
21
22 // Delete Firestore document
23 await db.collection('files').doc(fileDocId).delete();
24
25 return { success: true };
26});

Expected result: Deleting a file removes it from both the file list and Firebase Storage. The storage quota updates to reflect freed space.

Complete working example

functions/index.js
1const { onCall } = require('firebase-functions/v2/https');
2const { initializeApp } = require('firebase-admin/app');
3const { getFirestore, FieldValue } = require('firebase-admin/firestore');
4const { getStorage } = require('firebase-admin/storage');
5
6initializeApp();
7
8// Delete a file from Storage and Firestore
9exports.deleteFile = onCall(async (request) => {
10 const uid = request.auth?.uid;
11 if (!uid) throw new Error('Unauthenticated');
12 const { storagePath, fileDocId } = request.data;
13 const db = getFirestore();
14 const fileDoc = await db.collection('files').doc(fileDocId).get();
15 if (!fileDoc.exists || fileDoc.data().ownerId !== uid) {
16 throw new Error('Not authorized');
17 }
18 await getStorage().bucket().file(storagePath).delete();
19 await db.collection('files').doc(fileDocId).delete();
20 return { success: true };
21});
22
23// Get total storage used by a user in bytes
24exports.getUserStorageUsed = onCall(async (request) => {
25 const uid = request.auth?.uid;
26 if (!uid) throw new Error('Unauthenticated');
27 const db = getFirestore();
28 const snapshot = await db
29 .collection('files')
30 .where('ownerId', '==', uid)
31 .get();
32 let totalBytes = 0;
33 snapshot.forEach((doc) => {
34 totalBytes += doc.data().sizeBytes || 0;
35 });
36 return { totalBytes, totalMB: Math.round(totalBytes / 1024 / 1024) };
37});
38
39// Recursively delete a folder and all its contents
40exports.deleteFolder = onCall(async (request) => {
41 const uid = request.auth?.uid;
42 if (!uid) throw new Error('Unauthenticated');
43 const { folderId } = request.data;
44 const db = getFirestore();
45
46 async function deleteFolderRecursive(id) {
47 // Delete all files in this folder
48 const files = await db
49 .collection('files')
50 .where('folderId', '==', id)
51 .where('ownerId', '==', uid)
52 .get();
53 for (const file of files.docs) {
54 const { storagePath } = file.data();
55 try {
56 await getStorage().bucket().file(storagePath).delete();
57 } catch (e) {
58 console.warn('Storage delete failed for', storagePath, e.message);
59 }
60 await file.ref.delete();
61 }
62 // Recurse into subfolders
63 const subfolders = await db
64 .collection('folders')
65 .where('parentId', '==', id)
66 .where('ownerId', '==', uid)
67 .get();
68 for (const sub of subfolders.docs) {
69 await deleteFolderRecursive(sub.id);
70 await sub.ref.delete();
71 }
72 }
73
74 const folderDoc = await db.collection('folders').doc(folderId).get();
75 if (!folderDoc.exists || folderDoc.data().ownerId !== uid) {
76 throw new Error('Not authorized');
77 }
78 await deleteFolderRecursive(folderId);
79 await db.collection('folders').doc(folderId).delete();
80 return { success: true };
81});

Common mistakes when implementing a Custom File Management System in FlutterFlow

Why it's a problem: Using Firebase Storage folder paths as the folder system without Firestore metadata

How to avoid: Store all user-facing metadata in Firestore `files` and `folders` documents. Use Storage only to store the raw bytes. Never embed folder hierarchy in the Storage path — treat it as a flat content-addressed store.

Why it's a problem: Deleting Firestore file documents without deleting the Storage object

How to avoid: Always delete the Storage object first (via Cloud Function), then delete the Firestore document. If the Storage delete fails, do not delete the Firestore document — the user can retry.

Why it's a problem: Running the storage quota sum query client-side on every file in a large collection

How to avoid: Use a Cloud Function to compute the quota sum server-side. Alternatively, maintain a running total in a `userStats` document using `FieldValue.increment` on every file upload and delete.

Best practices

  • Always verify file ownership in Cloud Functions before performing Storage operations — do not rely solely on client-side checks.
  • Maintain a `sizeBytes` running total in a `userStats` Firestore document using `FieldValue.increment` to avoid expensive aggregation queries on every quota check.
  • Store the ancestor path string in each `folders` document so you can query all descendants with a single range query instead of recursive client-side traversal.
  • Use Firestore Security Rules with `request.auth.uid == resource.data.ownerId` on both `files` and `folders` collections — never rely on UI-only access control.
  • Namespace Storage paths with the user UID (e.g., `users/{uid}/files/{uuid}`) even though folder hierarchy is in Firestore — this makes Firebase Storage Rules simpler to write.
  • Generate a UUID for each file's Storage path instead of using the original filename to avoid collisions when two users upload files with the same name.
  • Implement soft delete (set a `deletedAt` timestamp instead of removing the document) so users can recover accidentally deleted files within a grace period.

Still stuck?

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

ChatGPT Prompt

I am building a file management system in FlutterFlow with Firebase. I have a `files` Firestore collection with fields: name, ownerId, folderId, storageUrl, storagePath, sizeBytes, createdAt. How do I write Firestore Security Rules that allow users to read and write only their own files, and how do I write a Cloud Function that safely deletes both the Firestore document and the Firebase Storage object in a single atomic operation?

FlutterFlow Prompt

In my FlutterFlow project, I want to implement a breadcrumb navigation bar for a file manager. I have a Page State variable `breadcrumbs` that is a list of objects with `id` and `name`. When the user taps a breadcrumb item at index N, I need to set `currentFolderId` to that item's ID and slice the `breadcrumbs` list to keep only items 0 through N. How do I implement the list slice in a FlutterFlow Custom Function?

Frequently asked questions

Can I rename a file in Firebase Storage without re-uploading it?

No. Firebase Storage has no rename API. To change a file's name, you would have to download it and re-upload it to a new path. This is why the tutorial stores the display name in a Firestore document — renaming updates only the Firestore `name` field, not the Storage object path.

How do I support file search when Firestore does not have full-text search?

The simplest approach for basic prefix search is to store a lowercase version of the filename and use Firestore range queries (`where name >= searchTerm` and `where name <= searchTerm + ''`). For full-text search, export file metadata to Algolia or Typesense via a Cloud Function trigger and query that index from FlutterFlow via an API call.

What happens if the Cloud Function deletes the Storage object but then fails before deleting the Firestore document?

The file will appear in the list but the download URL will return a 404. To handle this, catch the error in FlutterFlow and show a retry option. You can also run a scheduled Cloud Function that checks for Firestore file documents whose Storage objects no longer exist and cleans them up.

How do I enforce a per-user storage quota before allowing an upload?

Before triggering the upload, call the `getUserStorageUsed` Cloud Function to get the current total. Compare it to the user's quota (stored in `userSettings`). If the used amount plus the new file size would exceed the quota, show an error message and block the upload action in FlutterFlow using a Conditional action.

Can I share folders or files with other users in this system?

Yes. Add a `sharedWith` Array field to `files` and `folders` documents containing UIDs of users who have access. Update your Firestore Security Rules to also allow reads when `request.auth.uid in resource.data.sharedWith`. See the secure data sharing tutorial for a full implementation including permission levels.

Does this work on FlutterFlow Free plan?

The Firestore schema, file manager UI, and upload/download flows work on the Free plan. The Cloud Functions for delete and quota aggregation require deploying Firebase Functions, which is free up to Google's limits but requires the Blaze (pay-as-you-go) Firebase plan. FlutterFlow Pro is needed only if you want to export and customize the generated Flutter code.

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.