Packages and pub.dev

pubspec.yaml dependencies, semantic versioning with ^, pubspec.lock, and publishing a package.

pubspec.yaml — a Dart project's manifest

Every Dart (and Flutter) project is described by a pubspec.yaml file at its root — the direct equivalent of package.json in Node, Cargo.toml in Rust, or a Gemfile in Ruby:

YAML
name: my_app
description: A sample application.
version: 1.0.0
environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  http: ^1.2.0
  provider: ^6.1.0

dev_dependencies:
  test: ^1.25.0
  lints: ^3.0.0

dependencies are compiled into the shipped app or package. dev_dependencies are only needed while developing — testing libraries, linters, code generators — and are never bundled into a release build. The environment.sdk constraint isn't a dependency at all; it declares which Dart SDK versions the project itself supports, and pub refuses to resolve dependencies for an SDK version outside that range.

Adding a dependency

Bash
dart pub add http
dart pub add test --dev     # adds to dev_dependencies instead
dart pub get                 # resolves and downloads everything listed in pubspec.yaml

dart pub add edits pubspec.yaml for you, picking a sensible version constraint automatically — the modern equivalent of typing a dependency line by hand and hoping the version syntax is right.

Semantic versioning and the caret operator

Constraint Meaning
^1.2.3 >=1.2.3 <2.0.0 — compatible updates only (the default, and by far the most common)
>=1.2.3 <2.0.0 The same range, written out explicitly
1.2.3 (no operator) Exactly this version, nothing else
any Any version at all (rare, and discouraged)

The caret (^) constraint trusts a package's author to follow semantic versioning — any later version sharing the same major version number is assumed to be backward-compatible, so bug fixes and additive features can be picked up automatically, while a breaking change (a major version bump) requires deliberately updating the constraint yourself.

pubspec.lock

Just as pubspec.yaml states acceptable ranges, pubspec.lock (generated automatically by dart pub get) records the exact version of every dependency, direct and transitive, that was actually resolved — the same role Cargo.lock plays for Rust and Gemfile.lock plays for Ruby. The common convention: commit pubspec.lock for an application (so every developer and CI run builds against identical versions), but it's typically not committed for a published package, since the package's own consumers will resolve their own compatible versions within your declared constraints anyway.

Bash
dart pub upgrade            # re-resolve every dependency to the newest version still allowed
dart pub upgrade http        # re-resolve just one dependency
dart pub outdated             # show which dependencies have newer versions available

pub.dev is Dart's central package registry — the default source pub fetches from, playing the same role crates.io plays for Rust or RubyGems.org plays for Ruby. A handful of packages are close to a de facto standard extension of what the language ships with:

  • http — a straightforward HTTP client for REST APIs.
  • provider / riverpod — popular state-management approaches for Flutter apps.
  • json_serializable / freezed — code generation for JSON (de)serialization and immutable data classes.
  • test — the testing package covered on the previous page.
  • lints / flutter_lints — the standard, officially-recommended static analysis rule sets.

Publishing a package, conceptually

Bash
dart pub publish --dry-run   # validates everything without actually publishing
dart pub publish

Publishing requires, at minimum, a valid pubspec.yaml (name, description, version), a LICENSE file, and reasonably complete documentation — pub.dev computes a visible "pub points" score partly from how complete and well-documented a package is, which real users do check before adopting one. Once a version is published, it's permanent: you can't edit or overwrite 1.0.0 after the fact, only "retract" it in narrow circumstances — the normal path for any change, however small, is publishing a new version number.

Common mistakes

  • Hand-editing pubspec.lock directly instead of running dart pub get/dart pub upgrade — it's a generated file, and manual edits get overwritten or left inconsistent by the next resolution.
  • Adding a package needed only for testing or code generation under dependencies instead of dev_dependencies, unnecessarily growing what actually ships in the compiled app.
  • Using an unconstrained any version (or a very wide range) instead of the default caret constraint, letting a future breaking major version install silently on the next dart pub upgrade.
  • Assuming a published package version can be edited or replaced after the fact — publishing is effectively one-way; fixing a mistake means publishing a new version, not modifying the old one.

Interview questions

Q: What's the difference between dependencies and dev_dependencies in pubspec.yaml? dependencies are required at runtime and are bundled into the shipped app or package. dev_dependencies — testing libraries, linters, code generators — are only needed while developing the package itself and are never included in a release build, keeping the shipped app's dependency footprint smaller.

Q: What does the caret (^) version constraint mean for a Dart package dependency? ^1.2.3 means "any version from 1.2.3 up to, but not including, 2.0.0" — it accepts routine minor and patch updates automatically (trusting the package follows semantic versioning), while refusing a major version bump that could include breaking changes, which would require deliberately widening the constraint yourself.

Q: Why can't you overwrite an already-published version of a package on pub.dev? Countless other projects may already depend on that exact version and expect its contents never to change underneath them — allowing silent edits to a published version would break reproducible builds everywhere it's used. Instead, any change — even a tiny fix — is published as a new version number, and the old version can only be "retracted" in narrow circumstances, never silently altered.