← Articles

Managing a Flutter monorepo with Melos

By John · 4 March 2026

A Flutter monorepo puts multiple packages and apps in one Git repository. The benefit: a single PR can span shared library code and the app that uses it, tests run together, and versioning is coordinated. Melos is the tool that makes Flutter monorepos manageable.

When a monorepo makes sense

  • Multiple apps sharing significant common code (design system, data layer)
  • Internal packages that shouldn't be published to pub.dev
  • Teams where the same people work on both the library and the app
  • Avoiding the "publish + update pubspec + PR" cycle for every internal change

Project structure

my_workspace/
├── melos.yaml
├── apps/
│   ├── customer_app/
│   └── admin_app/
├── packages/
│   ├── core/          # Business logic, models, repositories
│   ├── ui/            # Design system, shared widgets
│   ├── api/           # HTTP client, DTOs
│   └── analytics/     # Analytics abstraction
└── tools/
    └── scripts/

melos.yaml

name: my_workspace

packages:
  - apps/**
  - packages/**

command:
  bootstrap:
    # Use local paths for workspace packages
    usePubspecOverrides: true

scripts:
  test:all:
    run: melos exec -- flutter test
    description: Run tests in all packages
    packageFilters:
      dirExists: test

  test:coverage:
    run: melos exec -- flutter test --coverage
    packageFilters:
      dirExists: test

  analyze:
    run: melos exec -- flutter analyze
    description: Analyze all packages

  gen:
    run: melos exec -- dart run build_runner build --delete-conflicting-outputs
    description: Run code generation in all packages
    packageFilters:
      dependsOn: build_runner

  clean:
    run: melos exec -- flutter clean
    description: Clean all packages

  format:
    run: melos exec -- dart format . --fix

  # Run tests only in changed packages (useful in CI)
  test:changed:
    run: melos exec -- flutter test
    packageFilters:
      dirExists: test
      diff: origin/main

Bootstrap

npm install -g melos  # Or: dart pub global activate melos

melos bootstrap
# Equivalent to: flutter pub get in every package
# Also sets up pubspec_overrides.yaml so packages reference each other locally

Using local packages

With usePubspecOverrides: true, Melos creates pubspec_overrides.yaml in each package automatically. Your pubspec.yaml can reference the published version:

# apps/customer_app/pubspec.yaml
dependencies:
  core: ^1.0.0
  ui: ^1.0.0

And Melos overrides it to use the local path during development:

# apps/customer_app/pubspec_overrides.yaml (generated, gitignored)
dependency_overrides:
  core:
    path: ../../packages/core
  ui:
    path: ../../packages/ui

Running scripts

# Run across all packages
melos run test:all

# Run in specific package
melos run test:all --scope=core

# Run in changed packages only
melos run test:changed

# Run in packages that depend on 'core'
melos run test:all --scope='*' --filter='dependsOn:core'

# Exec directly (without a named script)
melos exec -- flutter pub outdated

CI workflow

# .github/workflows/test.yml
name: Test
on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for diff-based filtering

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.0'
          cache: true

      - name: Install Melos
        run: dart pub global activate melos

      - name: Bootstrap
        run: melos bootstrap

      - name: Analyze
        run: melos run analyze

      - name: Test changed packages
        run: melos run test:changed

Versioning with Melos

Melos can manage versions across packages:

# Bump version based on conventional commits
melos version

# Publish all changed packages to pub.dev
melos publish
# melos.yaml
command:
  version:
    # Use conventional commits to determine version bumps
    message: "chore(release): publish packages\n\n{new_package_versions}"
    includeScopes: true
    branch: main
  publish:
    gitTagVersion: true

Workspace-level analysis

Create an analysis_options.yaml at the workspace root and import it in each package:

# analysis_options.yaml (workspace root)
include: package:flutter_lints/flutter.yaml

analyzer:
  errors:
    missing_required_param: error
    dead_code: warning

linter:
  rules:
    prefer_const_constructors: true
    avoid_print: true
    require_trailing_commas: true
# packages/core/analysis_options.yaml
include: ../../analysis_options.yaml

Common pitfalls

Not gitignoring pubspec_overrides.yaml. Add **/pubspec_overrides.yaml to .gitignore. These are local overrides for development — committing them breaks CI which expects published versions.

Circular dependencies. If ui depends on core and core depends on ui, Melos can't bootstrap. Keep dependency direction one-way: appsuicoreapi.

Missing fetch-depth: 0 in CI. Melos diff-based filtering compares against origin/main. Without full git history, every package looks changed. Always checkout with fetch-depth: 0 when using diff filters.

Running flutter pub get instead of melos bootstrap. In a Melos workspace, melos bootstrap sets up the pubspec overrides. Running flutter pub get directly in a package skips this step.

Sign in to like, dislike, or report.

Managing a Flutter monorepo with Melos — ANN Tech