> For the complete documentation index, see [llms.txt](https://wong-coupon.gitbook.io/flutter/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wong-coupon.gitbook.io/flutter/my-flutter/architecture-state/dart-workspace-melos.md).

# Dart Workspace and Melos

Use Dart Workspace for consistent dependency resolution and Melos to orchestrate commands in a Flutter monorepo

## Result

After migrating to Dart Workspace, the packages in my project share one dependency resolution, one `pubspec.lock`, and one `.dart_tool/package_config.json`. I use Melos to run analysis, tests, and code generation from the root, so local development and CI no longer maintain different command loops.

The result looks like this:

```
                     Flutter monorepo
                            │
             ┌──────────────┴──────────────┐
             │                             │
      Dart Workspace                    Melos
             │                             │
   ┌─────────┴──────────┐        ┌─────────┴─────────┐
   │                    │        │                   │
Dependency         Shared lockfile     Analyze / Test
resolution         package config      Generate / CI
```

Dart Workspace provides the shared dependency foundation, while Melos uses that foundation to orchestrate workflows.

This is a relatively new Dart Pub feature, not a Flutter-specific feature. Pub Workspace has been available since Dart 3.6, while workspace glob syntax such as `packages/*` requires Dart 3.11 or later.

## Problem

When my project started to contain more packages, creating directories was not the difficult part. I had to run the same commands repeatedly, check dependency versions, and remember which package needed code generation first.

If every package resolves dependencies independently, a repository can end up with:

* Multiple `pubspec.lock` and `package_config.json` files.
* Different versions of the same dependency across packages.
* Multiple analysis contexts when the root project is opened in an IDE.
* Scripts that enter every directory to run `pub get`, analysis, or tests.
* Code generation running in the wrong order when one package depends on another package's output.

A shell loop can reduce the number of commands I type, but it does not understand the dependency graph. Every time a package is added, renamed, or removed, that loop must also be updated manually.

This article assumes that you already have multiple packages in one repository. If you want to understand why I split code into packages and how I organize them, I covered that in the Package article and will not repeat it here.

{% content-ref url="/pages/QMkTqQbLXYMSzWOrpNtw" %}
[Package](/flutter/my-flutter/architecture-state/package.md)
{% endcontent-ref %}

## Solution

### Dart Workspace and Melos Have Different Responsibilities

Dart Workspace and Melos solve different parts of the problem:

| Tool           | Main responsibility                                                                                         |
| -------------- | ----------------------------------------------------------------------------------------------------------- |
| Dart Workspace | Defines workspace members, resolves dependencies, and creates the shared lockfile and package configuration |
| Melos          | Runs commands, filters packages, controls concurrency, and organizes workflows                              |
| Make           | Provides short entry points for developers and CI when needed                                               |
| FVM            | Pins the Flutter and Dart SDK before workspace commands run                                                 |

Melos is not required to use Dart Workspace. If you only need shared dependency resolution, Dart Pub already handles that part.

### Prerequisites

Check the Dart version:

```bash
dart --version
```

The main example in this article uses Dart 3.11 or later so that it can declare `packages/*`. A project using Dart 3.6 up to, but not including, Dart 3.11 can still use Workspace, but every package path must be listed explicitly.

In a team, SDK selection should not depend on every developer remembering the correct version. I normally use FVM to pin Flutter before running any workspace command.

{% content-ref url="/pages/757ESPKGR9SwyJPqwpIM" %}
[FVM](/flutter/my-flutter/foundations/fvm.md)
{% endcontent-ref %}

Example structure:

```
example_app/
├── lib/
├── packages/
│   ├── config/
│   │   └── pubspec.yaml
│   ├── models/
│   │   └── pubspec.yaml
│   ├── services/
│   │   └── pubspec.yaml
│   └── store/
│       └── pubspec.yaml
└── pubspec.yaml
```

These names are intentionally generic. You can replace them with the packages that already exist in your project.

### Step 1: Declare the Workspace at the Root

Add the SDK constraint and package list to the root `pubspec.yaml`:

```yaml
name: example_app
publish_to: none

environment:
  sdk: ">=3.11.0 <4.0.0"

workspace:
  - packages/*
```

With Dart 3.11 or later, the `packages/*` glob finds direct child directories that contain a `pubspec.yaml`. Adding another package directly under `packages/` does not require another path in the root file.

If the repository cannot upgrade to Dart 3.11, list each package explicitly:

```yaml
workspace:
  - packages/config
  - packages/models
  - packages/services
  - packages/store
```

### Step 2: Use Workspace Resolution in Every Package

Add `resolution: workspace` to every workspace member:

```yaml
name: example_services
publish_to: none

environment:
  sdk: ">=3.11.0 <4.0.0"

resolution: workspace

dependencies:
  example_models: ^1.0.0
```

If `example_models` is another workspace member and its local version satisfies the constraint, Dart Pub resolves the local package.

Every workspace member must have an SDK constraint that supports Workspace. A single package with an SDK minimum that is too low can make `pub get` fail.

### Step 3: Resolve Dependencies Once

Run the command at the workspace root:

```bash
dart pub get
```

The repository now uses:

```
example_app/
├── .dart_tool/
│   └── package_config.json
├── packages/
│   ├── config/
│   ├── models/
│   ├── services/
│   └── store/
├── pubspec.lock
└── pubspec.yaml
```

There is no reason to run `pub get` sequentially in every workspace package by default. Dart Pub also removes old lockfiles and package configurations next to workspace members so that they cannot override the root configuration.

### Step 4: Verify Workspace Membership

Run:

```bash
dart pub workspace list
```

The expected output contains the root app and every workspace member:

```
Package           Path
example_app       ./
example_config    packages/config/
example_models    packages/models/
example_services  packages/services/
example_store     packages/store/
```

I run this command immediately after adding or moving a package. It catches missing workspace membership before analysis or builds fail later.

### Step 5: Add Melos

Add Melos to the root `dev_dependencies`:

```yaml
dev_dependencies:
  melos: ^7.4.0
```

Then add the configuration:

```yaml
melos:
  useRootAsPackage: true
  scripts:
    analyze:
      exec: dart analyze .

    test:
      exec: flutter test
      packageFilters:
        flutter: true
        dirExists: test
```

In this example:

* `useRootAsPackage: true` includes the Flutter app at the root in Melos operations.
* `analyze` runs the Dart analyzer in the packages.
* `test` runs only in Flutter packages that contain a `test` directory.
* `packageFilters` prevents `flutter test` from running where it does not apply.

Check that Melos sees the packages:

```bash
dart run melos list
```

Run scripts without displaying the package selection prompt:

```bash
dart run melos run analyze --no-select
dart run melos run test --no-select
```

After each command, Melos reports which packages ran and whether they passed or failed. This command orchestration is the layer that Melos adds on top of Dart Workspace dependency resolution.

### Step 6: Run Code Generation in the Correct Order

Analysis and tests can often run independently. Code generation is different when the output of one package becomes the input of another.

For example:

```
config
   │
   ▼
models
   │
   ▼
services
   │
   ▼
store
   │
   ▼
root app
```

For this flow, I use a sequential script:

```yaml
melos:
  scripts:
    generate:
      run: |
        (cd packages/config && dart run build_runner build --delete-conflicting-outputs) && \
        (cd packages/models && dart run build_runner build --delete-conflicting-outputs) && \
        (cd packages/services && dart run build_runner build --delete-conflicting-outputs) && \
        (cd packages/store && dart run build_runner build --delete-conflicting-outputs) && \
        dart run build_runner build --delete-conflicting-outputs
```

Run it with:

```bash
dart run melos run generate
```

The `&&` operators preserve the order and stop the workflow as soon as one step fails. Do not increase concurrency only because Melos supports parallel execution. First confirm that the packages do not depend on each other's generated output.

Melos also provides `orderDependents` to order `exec` operations by dependency graph. For a complex workflow, it can replace a fixed list. I still start with an explicit order when I need to see which package owns each generation step.

### Step 7: Use Make as a Short Entry Point

After Workspace and Melos own the main logic, the Makefile only needs to expose commands that are easy to remember:

```makefile
DART ?= fvm dart
MELOS ?= $(DART) run melos

.PHONY: workspace-list analyze test generate

workspace-list:
	$(DART) pub workspace list

analyze:
	$(MELOS) run analyze --no-select

test:
	$(MELOS) run test --no-select

generate:
	$(MELOS) run generate
```

Developers and CI can use the same entry points:

```bash
make analyze
make test
make generate
```

Make does not replace Dart Workspace or Melos. It only wraps the existing commands with short and stable names. I covered the full Makefile organization in the Make article, so I will not repeat it here.

{% content-ref url="/pages/PG14RfyD6nuNYL68vG7H" %}
[Make](/flutter/my-flutter/foundations/make.md)
{% endcontent-ref %}

### Applying This to an Older Package Repository

I applied this approach to a `packages` repository nested inside an older Flutter app. The `packages` directory is also an independent Git repository, so its workspace root lives inside that directory instead of at the app root:

```
legacy_app/
├── pubspec.yaml
└── packages/                 # Independently managed Git repository
    ├── pubspec.yaml          # Dart Workspace root
    ├── pubspec.lock          # Shared lockfile
    ├── ancestor_cores/
    ├── vendor/
    └── ...                   # 15 workspace packages in total
```

The project uses Flutter 3.27.4 and Dart 3.6.2, so I cannot use the `packages/*` glob. The root `pubspec.yaml` explicitly lists all 15 paths, and every member declares `resolution: workspace`.

Melos 7 stable requires a newer Dart version. Upgrading Flutter in an old app only to get a newer Melos version could introduce unrelated migration work, so I kept the existing SDK and pinned Melos to `7.0.0-dev.8`, the last prerelease in that line that supports Dart 3.6.

The migration exposed several problems that had previously been hidden by separate package lockfiles:

* The packages used `flutter_lints` 3, 4, and 5, which could not resolve together. I aligned them to `^5.0.0`.
* A Flutter package that uses Material needs `uses-material-design: true` in the primary pubspec during tests. Because Melos runs commands inside each package, this setting must be consistent across members.
* Six packages had a `test` directory, but their files were only placeholders. Filtering with `dirExists: test` still produced `No tests were found`, so those packages are excluded until they contain real test cases.
* One Git dependency contained a credential directly in its URL. The credential had to be removed from the configuration and revoked even though the repository remains private.

After resolving those issues, I verified that:

1. `dart pub workspace list` detects the workspace root and all 15 packages.
2. `dart run melos list` detects the same 15 packages.
3. Analysis succeeds in 15 out of 15 packages.
4. Tests succeed in 7 out of 7 packages that currently contain executable test cases.
5. The older app still resolves path dependencies when running `flutter pub get --dry-run`.

The important lesson is not to force an old project to use the latest tool versions. I kept the SDK that was already working, selected a compatible Melos version, and let Workspace expose real dependency conflicts early. The real repository remains private, so this article only uses an anonymized structure and the minimum configuration needed to reproduce the approach.

### Verify the Result

After configuration, I verify the repository in this order:

1. `dart pub workspace list` shows the root app and every workspace member.
2. Only one `pubspec.lock` and one `.dart_tool/package_config.json` remain at the root.
3. `dart run melos list` shows the same group of packages that must be orchestrated.
4. `dart run melos run analyze --no-select` succeeds across the workspace.
5. `dart run melos run test --no-select` skips packages that do not have tests.
6. The generation script stops at the first failing package and does not run dependent steps afterward.

I do not use "fewer commands" as the only proof that the migration is complete. Workspace is ready only when dependency resolution, package discovery, and workflows produce repeatable results locally and in CI.

### Common Errors and Trade-offs

#### Missing `resolution: workspace`

**Symptom:** `pub get` reports that a package is not configured correctly for the workspace.

**Fix:** Check every `pubspec.yaml` listed in `workspace` and add:

```yaml
resolution: workspace
```

#### A `pubspec.yaml` Is Not Part of the Workspace

**Symptom:** Pub reports that a pubspec exists between the root and a workspace member but is not declared in the workspace.

**Fix:** Add that package to the workspace, move it outside the workspace tree, or remove the pubspec if it is no longer used.

#### Conflicting Dependency Constraints

**Symptom:** Two packages work when resolved separately but cannot resolve together inside the workspace.

This is not necessarily a disadvantage. One dependency resolution forces the repository to address conflicts when they appear instead of waiting until the packages are integrated into an app.

#### Running Every Command in Every Package

**Symptom:** `flutter test` or code generation fails in a package that does not have tests or the required dependency.

**Fix:** Use `packageFilters` such as `flutter`, `dirExists`, or `dependsOn` to select the correct packages.

`dirExists: test` only confirms that the directory exists. If it contains placeholder files without test cases, Flutter still reports `No tests were found`. Add real tests or temporarily exclude that package from the test script.

#### Using Workspace for a Project That Is Too Small

If a project contains only one app and no internal packages, Melos can add configuration without providing much value. I use this model only when manual commands, dependency conflicts, or CI workflows become a real problem.

### Verified Versions

* Main example: Flutter 3.41.2, Dart from 3.11 up to but not including 4.0, Melos constraint `^7.4.0`, and lockfile resolution `7.5.0`.
* Older package repository: Flutter 3.27.4, Dart 3.6.2, and Melos `7.0.0-dev.8`.
* Platform: Shared.
* Verification date: August 22, 2026.

### References

* [Dart — Pub workspaces](https://dart.dev/tools/pub/workspaces)
* [Melos — Bootstrap](https://melos.invertase.dev/commands/bootstrap)
* [Melos — Exec](https://melos.invertase.dev/commands/exec)
* [Melos — Migrations](https://melos.invertase.dev/guides/migrations)
* [Melos — Workspace scripts](https://melos.invertase.dev/configuration/scripts)
* [Melos — Filtering packages](https://melos.invertase.dev/filters)
* [pub.dev — Melos versions](https://pub.dev/packages/melos/versions)

## Conclusion

Dart Workspace manages dependencies between packages, while Melos manages how workflows run across the repository. Combined with FVM and Make, local development and CI can start from the same SDK and the same commands while each tool keeps a clear responsibility.

This approach fits packages that are developed and released together with one app. If a project has only one package, or its packages use completely different SDKs and release cycles, you do not need to force them into the same workspace yet.

[Buy Me a Coffee](https://buymeacoffee.com/ducmng12g) | [Support Me on Ko-fi](https://ko-fi.com/I2I81AEJG8)
