A Flutter monorepo with Melos
One repo, multiple packages
packages/ , each with its own pubspec.yaml . The app and the modules reference each other via path dependency ( path: ../core_api ), and Melos resolves these links locally instead of fetching the versions published on pub.dev.
feature_auth depends on core_api , the reverse doesn't compile. The dependency graph is written in black and white in the pubspec.yaml files, and a forbidden dependency shows up immediately: it doesn't resolve. A single repository keeps these boundaries intact and simply makes crossing them easier when it's legitimate.
The melos.yaml file
melos.yaml at the root (recent versions also accept a melos: key in the root pubspec.yaml ). It declares the workspace packages and named scripts, reusable both locally and in CI.
exec: form launches its command in each selected package, and packageFilters narrows the set before execution. Here flutter: true excludes pure Dart packages, and dirExists: test skips those without a test folder, which avoids a noisy failure on a package with no test suite. The command: section configures the built-in commands; under version , workspaceChangelog adds an aggregated changelog at the root in addition to each package's own.
melos run analyze or, with no argument, by a melos run that offers the list of scripts as a keyboard prompt. The melos exec command remains the escape hatch for anything that doesn't have a dedicated script.
Bootstrap and local linking
melos bootstrap (shortened melos bs ) is the first command run after a clone. It fetches the dependencies of all packages at once and wires up the path dependencies between them, without having to chain flutter pub get package by package.
pubspec_overrides.yaml in each package that points the internal dependencies to their local folder:
pub reads it as an overlay on top of pubspec.yaml and prefers the local version indicated. It's left out of version control, and bootstrap is rerun after each modification of a pubspec.yaml . On recent Dart versions, Melos can also rely on pub 's native workspaces ( resolution: workspace ) to get the same local resolution.
post hook in the configuration above fires at the end of bootstrap . It's the natural place for code generation, very common in a Flutter project with freezed , json_serializable , or a provider generator: the clone is ready to compile from the first command, with no manual step forgotten. The dependsOn: build_runner filter limits generation to the packages that actually need it.
Targeting a subset: filters
melos exec , which runs an arbitrary command on part of the graph. Filters combine:
--diff compares the working tree to a git reference and keeps only the touched packages. --scope and --ignore filter by name with globs. --depends-on and --include-dependents follow the graph's edges to also catch packages upstream or downstream of a change. In CI, --diff="origin/main" is the lever that avoids replaying the analysis and tests of the whole repository on every push: only the modified subgraph runs, and the rest keeps its previous result.
Coordinated versioning
melos version translates conventional commits into version changes. It reads the git history since each package's last tag, deduces the bump ( fix: as patch, feat: as minor, a BREAKING CHANGE: as major), updates the version: field of the relevant pubspec.yaml , and writes its CHANGELOG.md . The tag it sets bears the package's name, for example core_api-v1.4.0 .
fix(core_api): ... bumps core_api , but also the packages that depend on it: Melos adjusts their version constraint and applies an increment to them. The whole stays consistent, without a package referencing a version of a neighbor that doesn't exist yet. The scope in parentheses ( core_api ) ties the commit to the right package when a single change touches several.
melos publish pushes the publishable packages to pub.dev. The command runs dry by default and only acts for real with --no-dry-run , and it automatically skips packages marked publish_to: none , such as the Flutter app itself.
Chaining it in CI
melos bootstrap installs and links, then melos run analyze and melos run test run on the modified packages via --diff , and the release branch adds melos version then melos publish . Each step reuses the same script definitions as developers use locally, which avoids drift between CI and the workstation: what passes on a machine passes in the pipeline, with the same command.
A monorepo is judged by the cost of a cross-cutting change. With Melos, touching a shared package and its consumers fits in a single branch to review and merge, whereas separate repositories would impose a queue of publications to order by hand.