skip to content

In a Flutter or Dart pubspec.yaml, what decides whether a package belongs under `dependencies` or `dev_dependencies`, and what changes for your package's consumers?

level: juniorimportance: must knowfreq 60%

answer

  1. who imports it: lib or test
  2. flutter_test and flutter_lints
  3. consumers ignore your dev deps
  4. smaller graph, easier solve
  5. dart pub add dev:

basics

~20 s

Anything imported from lib/ or bin/ must be a regular dependency; packages used only by tests, examples or tooling go in dev_dependencies. Pub installs your own dev dependencies but ignores the dev dependencies of every package you depend on.

solid answer

~40 s

The rule on dart.dev is about **who imports the package**: if anything in `lib/` or `bin/` imports it, it is a regular `dependencies` entry; if only `test/`, `example/` or tools use it, it belongs in `dev_dependencies`. A new Flutter app shows the split: `flutter` and `cupertino_icons` under `dependencies`, `flutter_test` (`sdk: flutter`) and `flutter_lints: ^6.0.0` under `dev_dependencies`. The consequence is for consumers: pub fetches your own dev dependencies, but **ignores the dev dependencies of packages you depend on**, which keeps graphs smaller and solving easier. Putting a runtime import in `dev_dependencies` therefore works in your own tests and breaks for anyone who depends on you. `dart pub add dev:mocktail` adds to the right section.

code

yaml · 20 lines
yaml
name: weather_app
publish_to: 'none'
version: 1.0.0+1

environment:
  sdk: ^3.13.0

dependencies:
  flutter:
    sdk: flutter
  http: ^1.6.0
  json_annotation: ^4.12.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0
  build_runner: ^2.16.0
  json_serializable: ^6.14.0
  mocktail: ^1.0.4

go deeper

for a junior

Remember the import rule: imported from lib or bin means dependencies; test, example and tooling only means dev_dependencies.

for a middle

Explain that pub ignores dev dependencies of dependencies, and why a misplaced runtime import passes locally but breaks consumers.

for a senior

Catch the generator-versus-annotation split in reviews and use depend_on_referenced_packages so misplacements fail in CI.

for a principal

Set a policy on dependency sections and constraint width for shared packages, so internal consumers resolve with minimal graphs.

## Two kinds of dependency A **dependency** in pub is another package your package needs. The `pubspec.yaml` has two lists of **immediate** dependencies (pub handles transitive ones): - `dependencies`: packages your shipped code needs at run time or compile time; - `dev_dependencies`: packages you need only while developing: test frameworks, mocking libraries, lint sets, code generators. ## The rule: who imports it dart.dev states the rule in one line: if a package is imported from something in your **`lib` or `bin`** directories, it must be a regular dependency. If it is imported only from `test`, `example`, `tool` and similar, it **can and should** be a dev dependency. | Package | Typical section | Why | |---|---|---| | `flutter` (`sdk: flutter`) | `dependencies` | every widget in `lib/` imports it | | `http` or `dio` | `dependencies` | the app's API client lives in `lib/` | | `flutter_test` (`sdk: flutter`) | `dev_dependencies` | only widget tests import it | | `flutter_lints` | `dev_dependencies` | used by the analyzer, never imported by `lib/` | | `mocktail`, `build_runner` | `dev_dependencies` | tests and generators only | | `json_annotation` | `dependencies` | its annotations are imported by model files in `lib/` | The last two rows are the classic trap: code generation needs the generator as a dev dependency but the **annotation package** as a regular one, because `lib/` imports it. ## What changes for consumers Pub gets every package you depend on, everything *those* depend on, and **your own** dev dependencies. It **ignores the dev dependencies of every other package** in the graph. dart.dev's example: if `transmogrify` lists `test` as a dev dependency, a package that depends on `transmogrify` gets `transmogrify` but not `test`. That has two effects: 1. **Smaller graphs and easier solving.** Fewer constraints means pub runs faster and is more likely to find a set of versions that satisfies everyone. 2. **Misplaced runtime imports break consumers.** If `lib/` imports a package you listed under `dev_dependencies`, your own tests pass because pub fetched it for you, but a consumer resolving your package never gets it, and their build fails. The `depend_on_referenced_packages` lint in `package:lints`' core set reports imports of packages the pubspec does not list. For an application that nobody depends on, the split still matters: it documents intent, keeps what a release build depends on explicit, and lets pub's graph for the app stay honest. ## Adding with the CLI `dart pub add` edits the pubspec and runs `dart pub get`: - `dart pub add http` adds a regular dependency with a caret constraint on the latest compatible stable version; - `dart pub add dev:mocktail` adds a dev dependency (the older `--dev` flag did the same); - `dart pub add json_annotation dev:build_runner` adds both in one command. Since **Dart 3.12**, `dart pub add` also accepts `@` in place of `:` between a package name and its constraint. ## Tighten dev constraints dart.dev recommends a slightly different policy per section: keep `dependencies` constraints as wide as your code genuinely supports, and **tighten `dev_dependencies`** to a lower bound of the latest version you use, since no consumer ever resolves them.

  • A Dart package lists `collection` under `dev_dependencies` but imports it from `lib/src/sort.dart`. Its own tests pass. What goes wrong, and for whom?
    Nothing for the author, because pub fetches the root package's dev dependencies. A consumer resolving the package never receives `collection`, because dev dependencies of dependencies are ignored, so the import fails in their build. Move it to `dependencies`.
  • In a Flutter app, why is `json_annotation` a regular dependency while `json_serializable` is a dev dependency?
    Model files in `lib/` import `json_annotation` for `@JsonSerializable`, so it must be available wherever the package is resolved. `json_serializable` is a generator that runs at development time and is never imported by `lib/`, so it belongs under `dev_dependencies`.

saying these in an interview costs you the question

  • Consumers also download a package's dev_dependencies
  • Dev dependencies are stripped only from release builds
  • A code generator's annotation package belongs in dev_dependencies
  • It only matters for published packages, never for apps
  • flutter_test belongs under dependencies because tests ship with the app