๐Ÿงฉ Flutter ยท package-per-capability monorepo

A Flutter template with real boundaries between its parts.

Every capability is two packages โ€” an _api contract and an _impl implementation โ€” enforced by the analyzer, not by discipline. This site walks through why, shows the moving parts, and runs the modular CLI in front of you.

24
packages in the workspace
9
core capabilities
2
worked-example features
1
CLI that wires it all together

Try it

Clone it, rename it, run it.

modular rename rewrites the bundle id, the app name and every reference to modular_app_template in one pass โ€” there's no search-and-replace by hand.

1
Clone
Fork or clone the repo, then resolve the workspace once from the root.
2
Rename
Activate the CLI and point modular rename at your bundle id and app name.
3
Run
Launch the dev flavor โ€” you get a counter screen and a live posts list, wired end to end.

terminal

# clone and resolve the workspace
git clone https://github.com/thisisyusub/modular_app_template my_app
cd my_app && flutter pub get

# install the CLI, then rename the template to your app
dart pub global activate --source git https://github.com/Yusubov-Engineering/modular_cli.git --git-ref v1.0.0
modular rename --bundle-id com.acme.myapp --name "My App"

# run it
cd app && flutter run --flavor dev \
  --dart-define-from-file=../config/dev.json

Why not just use folders

Folders don't stop anyone. Package boundaries do.

A lib/features/counter/ directory is a suggestion. A separate counter_impl package with implementation_imports set to error is a wall the analyzer enforces on every save.

FoldersThis template
Import a feature's internalsnothing stops youanalyzer error
Feature A reaching into feature Bone import awayimpossible; must go through _api
Forgot to register a modulecrashes at runtimemodular doctor catches it before you ship
Navigating by hardcoded patheverywhere, eventuallyroute addresses are private to _impl
Adding a featurecopy, paste, rename, wire by handmodular new feature <name>

The cost is real โ€” two packages per capability, and a composition root you have to keep current. It tends to pay for itself above roughly ten features or two teams working the same codebase; probably not below that.

The shape

Four layers, dependencies flow one way.

app โ†’ features โ†’ base / core. Nothing downstream ever imports upstream, and an _impl package is invisible outside the one that owns it.

/app โ€” the only leaf
appcomposition root ยท flavors ยท bootstrap
wires every _impl
/features โ€” one _api + _impl pair per domain
counter_api + _impl
posts_api + _impl ยท data layer
depends on _api contracts only
/base โ€” cross-feature primitives
app_localizationARB ยท locale scope
app_network_contractAppResponse ยท parser
/core โ€” infrastructure, each an api/impl pair
state_manager
router
dependency_injection
network
design_system
storage
logger
biometric_auth
app_linter

What's in the box

Nine core capabilities, two worked examples.

Every core capability ships as its own _api/_impl pair so it can be swapped without touching a feature.

core

state_manager

From-scratch State/Event/Effect controllers. No riverpod, no bloc, no dependency at all.

core

router

go_router underneath, but no feature ever imports it โ€” routes are typed requests.

core

dependency_injection

get_it underneath, exposed through a container-agnostic locator.

core

network

dio-backed REST client that knows nothing about your backend's envelope.

core

design_system

Tokens, theming, six example components. Build yours on the tokens.

core

storage

Standard + secure key/value storage behind one contract.

base

app_localization

ARB translations in 7 languages, locale persisted and RTL-aware.

base

app_network_contract

This app's own success/error envelope โ€” the one file you edit for a new backend.

feature

counter

The minimal shape modular new feature generates.

feature

posts

The same shape plus a full data layer over JSONPlaceholder โ€” DTOs, repository, sealed failures.

core

logger

talker-backed, with an in-app log screen.

core

biometric_auth

Face ID / Touch ID behind a two-method contract.

The CLI

modular generates a feature and wires it โ€” in one command.

Generating two packages is easy. Registering the feature's module, adding it to the router, and listing it in the workspace is the part that's easy to forget โ€” so the CLI does it, by editing three files at known <generated:...> anchors.

  • โœ… modular new feature <name> โ€” scaffolds and wires an _api/_impl pair
  • โœ… modular doctor โ€” five architecture checks the compiler can't run
  • โœ… modular gen assets โ€” regenerates typed asset accessors
  • โœ… modular rename โ€” retargets bundle id and app name repo-wide
See how the wiring works โ†’
modular