Published August 18, 2026
Feature-First Clean Architecture: The AI-Ready Monorepo Pattern for Flutter
Very Good Ventures has published a detailed engineering guide to Feature-First Clean Architecture, or FFCA, a monorepo organization pattern for Flutter that inverts the traditional way teams structure large codebases. Instead of grouping packages by layer, FFCA makes the feature the top-level unit of organization, with each feature carrying its own domain, data, and presentation packages wired together through Dart workspaces.
The pattern grew out of a large native-to-Flutter migration involving multiple apps, dozens of features, and a single shared monorepo. The author, drawing on years of organizing Flutter code feature-first, sharpened the approach with contributions from the VGV engineering team, including improvements around deferred loading. The result is a structure designed to scale for human teams and behave predictably for coding agents at the same time.
Why Layer-First Stops Scaling
Most Flutter codebases, including VGV's own Very Good Layered Architecture, organize by layer. In a layered monorepo, a feature's UI sits in the application layer, its repository lives in the domain layer, and its data source resides in the data layer. That works beautifully for a single app with a handful of features, but as the count climbs past ten, each feature becomes spread across three packages and nothing in the structure ties its pieces together.
Three costs compound in that situation. Feature boundaries stay soft because nothing physically stops presentation code from reaching into another feature's internals — the boundary exists only in a reviewer's memory. Reusing or isolating a whole feature becomes a scavenger hunt across layer packages. And coding agents, which now contribute real code to repositories, have no machine-legible map of where a feature begins and ends, so they guess where new code belongs and cross boundaries in ways that only surface in review.
The FFCA Structure
FFCA inverts the top level: the feature becomes the primary unit, and each feature contains its own layers as separate packages. A cart feature, for example, splits into cart_domain with models, repository interfaces, and use cases; cart_data with data sources, DTOs, and repository implementations; and cart_presentation with screens, blocs, and the feature's module. Features with no screens, like auth or analytics, become headless features with just domain and data, free to grow a presentation package later without structural change.
Clean architecture's dependency rule still holds inside each feature. The domain layer is pure Dart and depends on nothing else in the feature. The data layer depends on the domain to implement its repository interfaces. The presentation layer depends only on the domain. The crucial difference is that because each layer is a real package with a declared dependency graph, the boundaries are enforced by the compiler rather than by convention. A layer violation becomes a compile error instead of a review comment.
- Each feature owns its domain, data, and presentation packages
- Dart workspaces wire the packages together and enforce the dependency graph
- An app becomes the list of features it composes, nothing more
- Deferred loading maps to web code splitting and Android dynamic feature modules
Two practical wins fall out of the structure. An app is little more than the list of features it composes, so one app can ship a feature another leaves out without either touching the other's code, and each app pulls in only the code and SDKs its features actually use. Second, because each feature presentation layer gives every screen its own barrel file, apps can defer-load a whole feature or a single screen on demand, which splits large web bundles into chunks and maps onto dynamic feature modules on Android.
Why It Works for Coding Agents
The properties that make FFCA good for large human teams are the same properties that make it good for AI agents. Every artifact has exactly one legal home, decided by naming conventions and layer packages, so two different agents asked where a new mapper belongs land in the same directory because the structure only permits one answer. The pubspec dependency graph scopes what is relevant, so an agent working in cart_presentation only resolves that package plus its declared domain dependencies; the dependency declaration effectively becomes the context window.
The enforcement matters just as much. When cart_presentation tries to import cart_data, the import fails because the dependency is not declared and never should be. That immediate, unambiguous feedback is precisely what agents need to avoid drifting across boundaries, and it guarantees a layer violation is caught at the moment it is written rather than surfacing weeks later in a code review.
The guide is more than an abstract proposal. The full standard, including package and naming conventions, is documented in the Very Good Engineering repository, and the pattern has been proven out in a production migration with multiple apps sharing one monorepo. For teams feeling the strain of a large Flutter codebase, or for teams that have started handing coding tasks to agents, FFCA offers a concrete way to make the structure readable to both humans and machines.
As Flutter monorepos become the norm for serious product organizations, the debate over organization strategy is shifting from aesthetics to efficiency. Feature-first packaging with enforced layers reframes that debate: the way you organize code is also the interface your tools read, and a structure only humans can navigate leaves agents guessing on every task. FFCA's answer is to make the structure itself the documentation, enforced by the build and legible to everyone, human or otherwise.