Scaffolding
flutter_modular_monorepo Free
- Tier
- Freeversion 5.2.6
- Command
archipelago generate flutter_modular_monorepo- Generates
- the Dart workspace, the app, shared packages, devtools and the starter features you choose
A comprehensive Flutter monorepo template with modular architecture, dual-GetIt DI, FeatureSDK self-registration, two-phase initialization, and build-time dependency stripping. Generates a full Dart workspace with Melos, Fastlane, and CI/CD workflows.
Variables
| Variable | Type | Default | Description |
|---|---|---|---|
| appName | string | MyApp | The name of your application |
| organization | string | MyCompany | The name of your organization |
| domain | string | com | Your organization's top-level domain. See App and Bundle IDs for how they become the Android and iOS IDs. |
| prefix | string | App | Prefix for project components (e.g., 'App' for AppButton) |
| prefixCasing | enum (upperCase, pascalCase) | pascalCase | Casing style for the prefix |
| gitProvider | enum (github, gitlab) | github | Git provider for CI/CD workflows |
| networkLibrary | enum (dio, http) | dio | HTTP client library |
| isUsingWebSocket | boolean | false | Include WebSocket SDK |
| websocketLibrary | enum (dart, phoenix) | dart | WebSocket library (if using WebSocket) |
| isUsingFirebase | boolean | false | Using Firebase |
| analyticsVendors | array | [] | Analytics vendors to include (firebase, appsflyer, mixpanel, moengage, none) |
| monitoringVendors | array | [] | Monitoring vendors to include (crashlytics, sentry, newrelic, grafana, none) |
| isUsingFeatureFlags | boolean | false | Include Feature Flags SDK |
| featureFlagVendor | enum (flagr, remote_config, custom, none) | none | Feature flag vendor implementation |
| stateManagement | enum (bloc, cubit, provider, riverpod) | bloc | State management solution, forwarded as the raw string to the state-aware sub-bricks (shared_dependencies, auth_sdk, home_sdk, paywall_sdk). pre_gen.dart derives only isBloc/isProvider/isRiverpod for this brick's own templates; it never branches on cubit, which is simply passed through. |
| includeGenerated | boolean | false | Include generated files in Git |
| includeIntegrationTest | boolean | true | Include integration tests |
| integrationTestFramework | enum (patrol, maestro, none) | patrol | Integration test framework |
| initializeGitRepo | boolean | true | Initialize a Git repository |
| starterFeatures | array | introduction_sdk, auth_sdk, home_sdk, paywall_sdk | Which starter feature SDKs to generate. introduction_sdk and home_sdk have real dependencies on auth_sdk (and introduction_sdk on home_sdk too) — see Starter Feature Dependencies below; missing prerequisites are auto-included by pre_gen.dart, not rejected. Any other value (for example a leaf brick such as feedback_sdk) stops generation with Unknown starterFeatures: <values>. Valid values: introduction_sdk, auth_sdk, home_sdk, paywall_sdk. Add other feature bricks after generation with archipelago generate <brick>. |
| authBackend | enum (rest_api, supabase, firebase) | rest_api | Auth backend for the auth_sdk starter feature, forwarded to auth_sdk. firebase turns on isUsingFirebase. supabase adds SUPABASE_URL and SUPABASE_ANON_KEY placeholders to every .env.<flavor>. See auth_sdk backend setup. Ignored without auth_sdk. |
| userTier | string | free | User subscription tier (injected by CLI, not prompted) |
Usage
Interactive
archipelago generate flutter_modular_monorepoNon-interactive (CI)
archipelago generate flutter_modular_monorepo --config my_config.jsonGeneration Phases
pre_gen.dart stops ("Nothing was changed") when the output directory, or its <app_name> subdirectory, already has an archipelago.yaml: re-creating over a project would record your edits as generated. Add bricks to an existing project with archipelago generate <brick>. At the end of post_gen, the generation records are refreshed only for records this run wrote, and not at all when a sub-brick failed.
post_gen.dart (2,300+ lines) runs a fixed sequence of named phases, logged with === Phase N: ... === banners. Two failure models apply: sub-brick generation failures (Phases 1–5) are hard — any failure there throws at the end and the run is reported as failed; every phase after that (cleanup, workspace pruning, git init, env files, platform init, bootstrap, flavorizr/splash/icons, build_runner, l10n, format) is advisory — a failure is logged as a warning with manual next-step commands, but does not fail the overall generation, because those tools may not be usable immediately after a bare mason make (e.g. flutter/dart not yet bootstrapped).
- Phase 0 — writes
archipelago.yaml(ArchipelagoConfigHelper.writeInitial, recordsappName,organization, the Android and iOS IDs (package_name,ios_bundle_id),prefix,stateManagement, vendor selections, etc.) — this is the fileArchipelagoConfigGuardin addon bricks likeutilities_sdkrequires to already exist. Then: cleans up conditional files (integration-test framework, Firebase files, any empty files left by unmet Mustache conditionals) and copies PNG image assets. - Phase 1: Core Utilities —
shared_dependencies,shared_app_config,shared_consent,shared_identifiers,flutter_l10n,core_infrastructure_package,app_lints,app_utilities(all generated viaSubBrickGenerator, alwaysisForMonorepo: true) - Phase 1.5: Assets & UI Kit —
flutter_asset(withprefix),flutter_ui_kit - Phase 2: Tooling —
monorepo_toolkit,asset_generator,changelog_generator,ui_kit_generator - Phase 3: Infrastructure SDKs —
network_sdk,launch_tracker_sdk(always; the hostbootstrap()andAppDelegatecarry its native launch-milestone calls, see Host code),websocket_sdk(ifisUsingWebSocket),analytics_sdk+ selected vendor impls (if any analytics vendor selected),feature_flag_sdk(ifisUsingFeatureFlags), selected monitoring vendor impls, thenmonitoring_sdkalways — even with zero vendors selected, soapp_launch_tracker_implalways hasmonitoring_apito resolve (monitoring_impl.isEnabledisfalseand the vendor fanout is empty). Monitoring vendor impls generate beforemonitoring_sdk(the reverse of analytics) — see Vendor Multi-Select below.- Pro/Enterprise Tooling (
tierToolingBricksinpost_gen.dart) —proorenterprise:coverage_manager,size_guard,detect_new_library,size_analyzer,permission_guard,asset_optimizer;enterpriseaddsaffected. The tiers matchbrick_registry.yaml.pre_gen.dartderivesisProfromuserTier, and the CI templates use it:pr-checks.yml,update-baselines.ymland the guard, analysis and baseline jobs of.gitlab-ci.ymlrender only for Pro and Enterprise, because they call these tools. Before 5.0.0 the CI calleddetect_new_library,size_analyzerandpermission_guardin every tier but no tier generated them, andui_kit_generator(Free) was generated only for Enterprise.
- Pro/Enterprise Tooling (
- Phase 5: Starter Features — whichever of
introduction_sdk/auth_sdk/home_sdk/paywall_sdksurvive dependency-closure expansion (see Starter Feature Dependencies below); an emptystarterFeatureslist logs "bare scaffold" and generates none. 5.5. Phase 5.5: Workspace Cleanup —pruneEmptyGlobEntriesremoves any top-level workspace glob entry (in the rootpubspec.yaml) that ended up matching zero packages — most commonlyfeatures/**on a bare scaffold (starterFeatures: []). Dart's native workspace resolution doesn't skip one dead glob gracefully; it silently degrades package discovery for the entire workspace, breakingmelos list/melos bootstrap/melos run buildeven thoughdart pub getitself still exits 0. - Phase 6: Git Init (if
initializeGitRepo) —git init -b main && git add .- Environment files (always, unlabeled in the log banner but internally "Phase 6.5") — writes
.env.development/.env.staging/.env.productiondirectly intoshared/app_configwith a fixed placeholder template (API_BASE_URL,APP_NAME,AES_KEY,DATABASE_URL, plus a line per selected analytics/flagr vendor — kept in sync by hand withshared_app_config's own pre_gen-derived flags), then copies.env.developmentto the active.envif it doesn't already exist. This is a separate, generation-time mechanism from theenv_sync/monorepo_toolkit envcommands documented on theshared_app_configpage — both exist and both write the same files, so a project generated end-to-end already has working flavor.envfiles without runningenv initby hand.
- Environment files (always, unlabeled in the log banner but internally "Phase 6.5") — writes
- Phase 7: Platform Initialization — runs
flutter createin a scratch directory and merges its output intoapps/{appName}(android, ios) and the widgetbook app (all platforms) with shared_hooksPlatformScaffoldHelper.ensure, the same pathapp_scaffolduses. The merge adds only missing files, so the files the brick renders win:MainActivity.kt(FlutterFragmentActivitywith the deeplink channels),AppDelegate.swift(deeplinks and thelaunch_trackermarkers),android/app/build.gradle.ktsandproguard-rules.pro.flutter createsupplies the rest (Runner.xcodeproj,Podfile, icons, Gradle wrapper,AndroidManifest.xml).Info.plistkeepsflutter create'sUIApplicationSceneManifest: iOS 27 needs the scene lifecycle, and the brick'sAppDelegatetakes deeplinks as a scene delegate (see Deeplink handling). The phase then patches Gradle (release signing, packaging, ABI splits, artifact names) and iOS config. TheInfo.plistkeys it adds go before the root dict's closing tag, so the scene manifest stays intact. Running it again changes nothing. Before 4.12.1 this phase deleted the brick's platform files and kept plainflutter createoutput.- Phase 7.5: Debug Keystore & Asset Links — runs after Phase 7 so the keystore lands in the merged
android/tree. Generates a debug keystore and substitutes its fingerprint into.well-known/assetlinks.json/apple-app-site-association.
- Phase 7.5: Debug Keystore & Asset Links — runs after Phase 7 so the keystore lands in the merged
- Phase 8: Bootstrap & Code Generation —
dart pub get/melos bootstrap.- Phase 8.5: Platform & Asset Generation — flavorizr, native splash, launcher icons. flavorizr's
instructions:list runsandroid:flavorizrGradle(emitsandroid/app/flavorizr.gradle.ktswith the product flavors) immediately beforeandroid:buildGradle(which only appends theapply from("flavorizr.gradle.kts")line referencing it) — listing only the latter leaves a dangling reference and Android builds fail before compiling anything. With Patrol selected, apatrol iosstep runs right after flavorizr:PatrolIosHelperadds theRunnerUITestsUI testing target (one build configuration per project configuration, so the flavored ones are included),ios/RunnerUITests/RunnerUITests.mand the scheme Test-action entry. The Android side (PatrolJUnitRunner, orchestrator,MainActivityTest.java) is templated, and Patrol's CLI settings are thepatrol:section of the apppubspec.yaml. The app'sprotobuf-javaexclusion (a duplicate-class fix) skips the Android test platform's_internal-*configurations, which need that library to run connected tests.
- Phase 8.5: Platform & Asset Generation — flavorizr, native splash, launcher icons. flavorizr's
- Phase 9: Code Generation —
melos run build(build_runner) across the whole workspace.- Phase 9.5: L10n Generation — the standalone
dart run slangCLI, run after build_runner, not before (melos run build:selectfilters packages by a directbuild_runnerdependency, which slang-only packages likeshared/locale_coredon't declare, so build_runner alone leavestranslations.g.dartungenerated; running slang before build_runner previously caused a different collision).
- Phase 9.5: L10n Generation — the standalone
- Phase 10: Format —
dart formatonly. There is no automaticdart fix --applystep — it was deliberately removed (seetemplate_base/CLAUDE.md): on Dart 3.13.0/Flutter 3.47.0,dart fix --apply'sprefer_const_constructors_in_immutablesandunnecessary_null_checksauto-fixes are confirmed to silently produce invalid/wrong code on this codebase's primary-constructor classes, so it is never run unattended against generated scaffolding.
Vendor Multi-Select
Analytics and monitoring vendors are selected via Mason type: array multi-select. Boolean flags are derived in pre_gen.dart:
? Which analytics vendors do you want?
❯ ◉ firebase
◯ appsflyer
◯ mixpanel
◯ moengage
◯ noneOnly selected vendor bricks are generated. Selecting "none" or leaving empty skips the entire SDK, with one exception: monitoring_sdk itself is always generated (api + impl + noop), even with zero monitoring vendors selected, so app_launch_tracker_impl always has monitoring_api to resolve — with no vendor, monitoring_impl.isEnabled is false and the vendor fanout is empty. build_prepare.yaml does not swap monitoring_impl for the noop: crash reporting ships in release, gated by the diagnostics consent purpose. Monitoring vendor impls also generate before monitoring_sdk (the reverse of analytics, where the SDK generates first) — each vendor registers its own module into injector.dart's externalPackageModulesBefore list, and MonitoringFanoutImpl reads those @Named vendor instances eagerly at MonitoringImplPackageModule.init() time, so a vendor generated afterward would be invisible to the fanout.
Starter Feature Dependencies
starterFeatures presents introduction_sdk, auth_sdk, home_sdk, and paywall_sdk as an unconstrained type: array multi-select, but the generated packages are not actually independent of each other:
| Feature | Requires | Why |
|---|---|---|
introduction_sdk | auth_sdk, home_sdk | GetStartedPage/WelcomePage call GetIt.I<AuthSDK>().requireAuthentication() and navigate via the AuthGuard.authPath constant (both from auth_api); GetStartedPage also calls GetIt.I<HomeSDK>().goToHome(context) (from home_api). |
home_sdk | auth_sdk | HomePage calls GetIt.I<AuthSDK>().logout() (from auth_api). |
auth_sdk | — | No cross-feature package dependency. |
paywall_sdk | — | No cross-feature package dependency — the one starter feature that is genuinely independent and safe to select alone. |
These are concrete SDK-interface imports for cross-feature navigation/session control (not just route-name strings), so the coupling is structural, not merely navigational. Decoupling would require introducing shared "capability" interfaces in feature_sdk that auth_sdk/home_sdk implement and introduction_sdk/home_sdk depend on instead of the concrete auth_api/home_api packages — a larger redesign that hasn't been justified yet.
Instead, flutter_modular_monorepo/hooks/pre_gen.dart expands whatever starterFeatures subset is selected to its full dependency closure before Phase 5 runs, logging a notice (starterFeatures: auto-including auth_sdk, home_sdk (required by introduction_sdk)) whenever it adds a prerequisite the user didn't explicitly select. This means every starterFeatures subset — including [], ["paywall_sdk"], ["home_sdk"], or ["introduction_sdk"] alone — always generates a workspace that resolves cleanly; omitted prerequisites are silently (but visibly, via the log line) added rather than left to break dart pub get.
Mason does not check the values of an array var, so pre_gen.dart does: an unknown starterFeatures value logs an error that names the valid values and exits 1, instead of being dropped.
Generated Structure
your_project/
├── .claude/commands/ # Claude Code slash commands
├── .github/workflows/ # CI/CD workflows (if GitHub)
├── .claude/skills/ # Agent skills (see AI Agent Support)
├── .cursorrules # Cursor rules
├── AGENTS.md # AI agent context + skill index
├── CLAUDE.md # AI coding assistant context
├── .mcp.json # Registers the Marionette MCP server
├── .run/ # JetBrains run configurations, per app and flavor
├── .vscode/ # launch.json + tasks.json, per app and flavor
├── apps/
│ ├── {app_name}/
│ │ ├── android/ # Brick host files + flutter create backfill + Gradle patching
│ │ ├── ios/ # Brick AppDelegate + flutter create backfill + iOS patching
│ │ ├── fastlane/ # iOS/Android build automation
│ │ ├── launcher/ # App icons per flavor
│ │ ├── splash/ # Splash screen assets
│ │ ├── lib/
│ │ │ ├── app.dart
│ │ │ ├── bootstrap.dart
│ │ │ ├── main_development.dart
│ │ │ ├── main_staging.dart
│ │ │ ├── main_production.dart
│ │ │ ├── di/injector.dart
│ │ │ ├── host/
│ │ │ │ ├── host_app_widget.dart
│ │ │ │ └── initializers/
│ │ │ └── router/app_router.dart
│ │ ├── test/
│ │ │ ├── architecture/feature_boundary_test.dart # No feature depends on another's _impl
│ │ │ ├── di/injector_test.dart # Composition-root smoke test
│ │ │ ├── di/{lazy_feature_sdk,base_scope_budget,port_singleton}_test.dart # DI guard tests
│ │ │ └── support/composition_root_harness.dart, feature_files.dart # Harness filled by leaf bricks
│ │ ├── flavorizr.yaml
│ │ ├── flutter_launcher_icons-*.yaml
│ │ └── flutter_native_splash.yaml
│ └── {app_name}_widgetbook/ # Widgetbook app (all platforms)
├── shared/
│ ├── feature_sdk/ # Native to this brick — FeatureSDK base + registry
│ ├── router_registry/ # Native to this brick — route registration abstraction
│ ├── consent_api/ # Generated by the `shared_consent` sub-brick (Phase 1) — telemetry consent + PII-scrub contracts
│ ├── consent_impl/ # Generated by the `shared_consent` sub-brick (Phase 1) — persisted consent gate, default scrub rules
│ ├── identifiers/ # Generated by the `shared_identifiers` sub-brick (Phase 1)
│ ├── locale_core/ # Generated by the `flutter_l10n` sub-brick (Phase 1)
│ ├── dependencies/ # Generated by the `shared_dependencies` sub-brick (Phase 1)
│ └── app_config/ # Generated by the `shared_app_config` sub-brick (Phase 1)
├── features/ # Empty except .gitkeep until Phase 5 sub-bricks populate it
│ ├── auth/ # auth_api + auth_impl (with per-feature slang)
│ ├── home/ # home_api + home_impl (with per-feature slang)
│ ├── introduction/ # Onboarding feature
│ └── paywall/ # paywall_api + paywall_impl
├── infrastructure/ # Infrastructure SDKs + vendor impls (Phase 3 sub-bricks)
├── packages/ # UI Kit, shared packages (Phase 1.5 sub-bricks)
├── utilities/ # Utility packages (Phase 1 sub-bricks)
├── devtools/ # Monorepo toolkit + asset generator (Phase 2 sub-bricks)
├── gradle/libs.versions.toml # Native to this brick — Gradle version catalog
├── third_party/ # Native to this brick — vendored upstream patches
│ └── mason_logger/ # In-tree patched copy resolving a win32 6.x/flutter_flavorizr
│ # compile-time conflict; see its own
│ # PROVENANCE.md for the upstream commit and removal condition
├── .well-known/
│ ├── assetlinks.json # Android App Links — fingerprint substituted in Phase 7.5
│ └── apple-app-site-association # iOS Universal Links — same substitution
├── build_prepare.yaml # Build-time dependency stripping
├── flavors.yaml # Flavor configuration
├── pubspec.yaml # Workspace root with melos scripts
└── analysis_options.yamlKey Architecture Patterns
- Dual-GetIt — Global scope (SDKs, infra) + local scope per feature (repos, usecases)
- FeatureSDK self-registration — Features register routes and DI without app knowing about them
- Two-phase init — Pre-launch (blocking: DI, routes) + Post-launch (non-blocking: analytics, flags)
- build_prepare — Swap debug → noop dependencies at build time (debug tooling such as
app_debugger→app_debugger_noop; monitoring and the launch tracker are not swapped). Thedebug_build_onlylist follows the build mode:--mode release|profileswapsapp_marionette→app_marionette_noopfor every flavor - Marionette MCP —
features/app_marionetteinstalls LeanCode'sMarionetteBindingin debug builds only, so an AI agent can drive the running app; the Fastfile, CI templates and melos scripts pass--modeso no release or profile build contains it (see AI Agent Support) - Composition-root smoke test:
apps/{app_name}/test/di/injector_test.dartbuilds the real DI container underflutter test. Leaf bricks add their test prerequisites totest/support/composition_root_harness.dartwhen they register their DI module.melos run di:checkfails when that harness orinjector.dartdrifts from the package manifests. See Testing. - DI guard tests:
test/architecture/feature_boundary_test.dart,test/di/lazy_feature_sdk_test.dart,test/di/base_scope_budget_test.dartandtest/di/port_singleton_test.dartguard the feature boundary, lazy SDKs, the boot budget and port identity without a per-feature list. See DI Guard Tests. - Per-feature slang — Each feature module can have its own translations with scoped
translate_var
Note: userTier is injected automatically by the CLI based on the authenticated user's subscription tier and is not included in interactive prompts.
Test gate and golden tests
The root pubspec.yaml melos scripts test:select (and so test) and coverage:run:parallel run flutter test --coverage --exclude-tags golden. They exclude golden tests by tag, so the *_ui_kit package keeps its ordinary tests in the default run. flutter test exits 79 when the exclusion leaves a package with no tests (all tests golden). The scripts accept 79 as success; any other exit code fails.
melos run test:golden runs only the golden tests, read-only against the committed fixtures. To regenerate fixtures, run flutter test --tags golden --update-goldens inside the UI kit package. See the testing guide.
Android Release Build
android/app/build.gradle.kts's release build type ships with isMinifyEnabled/isShrinkResources on and proguardFiles(...) wired to android/app/proguard-rules.pro. That file carries baseline @Keep-respecting and stack-trace-preserving rules, plus an archipelago:proguard:keep-rules marker pair — capability bricks (webview_sdk, webview_sdk_advanced, biometric_auth_sdk) append their own conditional keep rules there at generation time, so a release build only carries the rules the app's actual dependency tree needs.
An archiveDebugSymbols Gradle task is also registered:
./gradlew :app:archiveDebugSymbols -Pvariant=releaseRun after a release build — copies that variant's R8 mapping.txt and native-debug-symbols bundle into build/outputs/debug-artifacts/<variant>/, a stable path CI or a developer can upload to a crash-reporting vendor for deobfuscation.
Changelog
Run archipelago brick info flutter_modular_monorepo to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.