Packages, pubspec and project layout

Write a pubspec, manage version constraints and lock files, structure a package, and know what publishing requires.

The pubspec

name: shop_core
description: Domain models and pricing rules for the shop.
version: 0.3.0
repository: https://github.com/example/shop_core
publish_to: none            # remove this line to publish to pub.dev

environment:
  sdk: ^3.5.0               # a caret constraint: 3.5.0 up to but not including 4.0.0

dependencies:
  collection: ^1.19.0
  http: ^1.2.0
  meta: ^1.15.0

dev_dependencies:
  lints: ^5.0.0
  test: ^1.25.0
  build_runner: ^2.4.0

dependency_overrides:
  # temporary: force one version while waiting for an upstream fix
  collection: 1.19.0
dart pub get                  # resolve and write pubspec.lock
dart pub upgrade              # move to the newest allowed versions
dart pub upgrade --major-versions
dart pub outdated             # what is behind, and whether it is safe to move
dart pub add http             # edit the pubspec and resolve
dart pub deps --style=compact
dart pub publish --dry-run    # validate before publishing
dart pub cache repair         # fix a corrupted package cache
ConstraintMeansUse for
^1.2.3>=1.2.3 <2.0.0The default for a library
>=1.2.3 <2.0.0Explicit rangeWhen a caret is not what you mean
anyNo constraintAlmost never; it breaks resolution
1.2.3Exactly that versionA temporary override
git: blockA git dependencyAn unreleased fix, pinned to a ref
path: blockA local directoryA monorepo or local development
  • Commit pubspec.lock for an application so every machine and CI run resolves the same versions. Do not commit it for a library, where consumers must be free to resolve their own graph.
  • A caret constraint on a 0.x version treats the minor as breaking: ^0.3.0 means >=0.3.0 <0.4.0, not <1.0.0.
  • dependency_overrides changes resolution for the whole package graph and should be temporary. It hides the conflict rather than fixing it.
  • Prefer a git dependency pinned to a commit hash over a branch. A branch moves and your build becomes non-reproducible.

Layout and visibility

shop_core/
  pubspec.yaml
  analysis_options.yaml
  README.md
  CHANGELOG.md
  lib/
    shop_core.dart          # the public API: exports only what consumers need
    src/                    # everything else is private by convention
      money.dart
      pricing.dart
      internal/
        rounding.dart
  test/
    money_test.dart
    pricing_test.dart
  example/
    main.dart               # compiled by pub.dev and by the analyzer
  tool/
    generate.dart           # scripts, not part of the published package
// lib/shop_core.dart — the public surface
/// Pricing rules and domain models for the shop.
library;

export 'src/money.dart' show Money;
export 'src/pricing.dart' show PricingRule, applyRules;
// src/internal/rounding.dart is deliberately not exported.

// Because everything outside lib/src is importable by convention,
// dart's linter warns when a consumer imports package:shop_core/src/...
// The src/ directory is the community agreement for "private to this package".
# analysis_options.yaml — the strongest cheap quality gate
include: package:lints/recommended.yaml

analyzer:
  language:
    strict-casts: true
    strict-inference: true
    strict-raw-types: true
  errors:
    invalid_annotation_target: ignore
    todo: ignore

linter:
  rules:
    - prefer_final_locals
    - avoid_print
    - unawaited_futures
    - always_declare_return_types

Publishing rules

  • A published version is immutable. Increasing the version and publishing again is the only way to fix a mistake, so run --dry-run every time.
  • Follow semantic versioning. A breaking change in a 1.x library means 2.0.0, and consumers need a changelog entry that names the change.
  • Every public declaration needs a doc comment, and every package needs a README.md and a CHANGELOG.md with an entry for the version being published. The publisher rejects a package without them.
  • Keep the dependency list small. Each dependency is a constraint you impose on every consumer's resolution.
  • Do not publish secrets or generated files. Check .pubignore or .gitignore and read the file list in the dry run output.
💡
Run dart pub publish --dry-run in CI. It validates the layout, the version constraint, the changelog and the analysis, and it catches the missing doc comment that would otherwise fail at the moment you are trying to ship a release.

FAQ

Why does resolution fail with a version conflict?
Two packages in the graph need incompatible versions of a third. Run dart pub deps and dart pub outdated to see who constrains what, then widen your own constraint or upgrade the package that is behind. Reach for dependency_overrides last.
Should I use build_runner?
Only when the generated code earns its keep: JSON serialisation for many models, a database layer, or code generation for a routing table. It adds a build step, a cache to clean and a version to keep in step with the source.

Testing Dart code Command-line tools: args, stdin and exit codes

Last refreshed 2026-09-18.