You have a FlutterFlow project, a Custom Action that almost works, and a growing suspicion that the builder is the thing slowing you down. Before you decide anything, open the Custom Code panel and count how many files you own there. That number tells you more than any feature grid.
FlutterFlow custom code has limits, but they are not where most people expect them. The builder happily lets you paste Dart into a Custom Action all afternoon. What it does not do is read that Dart the way the Dart analyzer reads it.
I maintain eight templates and more than 40 custom widgets on the official FlutterFlow Marketplace, so I spend real time on the seam where the visual builder stops and plain Flutter starts. This post covers what the limits are, which plan gates what, the four signals that mean you should leave, and the export detail that quietly overwrites your native config.
What Are FlutterFlow's Custom Code Limits?
FlutterFlow's practical limit is its Dart parser, not how much code you write: the builder scans your files to expose types in the visual editor, and shapes it cannot model are ignored.
There are several kinds of custom code, and they are not interchangeable. This table is the part I wish someone had shown me first.
| Type | Imports | Returns | What it is for |
|---|---|---|---|
| Custom Function | Only the default set of packages and files | A value, synchronously | Transforms, formatters, validators |
| Custom Action | pub.dev packages allowed | Always a Future | Async work, SDK calls, API glue |
| Custom Widget | pub.dev packages allowed | A widget; talks back through callback actions | UI the widget palette cannot express |
Per FlutterFlow's common code examples, Custom Functions cannot import new files or packages beyond the default dedicated imports, while Custom Widgets and Custom Actions can. The Custom Functions page adds that you cannot use FFAppState inside a function; you pass the value in as a parameter. So if your helper needs a package or app state, it is not a function. It is an action.
Code Files, the place for custom classes and enums, have stricter rules. FlutterFlow's Code File documentation lists three limitations: classes with generic types such as class ApiResponse<T> {} are not supported, methods or fields with function types are ignored, and Dart extensions are not supported yet. That rules out a generic result wrapper, an onTap callback field on a model, and extension StringX on String.
Those limits apply to what the parser has to model. Inside the body of a Custom Action you still write ordinary Dart. What you cannot do is hand those shapes back to the builder as types it understands.
What about custom widget size and previews?
Custom Widgets have one more rule that trips people up. The Custom Widgets documentation says width and height are mandatory, and that without them the widget will not render correctly. It also says the preview appears only once the widget compiles, so a broken import shows up as a missing preview rather than a friendly error.
Which FlutterFlow Plan Do You Need for Custom Code?
The Basic plan at $39 per month covers most custom code needs: the pricing page's comparison table marks code download, Custom Classes, third-party package imports and the CLI as available from Basic upward.
The FlutterFlow pricing page, read on 30 September 2026, is easy to misread. Custom Classes and CLI access can look like Business-tier features at $150 per month, but the comparison table does not say that. Here is what it says for the rows that matter here.
| Capability | Free | Basic ($39) | Growth ($80, first seat) | Business ($150, first seat) |
|---|---|---|---|---|
| Code Extensibility | Yes | Yes | Yes | Yes |
| Code download and APK download | No | Yes | Yes | Yes |
| Custom Classes | No | Yes | Yes | Yes |
| Third-party package imports | No | Yes | Yes | Yes |
| CLI | No | Yes | Yes | Yes |
| Push to GitHub, VS Code extension | No | No | Yes | Yes |
Business adds seats, more branches and features such as Figma frame import, none of which change what your Dart can do. One inconsistency is worth knowing: the Business plan card lists "CLI Access" as a highlight, while the comparison table marks the CLI from Basic. I trust the table because it is the itemised one, but check your own plan before you build a workflow around it.
That changes the question. A solo builder who needs custom classes or packages does not face a jump to $150. The pricing gate moves at Growth, and only for Git and editor tooling. Whether the export is worth it is a technical decision, and the price does not settle it for you. For a wider comparison of the two paths, see FlutterFlow vs Flutter for production apps.
For a team, the practical reading is simple. Growth is where collaboration tooling arrives, so a two-person team that wants pull requests and an editor extension pays for that step, not for the ability to write custom code. Price the plan against the workflow you need, and treat the export as a separate line item you can take at any tier that includes code download.
When Should You Drop to Pure Flutter?
Drop to pure Flutter when two of these are true together: graph-shaped state, models that need generics, a native SDK in the critical path, or tests that must run in CI.
This is my recommendation, not a FlutterFlow rule. Any one signal is usually solvable inside the builder. Two compounding is where workarounds start costing more than the move. Here is each signal in concrete form.
- Your state is graph-shaped. App State and page state cover screens that read and write a handful of values. Once one change has to invalidate four unrelated views, you want Riverpod or Bloc holding that graph instead of a pile of Custom Actions poking at
FFAppState. I compare those libraries in Flutter state management in 2026. - Your models need generics. A
Result<T>or a paginatedPage<T>is the normal way to model an API layer in Dart. The Code File parser does not support generics. - A native SDK sits in the critical path. Custom Podfile edits, entitlements or a hand-written platform channel mean you are working against generated native files, and that gets harder as the project grows.
- Tests are a requirement. You can write widget and integration tests against generated code. They are painful to keep green when a re-export rewrites the file underneath them.
Notice what is missing from that list: line count. Nothing on it says "more than a hundred Custom Actions". A project can carry a lot of custom code and stay healthy if each piece is a plain function or widget with clear inputs. The trouble starts when the pieces depend on each other through shared state that the builder cannot see.
Have you opened an exported project "just to check something" and still been in it three days later? That is signal number one arriving early.
How Do You Move Off FlutterFlow Without a Rewrite?
Move in stages: push logic into files you own while still in the builder, export once, then stop re-exporting so that only one side ever owns each file.
The migration that fails is the one where the builder and your editor both believe they own the same file. The ordered plan below avoids that.
- Move every non-trivial behaviour into Custom Actions and Custom Widgets while you are still in the builder. It feels like doing the work twice, but the exported project then contains your logic in files you named.
- Move business rules to the backend, whether that is Cloud Functions or an API you already run. Server-side logic survives any client rewrite.
- Export with the CLI and commit the result untouched. That gives you a diffable baseline.
- Make one small change in the builder, re-export and read the diff. That diff is a map of which files FlutterFlow believes it owns.
- Cut over screen by screen, replacing generated pages with hand-written ones behind your existing router. Nothing forces a big-bang switch.
Where does .flutterflowignore go?
It goes in the parent of your exported project folder, not inside it. FlutterFlow's export documentation gives the example: if the project is at /Users/yourname/projects/my_flutterflow_app/, the file lives at /Users/yourname/projects/.flutterflowignore, and each entry carries the project folder as a prefix, such as my_flutterflow_app/ios/Runner/Info.plist.
The same page says the file lists paths using globbing syntax and prevents those files from being overwritten during code export. This is my inference from that layout: an entry written the way you would write a .gitignore line, without the project folder prefix, is unlikely to match anything. Test it by exporting once and reading the diff, as in step four above.
Which pages help after the export?
If you are still choosing a direction, the alternatives to FlutterFlow worth considering are a better starting point than another feature grid. The Flutter developer tools I keep installed describe the post-export workflow, and what Flutter is covers the vocabulary if it is new.
Where would I draw the line?
If you are pre-launch and the app is mostly screens over a Firebase collection, stay in the builder. If you have paying users and a growing pile of Custom Actions, export, commit and treat FlutterFlow as a design tool that happens to emit Dart. That is a recommendation from my own work, and the number of Custom Actions that flips it will differ per team.
Open your project and count your Custom Actions and Code Files right now. Which of the four signals does your count point to?



