FlutterFlow offers four debugging approaches: (1) Run Mode with Flutter DevTools shows the full console log, (2) Custom Functions can return intermediate values for inspection in bindings, (3) debugPrint() in Custom Actions writes to the DevTools console without flooding logs, and (4) Test Mode runs your app against real Firestore data so you can observe live behavior. Use all four in combination to pinpoint most bugs.
Four Ways to Find and Fix Bugs in FlutterFlow
Debugging in FlutterFlow is different from debugging in a full IDE. You cannot set breakpoints in the visual canvas, and the canvas preview does not execute real Dart code. Most bugs fall into one of three categories: data binding issues (the wrong value is flowing into a widget), action flow logic issues (an action is not triggering or is triggering incorrectly), and custom code errors (a Custom Action or Function has a runtime error). Each category has a best debugging tool: DevTools console for custom code errors, binding inspection for data issues, and Test Mode for action flow logic. This guide walks through all four methods so you can confidently diagnose any FlutterFlow issue.
Prerequisites
- A FlutterFlow project with at least one Custom Action or complex widget binding
- A physical device or emulator connected for Run Mode (or use FlutterFlow's hosted run option)
- Basic understanding of FlutterFlow Action Flows and data bindings
- Firebase or Supabase backend connected if debugging data-related issues
Step-by-step guide
Open Run Mode and connect Flutter DevTools
Open Run Mode and connect Flutter DevTools
Run Mode is the most powerful debugging tool available in FlutterFlow. Click the 'Run' button in the top-right toolbar (the play icon, or the button that shows your connected device name). FlutterFlow compiles your full project and runs it either on a connected physical device, an emulator, or FlutterFlow's hosted preview environment. Once the app is running, a 'DevTools' button appears in the FlutterFlow toolbar. Click it to open Flutter DevTools in a browser tab. Navigate to the 'Logging' tab in DevTools — this is your console. All debugPrint() calls, Flutter framework warnings, and uncaught exceptions appear here in real time as you interact with the app. You can filter by log level and search for specific strings.
Expected result: The Flutter DevTools Logging tab is open and shows real-time log output as you interact with the running app.
Add debugPrint statements to Custom Actions
Add debugPrint statements to Custom Actions
When a Custom Action produces unexpected results, add debugPrint() calls at key points in the function to trace execution. Open the Custom Code panel (</> in the left sidebar), find your Custom Action, and add debugPrint statements that output variable values and execution checkpoints. For example, add 'debugPrint("fetchUserData: starting for uid=$uid");' at the beginning and 'debugPrint("fetchUserData: result = $result");' before the return statement. Tap Compile, then run the app in Run Mode and trigger the action. Check the DevTools Logging tab for your output. Unlike print(), debugPrint() is rate-limited (prevents log flooding with fast loops) and is automatically stripped in release builds.
1Future<String> fetchUserProfile(String uid) async {2 debugPrint('fetchUserProfile: called with uid=$uid');34 if (uid.isEmpty) {5 debugPrint('fetchUserProfile: ERROR — uid is empty, returning early.');6 return '';7 }89 try {10 final doc = await FirebaseFirestore.instance11 .collection('users')12 .doc(uid)13 .get();1415 debugPrint('fetchUserProfile: doc exists = ${doc.exists}');16 final displayName = doc.data()?['displayName'] as String? ?? '';17 debugPrint('fetchUserProfile: displayName = $displayName');18 return displayName;19 } catch (e) {20 debugPrint('fetchUserProfile: EXCEPTION $e');21 return '';22 }23}Expected result: debugPrint messages appear in the DevTools Logging tab when the Custom Action runs, showing variable values and execution path.
Use Custom Functions to inspect intermediate values
Use Custom Functions to inspect intermediate values
For data binding bugs — where a widget shows the wrong value — Custom Functions are a powerful inspection tool. Create a temporary Custom Function named 'debugInspect' that takes a dynamic value and returns it as a formatted String. In the widget whose data looks wrong, add a Text widget next to it and bind its value to 'Set from Variable → Custom Functions → debugInspect', passing in the same data source your problematic widget uses. This lets you see the raw value being passed to the widget at render time without needing the DevTools console. Once you have identified the issue, delete the debug Text widget. This technique is especially useful for diagnosing null values, type mismatches, and Firestore field name typos.
1// Temporary debug Custom Function — delete after debugging2// Return type: String3// Parameter: value (dynamic)4String debugInspect(dynamic value) {5 if (value == null) return 'NULL';6 return '${value.runtimeType}: $value';7}Expected result: The debug Text widget shows the raw type and value of the data source, revealing null values or unexpected types.
Use Test Mode to debug Action Flows against real data
Use Test Mode to debug Action Flows against real data
Test Mode runs your app in the FlutterFlow browser with real Firebase/Supabase data, without needing a connected device. Click the 'Test' button (different from 'Run') in the FlutterFlow toolbar. The app opens in a browser tab and you can interact with it as a real user would. Test Mode is ideal for debugging Action Flow logic: trigger the action, watch what happens, and check Firestore directly in the Firebase Console to see if documents were created, updated, or queried as expected. For actions that should write data, open Firestore in a side-by-side browser tab and refresh after triggering the action. For actions that should read data, check the widget output against the Firestore document values.
Expected result: You can trigger Action Flows in the browser and observe real Firestore data changes in the Firebase Console simultaneously.
Check Action Flow conditions and null safety
Check Action Flow conditions and null safety
Many FlutterFlow bugs are caused by Action Flow conditionals that evaluate incorrectly due to null values or type mismatches. In the Action Flow editor, review each conditional's expression carefully. If a condition checks 'currentUser.biometricEnabled is true', verify that the Firestore field is actually a boolean type and not a string 'true'. FlutterFlow's condition editor shows the data type of each operand — look for orange or red type indicators that signal a type mismatch. For conditionals that unexpectedly take the FALSE branch, add a Snackbar action in the FALSE branch that shows the evaluated value using a binding, so you can see what value the condition actually received at runtime.
Expected result: Action Flow conditionals are verified with correct data types and null-safe expressions, and any previously silent false-branch bugs are identified.
Complete working example
1// ─── Custom Action: debugAction ──────────────────────────────────────────────2// Use this temporary action in Action Flows to log the value of any variable.3// Parameters: label (String), value (String)4// Delete this action before publishing to production.56Future debugAction(7 String label,8 String value,9) async {10 debugPrint('DEBUG [$label]: $value');11}1213// ─── Custom Function: debugInspect ───────────────────────────────────────────14// Bind this to a Text widget's value to see the raw type and content.15// Parameters: value (dynamic)16// Return type: String17// Use in: Set from Variable → Custom Functions → debugInspect1819String debugInspect(dynamic value) {20 if (value == null) return '[NULL]';21 if (value is String && value.isEmpty) return '[EMPTY STRING]';22 if (value is List) return '[List length=${value.length}]: $value';23 if (value is Map) return '[Map keys=${value.keys.toList()}]';24 return '[${value.runtimeType}]: $value';25}2627// ─── Custom Action: assertNotNull ────────────────────────────────────────────28// Asserts a value is not null and logs a warning if it is.29// Parameters: label (String), value (String)30// Returns: Boolean (true = value is not null/empty, false = it is)3132Future<bool> assertNotNull(33 String label,34 String value,35) async {36 if (value.isEmpty) {37 debugPrint('ASSERTION FAILED [$label]: expected non-empty String, got empty.');38 return false;39 }40 debugPrint('ASSERTION OK [$label]: value = "$value"');41 return true;42}4344// ─── Usage notes ─────────────────────────────────────────────────────────────45// 1. Add debugAction to any Action Flow step to log a variable's value.46// 2. Add a Text widget with debugInspect binding to inspect live data.47// 3. Use assertNotNull before actions that require non-null IDs or keys.48// 4. Remove ALL debug helpers before deploying to production.49// 5. Open Flutter DevTools (Run Mode → DevTools button) to view debugPrint output.Common mistakes when debugging Your FlutterFlow App's Code
Why it's a problem: Using print() instead of debugPrint() inside Custom Actions
How to avoid: Always use debugPrint() for any logging inside Custom Actions and Custom Widgets. debugPrint() is rate-limited, appears in Flutter DevTools, and is excluded from release builds.
Why it's a problem: Trying to debug Custom Action logic in the FlutterFlow canvas preview
How to avoid: Use Test Mode for browser-based testing with real data, or Run Mode with a connected device for full native debugging including Flutter DevTools access.
Why it's a problem: Forgetting to call Compile after adding debugPrint statements
How to avoid: After every edit to a Custom Action, always tap the Compile button and confirm a green success message before switching to Run Mode or Test Mode.
Why it's a problem: Checking widget output in the canvas instead of Test Mode for data binding bugs
How to avoid: Always verify data binding behavior in Test Mode or Run Mode, not the canvas. Use the debugInspect Custom Function technique to see raw values in the running app.
Best practices
- Establish a naming convention for debug Custom Actions (e.g., prefix with 'debug_') so they are easy to find and remove before deployment.
- Create a dedicated Firebase test user and Firestore test dataset for debugging sessions — never debug on production data.
- Use the FlutterFlow Action Flow validator (three-dot menu → Validate) before running to catch obvious misconfigured actions without needing to deploy.
- Add error handling to every Custom Action with a try/catch block and a debugPrint in the catch — silent failures are the hardest bugs to find.
- When a widget shows the wrong data, work backwards from the widget: check the binding, then the query, then the Firestore document itself in the Firebase Console.
- Keep Test Mode and the Firebase Console open side-by-side when debugging write operations — you can see Firestore updates in real time as you trigger actions.
- Remove all debug Custom Actions, debug Text widgets, and temporary debugPrint calls before publishing or exporting code to production.
- Document known edge cases in your Custom Action comments as you discover them during debugging — this saves time when the same edge case reappears.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm debugging a FlutterFlow app and my Custom Action is not behaving as expected. Here is the action code: [paste code]. The action is called when [describe trigger] and it should [describe expected behavior] but instead it [describe actual behavior]. What debugPrint statements should I add to trace the issue, and what are the most likely causes?
I have a FlutterFlow Custom Action that [describe behavior] and it is silently failing — no error, but the expected result doesn't happen. The action parameters are [list parameters]. Write a debugged version of this function body with defensive null checks, debugPrint statements at each key decision point, and a try/catch that logs the full error. I will paste this into the FlutterFlow Custom Code editor.
Frequently asked questions
Can I set breakpoints in FlutterFlow like in a regular IDE?
Not directly in the FlutterFlow web editor. FlutterFlow does not support interactive breakpoints in its browser-based Custom Code editor. However, if you export your code (Pro plan) and open it in VS Code or Android Studio, you can set full breakpoints and use the IDE's debugger. Within FlutterFlow, debugPrint statements and the DevTools Logging tab are the primary alternatives.
What is the difference between Test Mode and Run Mode in FlutterFlow?
Test Mode runs the app in your browser using FlutterFlow's hosted preview — it is quick to launch and uses real Firebase data, but some native features (like biometrics or camera) do not work. Run Mode compiles the full Flutter app and runs it on a connected physical device or emulator — it supports all native features and gives you access to full Flutter DevTools. Use Test Mode for rapid data and UI debugging, Run Mode for native feature and Custom Action debugging.
How do I view Flutter DevTools when running in FlutterFlow?
Start Run Mode by clicking the Run button in the FlutterFlow toolbar. Once the app launches on your connected device or emulator, a 'DevTools' button appears in the FlutterFlow interface. Click it to open Flutter DevTools in a new browser tab. Navigate to the Logging tab to see your console output, or the Widget Inspector tab to examine the live widget tree.
My Custom Action runs but produces no output in DevTools. Why?
There are three common causes: (1) You added debugPrint calls but forgot to tap Compile — the old compiled version without the logs is still running. (2) The action is not being triggered at all — add a debugPrint at the very first line to confirm the action executes. (3) You are looking at the canvas preview, not Run Mode — canvas does not execute code. Always use Run Mode and confirm you compiled after your last edit.
How can I debug a Firestore query that returns no results?
Open the Firestore query in your Backend Query settings on the widget. Check three things: (1) The collection name matches exactly (case-sensitive). (2) Any filter conditions use the correct field name and value type. (3) Your Firestore Security Rules allow the read operation for the current user. Open the Firebase Console → Firestore → Rules → Rules Playground to test your rules against the specific query path and user.
Does RapidDev offer help debugging FlutterFlow apps?
Yes. For complex debugging scenarios — particularly Custom Action errors, Firebase Security Rules issues, or production crashes — RapidDev's engineering team can review your project and pinpoint the root cause. Reach out via rapiddev.app for a consultation.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation