Skip to content
Tech Stack19 September 2026 · 6 min readUpdated 30 September 2026

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

What you'll learn

  • What parameters can a FlutterFlow custom widget take?
  • Why does a FlutterFlow custom widget stop updating?
  • How does a FlutterFlow custom widget send a value back to the page?
Article contents

Your custom widget renders in Run Mode, then stops responding when the page changes a value, or you can't work out how to get a result back out of it. Before you touch anything else, open the widget's code and check whether it copies a parameter into its own state field. That one check explains most of the "not updating" reports.

A FlutterFlow custom widget has to satisfy a contract before it has to look like anything. I've built more than forty custom widgets across eight FlutterFlow Marketplace templates, and the contract has three edges: what comes in, what the widget is allowed to remember, and how it speaks back. Everything below is grounded in FlutterFlow's documentation, Flutter's API docs and public issue-tracker entries. I haven't re-run the tracker cases myself for this post, so read them as reported.

What parameters 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

A custom widget takes typed parameters that you declare in the builder and read in code as widget.parameterName, plus Widget Builder slots and Action callbacks, and it must always be given a width and a height.

FlutterFlow's custom widget documentation says "For custom widgets, it is mandatory to specify both width and height," and describes parameters as reachable through widget. in your code. That indirection matters, because widget.parameterName is a live read of the current configuration, while anything you copy out of it is a snapshot.

Two parameter kinds behave differently from plain values:

  • 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 receive a slot rather than a value, which is how you write a list widget that doesn't dictate what a row looks like.
  • Action. A callback. The page hands your widget a piece of behaviour and your Dart decides when to fire it.

Something like a controller, an open stream or a Dart function you wrote elsewhere has no obvious place in the parameter panel. My recommendation is to treat the parameter list as the widget's public API: design it so everything the widget needs is expressible there, and accept that anything else has to reach it through app state. That trade-off, and the price of the dependencies underneath, is the same one described in when pub packages break inside FlutterFlow's build.

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

The most common cause is a widget that copied a parameter into its own state inside initState, which Flutter calls exactly once per State object, so later parameter changes never reach the build method.

That is standard Flutter, not a FlutterFlow quirk. Flutter's initState documentation says "The framework will call this method exactly once for each State object it creates." When the parent rebuilds with a new value, didUpdateWidget is the hook the framework calls, "with the previous widget as an argument." A minimal fix looks like this:

@override
void didUpdateWidget(covariant MyWidget oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.value != widget.value) {
    _controller.value = widget.value;
  }
}

Or skip the local copy and read widget.value directly in build. As a rule of thumb, a widget that owns something with a lifecycle, such as a countdown timer's ticker, needs local state, while a QR generator that only renders its input doesn't. That is my recommendation, not a platform rule.

If didUpdateWidget doesn't help, the cause may sit above your code, and FlutterFlow's own tracker shows the ambiguity. Issue #2703, opened on 11 April 2024, reported that a custom widget didn't refresh after an update page state or app state action, and it is labelled "closed: not a bug". Issue #2788, opened 12 days later, describes a parameter change not reflected in a cloned documentation example, and it was still open with the label "status: confirmed" when I checked on 30 September 2026.

This suggests that "my custom widget doesn't update" is one symptom with at least two causes, and the platform's own triage has treated similar reports differently. Check didUpdateWidget first, because that fix is yours and takes a few lines. Reading app state directly through FFAppState() inside the widget is another route, but it ties the widget to the project it was written for.

A short triage order, which is my recommendation and not FlutterFlow's:

  1. Add a print statement inside didUpdateWidget and change the value from the page. If it never prints, the page isn't handing the widget a new value, and the bug lives in the page-side state or action.
  2. If it prints but the screen stays the same, compare oldWidget and widget and confirm your build method reads the current one rather than a stale field.
  3. If both look right, rebuild the smallest version of the widget in a fresh project and see whether it reproduces. A clean repro is what the tracker entries above are missing.

How does a FlutterFlow custom widget send a value 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

It sends the value through a parameter of type Action: the widget calls the callback from Dart, and the page reads what was passed under Callback Parameters.

