fluframe
Generate production-ready Flutter apps from the fluFrame boilerplate — Riverpod 3, go_router, localization, theming, dio, and tests wired out of the box.
▶ Try a generated app in your browser — the real thing, nothing installed.
| Home | Sample REST feature | Theming & 3 locales |
|---|---|---|
![]() |
![]() |
![]() |
Straight out of fluframe create — no edits.
Install
Requires Flutter 3.44 or newer (Dart 3.12+) — generated apps declare
sdk: ^3.12.1.
dart pub global activate fluframe
If fluframe is then not found, the pub cache's bin is not on your
PATH. Add $HOME/.pub-cache/bin (macOS/Linux) or
%LOCALAPPDATA%\Pub\Cache\bin (Windows) and reopen the terminal.
Usage
Runnable top to bottom, in this order:
fluframe doctor # check your environment first
fluframe create my_app --org com.mycompany --description "My shiny app"
cd my_app
flutter run --dart-define-from-file=env/dev.json
fluframe add feature billing --tab # later: scaffold your next feature
fluframe upgrade # later: pull template updates in (dry-run)
Adding a feature
create runs once; add feature runs every time the app grows one:
fluframe add feature billing # a full-screen route at /billing
fluframe add feature billing --tab # ...or a bottom-navigation tab
fluframe add feature billing --dry-run
It writes the repository, controller, screen and two tests, registers the
route in lib/app/router/app_router.dart, and adds the strings to all
three ARBs — telling you which ones still carry the English text. Then
run flutter gen-l10n.
Unlike upgrade, it is not dry-run by default: it only creates new
files and makes bounded insertions at anchors the template ships. Apps
generated before those anchors existed need fluframe upgrade first; the
command says so rather than guessing.
Why fluframe
Every starter is a snapshot: you generate, and from that moment your app
and the template diverge forever. fluframe is the only Flutter starter we
know of that keeps the connection — fluframe upgrade reconstructs the
template as it was at your generation version, three-way merges
everything that changed since into your working tree, and reports genuine
conflicts as conflicts instead of guessing. Your edits survive.
That is the reason to pick it, and it is not a reason to pick it over everything. If your team writes Bloc, or you need a package rather than an app, Very Good CLI is the better tool. docs/comparison.md lays both out honestly, including where fluframe loses.
What you get
- Riverpod 3 state management — manual
Notifier/AsyncNotifier, no codegen for providers - go_router 17 —
StatefulShellRoutebottom tabs, nested routes - freezed 3 + json_serializable models with a sample REST feature (dio)
- Typed error handling —
DioExceptionmapped to a sealedApiException - Localization —
flutter gen-l10nwith English, Japanese and Korean out of the box - Material 3 theming — light/dark with a persisted
ThemeMode - Persisted settings —
SharedPreferencesAsyncbehind a testableKeyValueStore - Flavors —
--dart-define-from-filewithenv/dev.json/env/prod.json - Strict lints —
very_good_analysis, zero warnings - Tests — unit + widget tests using mocktail and Riverpod overrides
Options
| Option | Default | Description |
|---|---|---|
--org |
com.example |
Bundle/application identifier organization |
--description |
template default | Description for the new pubspec.yaml |
--output-directory, -o |
. |
Where to create the project folder. May not contain & | < > ^ " or % — on Windows, cmd.exe reads these as command syntax before Flutter ever sees the path, so the rule applies on every platform: one path must behave the same for a whole team (exit 64) |
--platforms |
all six | Passed through to flutter create |
--backend |
none |
Wire a real auth backend (supabase | firebase) into the generated app |
--error-reporting |
none |
Wire crash reporting (sentry) into the error hooks |
--analytics |
none |
Wire product analytics (amplitude) into the analytics seam |
--no-pub |
off | Skip flutter pub get / gen-l10n |
Exit codes
Every failure is a sentence on stderr and a sysexits code — so a script can branch on what went wrong:
| Code | Name | Meaning |
|---|---|---|
0 |
EX_OK |
Success. Dry runs that found work to do exit 0 too |
64 |
EX_USAGE |
Bad arguments, or a refusal before anything was written (an invalid name, an app upgrade will not touch) |
65 |
EX_DATAERR |
.fluframe.json is malformed, holds a value of the wrong type, or names a package upgrade will not build a merge base for — including a name it fell back to from pubspec.yaml or the directory |
69 |
EX_UNAVAILABLE |
Something fluframe depends on is not usable: no Flutter on PATH, no git on PATH for upgrade --apply, a fatal doctor finding, or an older template bundle upgrade could not download or verify |
70 |
EX_SOFTWARE |
A step did not complete: a failed flutter invocation, an upgrade that ended with conflict markers still to resolve, an installed template bundle that is missing or incomplete (reinstall with dart pub global activate fluframe), or an error fluframe did not expect. The "This is a bug" trace is always this code |
74 |
EX_IOERR |
add feature failed while writing — a full disk, a read-only checkout, a locked file. The message says whether every file was put back, and names what to restore if not |
How it works
fluframe create runs flutter create --empty first — so platform folders
always match your installed Flutter version — then overlays the fluFrame
application template — lib/, test/, env/, l10n.yaml,
analysis_options.yaml, pubspec.yaml, plus a replacement README.md and
.gitignore and a ready-made .github/workflows/ci.yml — and rewrites
package-name tokens.
License
MIT
Libraries
- fluframe
- fluFrame CLI — generates production-ready Flutter apps from the fluFrame boilerplate.


