A FlutterFlow custom widget has to satisfy a contract before it has to look like anything.
That contract is the part nobody writes about. Search for flutterflow custom widget parameters and you will find plenty of videos pointing at the Add Parameter button, and almost nothing about what the platform does with the thing you typed into it. Across eight FlutterFlow Marketplace templates and more than forty custom widgets, the ones that cost me days were never the ones with hard Dart inside. They were the ones that guessed wrong about the boundary.
There are three boundaries: what comes in, what the widget is allowed to remember, and how it speaks back. A complicated widget that respects all three behaves. A trivial widget that breaks one goes quietly dead in Run Mode, with no error and no stack trace. If the underlying vocabulary is new, what a Flutter developer actually does day to day is the shorter prerequisite.
What parameter types can a FlutterFlow custom widget take?

FlutterFlow passes each parameter into your widget as a constructor argument, and every custom widget also takes a mandatory width and height it cannot refuse.
Open the code editor on a new custom widget and read the stub the platform writes for you before you write anything. There is a StatefulWidget, a State, a constructor, and two fields you did not ask for. Width and height arrive nullable in every widget I have shipped, which is the platform telling you something it does not say in words: your widget may be handed no size at all, and it still has to render.
The documentation is unusually direct about this. FlutterFlow's custom widget documentation states that "For custom widgets, it is mandatory to specify both width and height," and adds that "Without setting these dimensions, the custom widget will not render correctly within your application." Mandatory on the builder side. Nullable on the code side. You handle both.
Everything else you declare in the parameter panel reaches your State subclass as widget.parameterName, and that indirection is where a surprising number of bugs start, because widget.parameterName is a live read of the current configuration while anything you copied out of it is a snapshot.
Two parameter kinds behave differently from values, and both are worth knowing before you design around them:
- Widget Builder. FlutterFlow's widget builder documentation describes these as parameters that "allow component authors to substitute dynamic content within the widget tree of the component." You get a slot rather than a value. This is how you write a list widget that does not dictate what a row looks like, which matters more than it sounds, because a widget that hardcodes its own row design is a widget exactly one project can use.
- Action. A callback. The page hands your widget a piece of behaviour and your Dart decides when to fire it.
And if the thing you want to pass is none of those? A controller, an open stream, a Dart function you wrote in a custom action. The panel has nowhere to put it, and the honest answer is that the parameter list is the API. Design the widget so its public surface is expressible in that panel, or accept that part of the wiring has to live in app state instead. The same discipline applies one layer down, where some pub packages simply will not survive the cloud build no matter how clean your parameters are.
Why does a FlutterFlow custom widget stop updating?

Usually because the widget copied the parameter into its own state inside initState, which runs exactly once, so later parameter changes never reach the build method.
This is the single most common FlutterFlow custom widget bug, and it is not a FlutterFlow bug. It is ordinary Flutter, met by people who reasonably assumed the platform was handling lifecycle for them. initState fires when the element is created. It does not fire again when the parent rebuilds with a different value. Your widget keeps rendering the first thing it was ever told.
The fix is didUpdateWidget, which Flutter calls on every rebuild with the previous widget instance as its argument. Compare oldWidget.value to widget.value, and re-run whatever you did in initState only when they differ. Or skip the local copy entirely and read widget.value straight from build, which is what I do in almost everything now. A countdown timer needs the local copy because it owns a ticker. A QR generator does not, and giving it one buys you a bug for nothing.
Then there is the platform-level version of the same symptom, and it has a stranger history than the code-level one. FlutterFlow issue #2703, opened in April 2024, reported that a custom widget would not redraw after "update page state" plus a rebuild action. FlutterFlow closed it as "not a bug", with the label reading that the behavior is correct and expected. Twelve days later issue #2788 described the same thing from a cloned documentation example, and that one carries the label "status: confirmed".
Same symptom, two dispositions, from the same tracker. What I take from that is not that FlutterFlow is careless. It is that "my custom widget does not update" is a description of a symptom with at least two unrelated causes underneath it, and the platform's own triage has trouble telling them apart. So should you, before you go looking for a workaround. Check didUpdateWidget first, because that one is yours to fix and takes four lines.
Actually, let me back up on the word "unrelated". They share a root: FlutterFlow gives you a visual model of state on top of Flutter's element-tree model of state, and the two do not agree about when a widget is the same widget. Everything in this section is a consequence of that disagreement. A widget reading app state directly through FFAppState() sidesteps it, at the cost of a widget that is no longer portable to another project. Worth it sometimes. Rarely for anything you intend to ship to somebody else, and a genuine trap if your data is arriving through a FlutterFlow and Supabase stack where the same value exists in three places already.
How does a custom widget talk back to the page?

