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

How to Add Video Editing Features to Your FlutterFlow App

FlutterFlow does not include a built-in video editor — the FlutterFlowVideoPlayer widget only plays videos. To add editing, use the video_editor Flutter package for trim and crop via custom code, or offload heavy processing to a Firebase Cloud Function running FFmpeg on Cloud Run. Never run FFmpeg locally on files longer than 30 seconds.

What you'll learn

  • Why FlutterFlow has no built-in video editor and what to use instead
  • How to integrate the video_editor package for client-side trim and crop
  • How to upload videos to Firebase Storage for cloud processing
  • How to trigger a Cloud Function that runs FFmpeg on Cloud Run
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner10 min read30-45 minFlutterFlow Pro+ (code export required)March 2026RapidDev Engineering Team
TL;DR

FlutterFlow does not include a built-in video editor — the FlutterFlowVideoPlayer widget only plays videos. To add editing, use the video_editor Flutter package for trim and crop via custom code, or offload heavy processing to a Firebase Cloud Function running FFmpeg on Cloud Run. Never run FFmpeg locally on files longer than 30 seconds.

Video Editing in FlutterFlow: What Is Actually Possible

Many builders expect FlutterFlow to include a video editor alongside its video player, but the FlutterFlowVideoPlayer widget only handles playback. Actual editing — trimming clips, cropping frames, adding overlays — requires either a custom widget using the video_editor Dart package (for lightweight, on-device operations) or a server-side pipeline using Firebase Storage and a Cloud Function backed by FFmpeg running on Cloud Run (for heavy processing). This tutorial covers both paths so you can choose the right approach for your use case.

Prerequisites

  • FlutterFlow Pro plan with code export enabled
  • Firebase project connected to your FlutterFlow app
  • Firebase Storage bucket configured
  • Basic understanding of FlutterFlow Custom Actions
  • Cloud Functions billing enabled (Blaze plan on Firebase)

Step-by-step guide

1

Understand what FlutterFlowVideoPlayer can and cannot do

Open any FlutterFlow page, drag a VideoPlayer widget from the widget panel onto the canvas, and inspect its properties. You will see controls for autoplay, looping, mute, and a video URL field — but no trim, crop, or edit options. This is by design: FlutterFlowVideoPlayer wraps the video_player Flutter package for playback only. Accept this and plan your editing pipeline outside the visual builder. For short clips under 30 seconds on modern phones, the video_editor package can do client-side trim and crop. For anything longer or more complex — resolution changes, watermarks, merging clips — route the work to a server.

Expected result: You understand the boundary between what FlutterFlow provides natively and what requires custom code or server processing.

2

Add the video_editor package as a custom dependency

In FlutterFlow, go to Settings (gear icon in the left sidebar) then click on 'Custom Code' and select 'Dependencies'. Click 'Add Dependency', enter video_editor and set the version to ^3.0.0. Also add image_picker (for selecting videos from the gallery) and path_provider (for temporary file access). Click Save after adding each dependency. These packages allow your custom widget to display a timeline scrubber, set trim points, and export the trimmed segment as a new video file — all on the user's device without any server call.

Expected result: Three new entries appear in your Dependencies list: video_editor, image_picker, and path_provider.

3

Create a Custom Widget for the video trim UI

Navigate to Custom Code in the left sidebar and click the '+' button next to 'Custom Widgets'. Name it VideoTrimmerWidget. In the code editor, write the Dart widget class that initializes a VideoEditorController with the selected file path, renders the CropGridViewer and TrimSlider from the video_editor package, and exposes an Export button that calls controller.exportVideo(). Pass the output file path back to FlutterFlow via a widget callback parameter named onExportComplete of type String. The widget should accept one parameter: videoPath of type String. Keep the trim UI minimal — a preview player, a horizontal trim slider, and an Export button is enough for most apps.

