Skip to content
Tech Stack19 September 2026 · 10 min read

What a FlutterFlow Custom Widget Must Get Right

A FlutterFlow custom widget has to satisfy a contract before it renders. What a parameter can carry, why widget state goes stale, and how a widget talks back to the page.

What a FlutterFlow Custom Widget Must Get Right

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?

Isometric illustration of several differently-textured conduits converging into one sealed port on a pale deck, with a mismatched amber-ended square conduit lying unconnected beside it and a dust streak across the surface

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?

Isometric illustration of a pipeline split by a closed brass valve, glowing cyan blocks stacked solid in the upstream section and the downstream section completely empty, a thumb smudge visible on the valve housing

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?

Isometric illustration of a small dark module linked to a larger raised platform by two corridors, a single glowing cyan token travelling up the cyan return channel while an amber supply channel runs down, slack cable coiled unevenly at the module's base

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:

  1. 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.
  2. In your Dart, call it where the event actually occurs. Not in build, and not on a timer.
  3. On the page, bind an action flow to that callback the way you would to a button tap.
  4. 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 widgetFull-app template
Buyer already hasAn existing project with its backend already chosenAn idea and a deadline
Where state livesPassed in and handed back; the widget owns none of itPage state and app state the template defines
What a parameter meansThe entire public APIAn internal wiring detail the buyer will rename
Failure modeSilently stops updating inside someone else's layoutScreens too coupled to reuse individually
What ships alongsideInstructions and a reference docInstructions, 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?

Free resource

Free SaaS MVP Scope Template

A Notion document with the full feature checklist, MVP vs. nice-to-have table, pre-build questions, and cost signals — so you walk into any developer call knowing exactly what to ask for.

Get the template →
DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps using AI-augmented workflows — without agency coordination overhead. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

What data can I pass into a FlutterFlow custom widget as a parameter?

Values, a Widget Builder slot, and an Action callback, plus the width and height FlutterFlow adds to every custom widget whether you want them or not. Each one arrives in your State subclass as widget.parameterName, which is a live read rather than a copy. Anything outside that set, such as a controller or an open stream, has nowhere to go in the parameter panel, so the widget has to pick it up from app state instead. Treat the parameter list as the widget's public API and design it before you write the Dart: it is the only part a buyer or teammate can see, and it is the part you cannot change later without breaking every project that already uses the widget.

Why is my FlutterFlow custom widget not updating when the parameter changes?

Nine times out of ten the widget copied that parameter into a state field inside initState, and initState runs once per element, never again. The new value reaches widget.parameterName correctly; your build method just is not reading it. Fix it with didUpdateWidget, comparing the old widget to the new one and re-running the setup only when the value actually changed, or drop the local copy and read the parameter directly in build. If neither helps, the cause may be above your code: FlutterFlow issue #2788 tracks a confirmed case where a parameter change produces no redraw at all in a cloned documentation example.

How do I get a value out of a FlutterFlow custom widget and back to the page?

Declare a parameter of type Action, call it from your Dart when the event happens, then read the value on the page through Set Variable and Callback Parameters. That is the documented path, and it is the one that keeps the widget portable. The alternative, writing to FFAppState from inside the widget, works and quietly makes the widget project-specific, because it has decided where its output lives on behalf of everyone who ever reuses it. FlutterFlow's callbacks documentation covers the builder-side wiring, including how to attach an action flow to the callback once the parameter exists.

Does a FlutterFlow custom widget really need a fixed width and height?

Yes, and it is deliberate rather than an oversight, which is the part that catches people out. The builder requires both dimensions, while the generated Dart hands them to you as nullable doubles, so your widget has to cope with a size and with no size. Intrinsic sizing, where the widget measures its own content, is not available. FlutterFlow issue #2215 asked for exactly that in January 2024 and was closed as expected behaviour. Design around it: accept the box you are given, use LayoutBuilder inside it for anything responsive, and never assume your widget owns the full screen.

Where should I start if I have never written a FlutterFlow custom widget?

Start with a widget that wraps one pub.dev package and takes exactly two parameters, then wire a callback back to the page before you add anything else. Most tutorials get you to a rendered widget and stop, which is the easy half. The half that decides whether the widget survives contact with a real project is the round trip: a parameter going in, an Action firing out, and a didUpdateWidget that handles the parameter changing afterwards. Build that skeleton once on something trivial, like a countdown timer or a QR generator, and every later widget is a variation on it rather than a fresh set of platform surprises.