FlutterFlow's callbacks documentation states "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 custom widgets page shows the same pattern with an onRating callback. The wiring, in order:

  1. Declare a parameter of type Action on the widget and give it the arguments the page will care about, for example a circular slider handing back a double.
  2. Call it in your Dart where the event actually happens, not inside build.
  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, rather than through a page-state variable the widget wrote to.

Step four keeps the widget portable. A widget that writes to FFAppState decides on behalf of every future user where its output lives, while a widget that fires a callback decides nothing, which is the right amount.

Does a custom widget own the screen?

No. Flutter's layout rule is "Constraints go down. Sizes go up. Parent sets position.", and Flutter's constraints guide adds that a widget can decide its own size only within the constraints its parent gives it. FlutterFlow makes that concrete by requiring width and height. Issue #2215, opened in January 2024, asked for custom widgets that size themselves to their content and was closed as "not a bug". Build for the box you're given, and use LayoutBuilder inside it for anything responsive.

Drop-in widget or full-app template?

The case study I keep for the marketplace work treats these as different products on purpose, and the architecture differs accordingly.

Drop-in widgetFull-app template
Buyer already hasAn existing project with its backend chosenAn idea and a deadline
Where state livesPassed in and handed back; the widget owns none of itPage and app state the template defines
What a parameter meansThe entire public APIAn internal detail the buyer may rename
Likely failureStops 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

Dream Home and Schedule are full-app templates; the other bundles are collections of widgets. The failure-mode row is my inference from that split, not a measured result.

What changed in April 2026?

FlutterFlow v6.6.56 was released on 21 April 2026, and its release notes introduce Child Slots as "named 'drop zones' inside your components". That is about components, not custom widgets, so it doesn't change what your Dart receives. It does suggest where the platform expects reusable building blocks to be composed, which is worth knowing before you design a widget's parameter list around values alone.

If a widget still misbehaves after these checks, the question shifts to whether it belongs in FlutterFlow at all, and when to drop out of FlutterFlow into pure Flutter covers that decision. The Flutter tools I keep installed are the shorter list.

So open the last custom widget you wrote and find its initState. Does it copy widget.something into a field with no didUpdateWidget beneath it?

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 parameters can I pass into a FlutterFlow custom widget?

You can pass typed values, a Widget Builder slot and an Action callback, and every custom widget must also be given a width and a height. Each parameter you declare is read in your code as widget.parameterName, which is a live read of the current configuration rather than a copy. Anything outside that set, such as a controller or an open stream, has no obvious place in the parameter panel, so my recommendation is to treat the list as the widget's public API and design it before writing the Dart. It is the only part a teammate or buyer sees, and changing it later breaks every project already using the widget.

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

The most common cause is a widget that copied the parameter into a state field inside initState, which Flutter calls exactly once for each State object. The new value does reach widget.parameterName, but your build method is reading the stale copy. Override didUpdateWidget, compare the old widget with the new one and re-run setup only when the value changed, or drop the copy and read the parameter in build. If neither helps, the cause may be upstream: FlutterFlow issue #2788 is labelled "status: confirmed" and describes a parameter change not reflected in a cloned documentation example.

How do I return a value from a FlutterFlow custom widget 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 keeps the widget portable. Writing to FFAppState from inside the widget also works, but it decides on behalf of every future user where the widget's output lives. FlutterFlow's callbacks documentation covers the builder-side wiring, including how to attach an action flow to the callback once the parameter exists. A circular slider handing back a double is a typical shape for the argument.

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

Yes, FlutterFlow's documentation says it is mandatory to specify both width and height for custom widgets. That is deliberate rather than an oversight, and it has consequences for anything that wants to size itself to its content. FlutterFlow issue #2215 asked for exactly that in January 2024 and was closed as "not a bug". Flutter's own layout rule points the same way, since a widget can pick its size only within the constraints its parent gives it. Design for the box you are handed, and use LayoutBuilder inside it for anything responsive rather than assuming the widget owns the screen.

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

Start with a small widget that takes exactly two parameters, then wire an Action callback back to the page before adding anything else. Most tutorials stop once something renders, which is the easy half. The half that decides whether the widget survives a real project is the round trip: a parameter going in, a callback firing out, and a didUpdateWidget that handles the parameter changing afterwards. This is a recommendation based on the contract FlutterFlow documents, not a guarantee. Build that skeleton once on something trivial, like a countdown timer, and later widgets become variations on it instead of fresh surprises.