Upload a video (or image/audio) from Flutter to Firebase via a Cloud Function

When building a Flutter application backed by Firebase, file upload (video, audio, image) is a common feature. By default, many use Firebase Storage directly from Flutter. But in some cases, it is preferable, or even necessary, to go through a Cloud Function: for security reasons, server-side processing, or advanced business logic. Here is a complete example of implementing a binary upload via a Firebase backend.
Why not use Firebase Storage on the client side?
Firebase provides a very convenient SDK to upload a file directly from a Flutter application. However, this approach has several limitations :
- You must manage roles and fine-grained access permissions.
- You cannot encapsulate business logic (validation, AI processing, etc.).
- The file is sent as-is, without the possibility of server-side intervention.
As soon as you want more control (verify the user, transcribe the video, generate a thumbnail, etc.), going through a Cloud Function becomes the best option.
On the Flutter side: upload a file with Dio
In this example, the file is selected by the user (via file_picker, image_picker, or other), then sent as multipart/form-data to a Firebase URL (Cloud Function exposed via HTTP).
videoFile is of type XFile.
Here is the corresponding Flutter code :
final bytes = await videoFile.readAsBytes();
final formData = FormData.fromMap({
'video': MultipartFile.fromBytes(
bytes,
filename: videoFile.name,
contentType: DioMediaType.parse(videoFile.mimeType ?? 'video/mp4'),
),
});
final response = await dio.post(
_url,
data: formData,
queryParameters: {
'serieId': serieId,
'language': language,
},
options: Options(
headers: {
'Content-Type': 'multipart/form-data',
'x-user-id': userId,
},
),
);
We use Dio here to send a multipart POST request containing the file. The userId is passed in the headers to be verified on the server side.
On the Firebase side: parse multipart/form-data with Busboy
Firebase does not natively support parsing multipart request bodies. To retrieve files sent from Flutter, we therefore use the Node.js library busboy. It allows extracting form fields and files, even in streaming mode.
Let's install busboy :
npm install busboy
import busboy from "busboy";
import { buffer } from "stream/consumers";
import { Request } from "firebase-functions/v2/https";
import { Response } from "firebase-functions/v1";
import { Readable } from "stream";
interface FilePart {
fieldName: string;
filename: string;
buf: Buffer;
}
interface FormParts {
fields: Record<string, string>;
files: FilePart[];
}
function getParts(req: Request): Promise<FormParts> {
const bb = busboy({ headers: req.headers });
const fields: Record<string, string> = {};
const files: { fieldName: string; filename: string; buf: Promise<Buffer> }[] =
[];
bb.on("field", (fieldName: string, val: string) => (fields[fieldName] = val));
bb.on(
"file",
(fieldName: string, file: Readable, { filename }: { filename: string }) => {
if (filename) {
files.push({ fieldName, filename, buf: buffer(file) });
} else {
file.resume();
}
}
);
return new Promise((resolve) => {
bb.on("finish", () =>
Promise.all(files.map((o) => o.buf)).then((ar) => {
const processedFiles: FilePart[] = files.map((o, i) => ({
fieldName: o.fieldName,
filename: o.filename,
buf: ar[i] || Buffer.alloc(0), // Provide a default empty buffer if undefined
}));
resolve({ fields, files: processedFiles });
})
);
bb.end(req.rawBody);
});
}
export { getParts, addCorsHeaders };
Once the files are extracted (as Buffers), they can be stored in Firebase Storage via @google-cloud/storage.
Integration with business logic
In our use case, the video is linked to a series. We therefore pass the serieId and language parameters in the URL. On the server side, we verify :
- that the user is authenticated and has the right to upload,
- that the request is valid,
- and we call an
UploadSerieVideocommand with the Firebase dependencies (Firestore + Storage).
const { files } = await getParts(req);
const result = await command.execute({
serieId,
language,
videoFile: files[0]!.buf,
videoFileName: files[0]!.filename,
});
This approach allows encapsulating business logic, validating data, and ensuring that only an authorized user can trigger this upload.
Conclusion
Uploading a file from Flutter to Firebase via a Cloud Function is a powerful strategy for those looking to build a robust, secure, and easily extensible backend. It allows adding intermediate processing, protecting access to resources, and centralizing business logic in a single backend.
Comments
Loading...