video_trimmer_widget.dart
1import 'package:flutter/material.dart';
2import 'package:video_editor/video_editor.dart';
3import 'dart:io';
4
5class VideoTrimmerWidget extends StatefulWidget {
6 final String videoPath;
7 final void Function(String outputPath) onExportComplete;
8
9 const VideoTrimmerWidget({
10 Key? key,
11 required this.videoPath,
12 required this.onExportComplete,
13 }) : super(key: key);
14
15 @override
16 State<VideoTrimmerWidget> createState() => _VideoTrimmerWidgetState();
17}
18
19class _VideoTrimmerWidgetState extends State<VideoTrimmerWidget> {
20 late VideoEditorController _controller;
21 bool _exporting = false;
22
23 @override
24 void initState() {
25 super.initState();
26 _controller = VideoEditorController.file(
27 File(widget.videoPath),
28 minDuration: const Duration(seconds: 1),
29 maxDuration: const Duration(seconds: 30),
30 );
31 _controller.initialize().then((_) => setState(() {}));
32 }
33
34 @override
35 void dispose() {
36 _controller.dispose();
37 super.dispose();
38 }
39
40 Future<void> _export() async {
41 setState(() => _exporting = true);
42 await _controller.exportVideo(
43 onCompleted: (file) {
44 setState(() => _exporting = false);
45 widget.onExportComplete(file.path);
46 },
47 );
48 }
49
50 @override
51 Widget build(BuildContext context) {
52 if (!_controller.initialized) {
53 return const Center(child: CircularProgressIndicator());
54 }
55 return Column(
56 children: [
57 Expanded(
58 child: CropGridViewer.preview(controller: _controller),
59 ),
60 TrimSlider(
61 controller: _controller,
62 height: 60,
63 ),
64 ElevatedButton(
65 onPressed: _exporting ? null : _export,
66 child: _exporting
67 ? const CircularProgressIndicator()
68 : const Text('Export Trimmed Video'),
69 ),
70 ],
71 );
72 }
73}

Expected result: The custom widget appears in the FlutterFlow widget panel under Custom Widgets and can be added to any page.

4

Build the video selection flow on your FlutterFlow page

On your target page, add a Button widget labeled 'Select Video'. Create a new Custom Action named pickVideoAction. Inside the action, use image_picker's ImagePicker().pickVideo(source: ImageSource.gallery) to let the user choose a video, then store the returned file path in a Page State variable named selectedVideoPath (type String). Add a Conditional Widget below the button: when selectedVideoPath is not empty, show your VideoTrimmerWidget and pass selectedVideoPath as the videoPath parameter. Wire the onExportComplete callback to update a second Page State variable named exportedVideoPath. Add a second Conditional Widget that shows an upload button when exportedVideoPath is not empty.

Expected result: Tapping 'Select Video' opens the device gallery, selecting a video displays the trim UI, and exporting saves the trimmed file path in app state.

5

Upload the trimmed video to Firebase Storage

Create a Custom Action named uploadTrimmedVideo that accepts the exportedVideoPath String. Inside the action, use FirebaseStorage.instance.ref('trimmed_videos/${DateTime.now().millisecondsSinceEpoch}.mp4').putFile(File(exportedVideoPath)) to upload the trimmed file. Listen to the task.snapshotEvents stream to update a Page State variable named uploadProgress (double, 0.0-1.0) so you can display a LinearProgressIndicator. After upload completes, call task.snapshot.ref.getDownloadURL() and store the result in a Page State variable named uploadedVideoUrl. For files larger than 50 MB, consider chunked uploads by breaking the file with dart:io and reassembling with a Cloud Function.

Expected result: The trimmed video uploads to Firebase Storage and the download URL is stored in the page state, ready to be saved to Firestore.

6

Set up a Cloud Function with FFmpeg for server-side processing

For videos longer than 30 seconds or operations like resolution change, adding watermarks, or merging clips, create a Firebase Cloud Function that accepts a Firebase Storage path, processes it with FFmpeg on Cloud Run, and writes the result back to Storage. In the Firebase console, go to Functions and deploy the Node.js function below. The function reads the original file from Storage, runs FFmpeg via fluent-ffmpeg pointing at a Cloud Run container, and uploads the output. Trigger this function from FlutterFlow using a Custom Action that calls your Cloud Function's HTTPS endpoint with the storage path as the request body.

