May 9, 2025
Modular Layout: A well-structured
/packages
and/app
setup promotes separation and parallel development.Build Isolation: Each module manages its own code generation setup to prevent conflicts.
Shared Utilities: Core module hosts shared models and utilities, imported downstream by feature modules.
Centralized Codegen: Scripts or tools like Melos simplify build_runner orchestration across modules.
Version Alignment: Unified builder versions across modules prevent dependency clashes.
CI Optimization: Caching and automation ensure efficient builds and up-to-date generated code.
Introduction
In large-scale Flutter apps, a multi-module structure promotes separation of concerns, parallel development, and faster CI/CD workflows. When combining modules with code generation, build_runner (aka build runner) becomes indispensable. This tutorial dives deep into setting up a modular Flutter workspace, configuring code generation with build_runner, and optimizing your build pipeline for productivity and maintainability.
Module Architecture Setup
Workspace layout
• /packages
– core (shared utilities, models)
– feature_user (user-related UI, logic)
– feature_payment (payment flows)
• /app (Flutter app entrypoint)Declaring path dependencies
In each module’s pubspec.yaml, reference shared packages via relative paths. For example, feature_user depends on core:
Isolating build configurations
Each module that uses code generation must include a dev_dependency on build_runner and any specific builders (json_serializable, injectable, freezed, etc.). In core’s pubspec.yaml:
Integrating build_runner Code Generation
Defining models in core
Use json_serializable in core to generate data classes. Inlib/models/user.dart
:
Run code generation:
Consuming generated code in feature modules. In feature_user, import the core model:
Since user.g.dart already exists in core, feature_user doesn’t need to re-run build_runner unless it has its own generated sources.
Orchestrating the runner from root
For a monorepo, invoke build_runner across all modules with a single command. Use a script (e.g., bash, PowerShell) or tools like Melos. Example in rootrun_codegen.sh
:
Customizing builders
Define abuild.yaml
in modules to override default behaviors, aggregate outputs, or skip files:
Best Practices for Multi-Module Projects
Dependency Direction: Always ensure modules depend only “downstream.” Core → feature → app. Avoid cycles.
Caching & CI: Cache build and .dart_tool/build directories between CI runs. Only re-run code generation when relevant files change.
Avoid Duplicated Builders: If multiple modules use the same builder version, align their versions to prevent conflicts. Maintain a shared dev_dependency pattern via a root “tooling” pubspec or via melos workspace constraints.
Isolate Generated Code: Keep generated files in module-local lib/. Avoid spilling generated artifacts across modules to reduce import confusion.
Automate via CI: Integrate flutter pub run build_runner build --delete-conflicting-outputs or watch mode in pre-commit hooks or CI pipelines. This enforces up-to-date generated sources and prevents stale code.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
With a well-structured multi-module architecture and build_runner-driven code generation, you gain modularity, clear separation of concerns, and reproducible builds. Whether you’re using json_serializable for models, injectable for dependency injection, or building custom source_gen builders, this setup scales gracefully.