Managing Monorepos with Melos
The third time I copied a shared ApiClient into two Flutter apps, I stopped pretending a folder symlink was a strategy. We had a design system, a networking layer, and two product flavors that all needed the same bug fix within hours of each other. Melos is the tool that turned that sprawl into a repo where one melos bootstrap links local packages, one script runs every test, and releases stay coordinated.
Melos is a CLI for Dart and Flutter monorepos. It sits above pub workspaces and gives you dependency bootstrapping, cross-package script execution, conventional versioning, and changelog generation. If you have ever watched a CI job fail because package B still pointed at pub.dev while package A expected a local path override, Melos is the fix.
Workspace layout that scales
A typical Melos repo looks like this:
my_workspace/
melos.yaml
pubspec.yaml # workspace root
packages/
app/
design_system/
api_client/
The root pubspec.yaml declares the workspace:
name: my_workspace
publish_to: none
workspace:
- packages/app
- packages/design_system
- packages/api_client
Each package keeps its own pubspec.yaml with normal dependencies. Internal packages reference each other by name and version constraint; Melos resolves them locally during bootstrap.
melos.yaml essentials
Create melos.yaml at the repo root:
name: my_workspace
packages:
- packages/**
scripts:
analyze:
run: melos exec -- dart analyze .
description: Run analyzer in all packages
test:
run: melos exec -- flutter test
description: Run tests in all packages
format:
run: melos exec -- dart format . --set-exit-if-changed
Run dart pub global activate melos, then melos bootstrap from the root. Bootstrap installs dependencies for every package and wires path dependencies between workspace members automatically.
Linking packages without manual overrides
Before workspaces, teams hand-edited dependency_overrides or ran brittle scripts. Melos bootstrap reads your workspace graph and ensures design_system resolves to packages/design_system when app depends on it. That means a change in the design system is immediately visible in the app on the next hot reload—no pub publish to a private registry just to iterate.
For packages you intend to publish, keep version constraints honest (^1.2.0 on internal deps). Melos version commands bump those constraints when you cut a release.
Scripting CI-friendly workflows
The real payoff is melos exec and melos run. Filters narrow scope:
melos exec --scope=app -- flutter build apk
melos exec --depends-on=design_system -- flutter test
In GitHub Actions, a minimal pipeline:
- run: dart pub global activate melos
- run: melos bootstrap
- run: melos run analyze
- run: melos run test
One job, every package. Failed tests report which package broke, not a mystery stack trace three directories deep.
Versioning and changelogs
When internal packages ship to pub.dev or a private registry, Melos can bump versions across dependents:
melos version --all
It applies conventional commit messages to decide semver bumps, updates inter-package constraints, and writes CHANGELOG entries. That beats manually grep-ing for "design_system": in six pubspec files.
Gotchas worth knowing early
Circular dependencies. Melos will bootstrap them if pub allows it, but your architecture should not. Keep shared code in leaf packages, apps at the top.
Flutter vs pure Dart packages. Mixing is fine; use melos exec -- flutter test only in Flutter packages or filter with --flutter.
IDE analysis. Open the repo root, not individual packages, so the analyzer sees the full workspace graph. VS Code and Android Studio both respect Dart workspaces when the root is the project folder.
melos clean. When dependency state gets weird after a major refactor, melos clean followed by melos bootstrap resets symlinks and .dart_tool directories cleanly.
When Melos is overkill
A single app with feature folders does not need a monorepo tool. Neither does one app plus one tiny plugin unless you plan to reuse that plugin elsewhere. Melos earns its keep when package count, shared release cadence, or CI matrix complexity crosses the threshold where manual path overrides become a weekly time sink.
Publishing packages from a Melos workspace
When design_system ships to pub.dev while app stays private, Melos version coordinates bumps:
melos version --scope=design_system --all-versions
Tag releases in Git matching package versions. CI job on tag:
- run: melos bootstrap
- run: melos exec --scope=design_system -- dart pub publish --force
Private packages set publish_to: none in pubspec. Document which packages are public in root README so contributors do not accidentally publish internal API clients.
Local development ergonomics
Developers run melos bootstrap after every pull that touches pubspec locks—add a Makefile target or git hook reminder. VS Code multi-root workspace pointing at packages/* improves jump-to-definition across packages when root workspace analysis lags.
For hot reload across package boundaries: editing design_system triggers hot reload in dependent app when path dependency is wired—if not, run melos exec --scope=app -- flutter pub get once after bootstrap.
Scaling to dozens of packages
Filter scripts prevent noise:
scripts:
test:changed:
run: melos exec --since=origin/main -- flutter test
Use --depends-on and --dependents-on to rebuild only the graph affected by a PR. At 30+ packages, naive full-matrix CI exceeds ten minutes—partition by platform (--flutter vs pure Dart) and cache .dart_tool per package hash in CI.
Conflict resolution in multi-package PRs
When PR touches api_client and app, CI must test dependents:
melos exec --dependents-on=api_client -- flutter test
Reviewers verify version constraints bumped if public API changed.
Onboarding documentation
Root README sections: Prerequisites, bootstrap, common scripts, package map diagram. New hire first task: run example app in packages/app successfully within day one.
Lockfile strategy
Commit pubspec.lock in apps; packages library may omit lock per team policy—document in CONTRIBUTING. Melos bootstrap respects locks per package.
Selective publishing
melos publish dry-run in CI on tag—human approves publish job manually for public packages.
Workspace resolver errors
When pub cannot resolve graph, melos clean then bootstrap—document troubleshooting in FAQ for common version conflict messages.
Resources
- Melos documentation
- Melos on pub.dev
- Dart pub workspaces
- Conventional Commits specification
- Flutter monorepo CI example (Invertase)
Frequently asked questions
When should I use Melos instead of a single Flutter app?
Reach for Melos when you have two or more publishable or reusable packages—shared UI kits, API clients, feature modules—that need to stay in sync without copy-pasting code. A lone app with a lib/ folder does not need it. The tipping point is usually the second package that must version and test together with the first.
Does Melos replace pub workspaces?
No. Melos orchestrates scripts, versioning, and changelogs on top of Dart's native workspace support in pubspec.yaml. You still declare a workspace: block listing package paths; Melos adds bootstrap, exec, and publish automation that raw pub workspaces do not provide.
How do I run tests across all packages at once?
Define a melos script like test: melos exec -- flutter test, then run melos run test. Melos fans out the command to every package that matches your filter, failing the whole run if any package fails—exactly what CI needs.
Hiring a senior Android / Flutter engineer?
I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.
Get in touch →