functions/index.js
1// functions/index.js (Node.js 18)
2const functions = require('firebase-functions');
3const admin = require('firebase-admin');
4const { Storage } = require('@google-cloud/storage');
5const os = require('os');
6const path = require('path');
7const fs = require('fs');
8const ffmpeg = require('fluent-ffmpeg');
9
10admin.initializeApp();
11const storage = new Storage();
12
13exports.processVideo = functions
14 .runWith({ timeoutSeconds: 300, memory: '2GB' })
15 .https.onCall(async (data, context) => {
16 if (!context.auth) {
17 throw new functions.https.HttpsError('unauthenticated', 'Login required');
18 }
19 const { storagePath, startTime, endTime } = data;
20 const bucket = admin.storage().bucket();
21 const inputTmp = path.join(os.tmpdir(), 'input.mp4');
22 const outputTmp = path.join(os.tmpdir(), 'output.mp4');
23
24 await bucket.file(storagePath).download({ destination: inputTmp });
25
26 await new Promise((resolve, reject) => {
27 ffmpeg(inputTmp)
28 .setStartTime(startTime)
29 .setDuration(endTime - startTime)
30 .output(outputTmp)
31 .on('end', resolve)
32 .on('error', reject)
33 .run();
34 });
35
36 const outputPath = `processed/${Date.now()}.mp4`;
37 await bucket.upload(outputTmp, { destination: outputPath });
38
39 fs.unlinkSync(inputTmp);
40 fs.unlinkSync(outputTmp);
41
42 const [url] = await bucket.file(outputPath).getSignedUrl({
43 action: 'read',
44 expires: Date.now() + 7 * 24 * 60 * 60 * 1000,
45 });
46 return { downloadUrl: url, storagePath: outputPath };
47 });

Expected result: The Cloud Function appears in the Firebase console under Functions and returns a signed download URL when called with a valid storage path and time range.

Complete working example

pick_and_upload_video_action.dart
1// Custom Action: pickAndProcessVideo
2// Dependencies: image_picker, firebase_storage, cloud_functions
3import 'package:image_picker/image_picker.dart';
4import 'package:firebase_storage/firebase_storage.dart';
5import 'package:cloud_functions/cloud_functions.dart';
6import 'dart:io';
7
8Future<Map<String, dynamic>> pickAndProcessVideo() async {
9 // Step 1: Pick video from gallery
10 final picker = ImagePicker();
11 final XFile? video = await picker.pickVideo(
12 source: ImageSource.gallery,
13 maxDuration: const Duration(seconds: 120),
14 );
15
16 if (video == null) {
17 return {'success': false, 'error': 'No video selected'};
18 }
19
20 final file = File(video.path);
21 final fileSizeBytes = await file.length();
22
23 // Step 2: Enforce 100 MB limit before upload
24 if (fileSizeBytes > 100 * 1024 * 1024) {
25 return {'success': false, 'error': 'File exceeds 100 MB limit'};
26 }
27
28 // Step 3: Upload original to Firebase Storage
29 final storagePath = 'raw_videos/${DateTime.now().millisecondsSinceEpoch}.mp4';
30 final storageRef = FirebaseStorage.instance.ref(storagePath);
31
32 final uploadTask = storageRef.putFile(file);
33 await uploadTask;
34
35 // Step 4: Call Cloud Function for server-side trim
36 // (pass start/end seconds from trim UI as parameters)
37 final callable = FirebaseFunctions.instance.httpsCallable('processVideo');
38 final result = await callable.call({
39 'storagePath': storagePath,
40 'startTime': 0, // Replace with trim start from UI
41 'endTime': 30, // Replace with trim end from UI
42 });
43
44 final data = result.data as Map<String, dynamic>;
45
46 // Step 5: Clean up local file
47 await file.delete();
48
49 return {
50 'success': true,
51 'downloadUrl': data['downloadUrl'],
52 'storagePath': data['storagePath'],
53 };
54}

Common mistakes