Through a parameter of type Action: the widget executes the callback from inside Dart, and the page reads what it passed under Callback Parameters.
This is the direction people forget. Parameters flow down easily enough; getting a value back up feels like it should require writing to app state, and it does not. FlutterFlow's callbacks documentation is explicit that "To create a component that will execute a callback, you must create a component with a parameter with the Action type," and that "You can access the value passed to the callback by navigating to the Set Variable menu > Callback Parameters."
The wiring, in order:
- Declare a parameter of type Action on the widget, and give it the arguments the page will care about. A circular slider hands back a double. A before/after comparison hands back nothing at all, because the page only needs to know a drag happened.
- In your Dart, call it where the event actually occurs. Not in
build, and not on a timer. - On the page, bind an action flow to that callback the way you would to a button tap.
- Read the value through Set Variable, under Callback Parameters, not through a page state variable you set from inside the widget.
Step four is the one that separates a widget somebody else can use from a widget that only works in the project it was born in. A widget that writes directly to FFAppState has made a decision on behalf of every future user about where its output lives. A widget that fires a callback has made no decision at all, which is the correct amount.
The widget does not own the screen
You know this feeling. A widget looks perfect in isolation and collapses the moment it sits inside a Column.
Issue #2215, from January 2024, asked for custom widgets that size themselves to their content. FlutterFlow closed it as expected behaviour. So the rule stands: your widget receives a box, and building one that assumes it can expand forever is building one that throws.
Drop-in widget or full-app template?
These are different products, and the case study I keep for the marketplace work treats them that way on purpose. A drop-in widget lands in a project that already exists and has to survive decisions it was not present for. A full-app template is the decisions. Treating the second like a large version of the first is how you ship a template whose screens cannot be pulled apart.
| Drop-in widget | Full-app template | |
|---|---|---|
| Buyer already has | An existing project with its backend already chosen | An idea and a deadline |
| Where state lives | Passed in and handed back; the widget owns none of it | Page state and app state the template defines |
| What a parameter means | The entire public API | An internal wiring detail the buyer will rename |
| Failure mode | Silently stops updating inside someone else's layout | Screens too coupled to reuse individually |
| What ships alongside | Instructions and a reference doc | Instructions, reference docs, marketplace images, iOS screenshots, cover, thumbnail |
Both shapes are in the same portfolio. Dream Home and Schedule are full-app templates, at $75 and $50. The free PDF Viewer has been live since May 2025 and sits at roughly 1,500 downloads with four positive reviews, which is the number that made the paid listings viable at all. The five bundles submitted in one batch in April 2026 are widget collections, and they were faster to build per widget and much slower to get right per parameter.
What changed in April
FlutterFlow v6.6.56, released on 21 April 2026, introduced Child Slots: "named 'drop zones' inside your components, places where anyone using the component can plug in any widget they want."
Read the wording carefully, because it says components, not custom widgets. Child Slots do not change what your Dart receives. What they change is the expectation buyers now bring to anything sold as reusable, and that expectation runs straight into custom widget territory. A composition mechanism arriving on the visual side is a signal about where the platform thinks reusable building blocks should live, and it is worth knowing before you design your next widget's parameter list around values alone.
Where to look first
Open the last custom widget you wrote and find its initState. Is there a line in there that copies widget.something into a field? If yes, and there is no didUpdateWidget beneath it, you have already found the bug you were going to file next month.
If that check comes back clean and the widget is still misbehaving, the question stops being about parameters and becomes a platform question: whether this belongs in FlutterFlow at all, or whether this is the moment to drop out of FlutterFlow into pure Flutter. And if you are still assembling the surrounding kit, the Flutter tools I actually keep installed is the shorter list.
So: what is the last parameter you added to a widget that the page never actually needed to change?