Why it's a problem: Running FFmpeg video processing on the user's device for files over 30 seconds

How to avoid: Set maxDuration: Duration(seconds: 30) in VideoEditorController to gate client-side processing, and route longer videos to your Cloud Function pipeline.

Why it's a problem: Expecting FlutterFlow's built-in VideoPlayer to include editing controls

How to avoid: Build a Custom Widget using the video_editor package for client-side trim/crop, or use a Cloud Function with FFmpeg for server-side operations.

Why it's a problem: Not deleting temporary video files after processing

How to avoid: Call File(path).delete() after every successful upload, and also in the error handler to clean up failed attempts.

Why it's a problem: Calling the Cloud Function without Firebase Authentication

How to avoid: Ensure the user is authenticated before triggering the function. Add an auth check at the start of your Custom Action and redirect to login if needed.

Why it's a problem: Storing the raw upload path in Firestore before the Cloud Function finishes

How to avoid: Only write the final URL to Firestore after the Cloud Function returns successfully. Use the processedStoragePath from the function's response.

Best practices

  • Always cap client-side video editing to 30 seconds maximum to prevent memory crashes on low-end devices.
  • Display a LinearProgressIndicator wired to the Firebase Storage upload task's bytesTransferred / totalBytes to give users feedback during upload.
  • Use Firebase App Check to protect your Cloud Function from unauthorized calls that could incur unexpected processing costs.
  • Store both the raw and processed video paths in Firestore so you can regenerate processed versions without re-uploading the original.
  • Set Firebase Storage security rules to allow writes only to users' own uid-namespaced paths: allow write: if request.auth.uid == userId.
  • Add a cleanup Cloud Function scheduled daily to delete temporary raw videos older than 24 hours from the raw_videos/ bucket path.
  • Test your video trim widget on both Android and iOS physical devices — emulators do not accurately represent video codec performance.

Still stuck?

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

ChatGPT Prompt

I am building a FlutterFlow app and need to add video trimming. FlutterFlow has no built-in video editor. Explain how to create a custom Dart widget using the video_editor package that lets users trim a clip, and how to upload the result to Firebase Storage.

FlutterFlow Prompt

Add a Custom Widget to my FlutterFlow project that uses the video_editor package to display a trim slider for a video file at [videoPath]. The widget should call onExportComplete with the output file path when the user taps Export.

Frequently asked questions

Does FlutterFlow have a built-in video editor widget?

No. FlutterFlow only includes a VideoPlayer widget for playback. Any editing — trimming, cropping, adding text overlays — requires a Custom Widget using packages like video_editor, or a server-side Cloud Function with FFmpeg.

Which Flutter package should I use for client-side video trimming?

The video_editor package (pub.dev) is the most complete option. It provides a TrimSlider, CropGridViewer, and VideoEditorController. Limit on-device processing to clips under 30 seconds to avoid memory issues.

How do I run FFmpeg in a Firebase Cloud Function?

Install fluent-ffmpeg and @ffmpeg-installer/ffmpeg as npm dependencies in your functions directory. In your function, download the file from Storage to /tmp, run the FFmpeg command, then upload the output back to Storage. Increase memory to 2 GB in runWith options.

Will client-side video editing work on FlutterFlow's Free plan?

The video_editor package requires code export, which is a Pro plan feature. If you are on the Free plan you cannot add custom Dart packages — you would need to upgrade to at least Pro.

How long does the Cloud Function FFmpeg approach take?

A 60-second 1080p trim typically completes in 20-40 seconds on a 2 GB Cloud Function instance. Set the function timeout to at least 300 seconds and show a loading state in the app while waiting.

Can I add watermarks or text overlays to videos in FlutterFlow?

Not with client-side packages easily. Text and image overlays require FFmpeg's drawtext and overlay filters, which are best handled in a Cloud Function. Send the video storage path plus overlay parameters to the function and return the processed URL.

How should I handle the case where the Cloud Function times out?

Wrap your Custom Action call in a try-catch and check for the deadline-exceeded error code. Show the user a message explaining the video was too large or complex, and offer to retry with a shorter segment.

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.