| |

Dart 44 🎯 Package Management with pub.dev: dependencies, dev_dependencies, and Lockfiles

Every Dart project is a package. A package is a directory with a pubspec.yaml file, and the pubspec declares the package’s metadata, its SDK constraints, and its dependencies. The pub tool, which ships with the Dart SDK, reads the pubspec and resolves the dependency graph, downloading the required packages into a central cache and generating a package_config.json file that maps package names to file locations. The packages themselves come from pub.dev, the public package repository operated by the Dart team, or from a custom repository, a local path, or a Git repository.

The pubspec has two dependency sections. dependencies lists the packages that the application needs at runtime. dev_dependencies lists the packages that are needed only during development: test frameworks, linters, build tools, and code generators. The distinction matters because a package’s consumers do not receive its dev_dependencies; they only receive its dependencies. A package that is published to pub.dev should keep its dependencies minimal, because every consumer inherits them, while dev_dependencies can be more liberal.

The pubspec.lock file is the mechanism that makes dependency resolution reproducible. When dart pub get runs, it resolves the version constraints and records the exact version of every direct and transitive dependency in the lockfile. Subsequent runs of dart pub get use the locked versions, so every developer and every build server gets the same set of packages. The recommendation is to commit the lockfile for application packages and not for library packages, because applications need reproducible builds while libraries need to work with a range of dependency versions.

This chapter covers three areas. First, why package management exists — the problem of dependency resolution, the single-version constraint, and the role of the version solver. Second, how the pubspec and lockfile work — the dependencies and dev_dependencies sections, version constraints, and the lockfile’s role. Third, the commands and the workspace feature — pub get, pub upgrade, pub add, pub deps, and the monorepo support via Pub workspaces. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the dependency resolution flow.

Key point: The pubspec.yaml file declares dependencies. dependencies are runtime packages; dev_dependencies are development-only packages. The pubspec.lock file records the exact resolved versions and ensures reproducible builds. Commit the lockfile for applications, not for libraries. The version solver resolves all constraints to a single version of each package, because Dart allows only one version of a package in the compilation tree.


Why package management exists

The dependency-resolution problem. A Dart application depends on packages, and those packages depend on other packages. The result is a dependency tree, and the versions in the tree must be compatible with each other. The version solver’s job is to look at all the version constraints from the direct and transitive dependencies and find a single concrete version for each package that satisfies all of them. This is not a trivial problem: the constraints can conflict, and resolving the conflict may require choosing an older version of one package to satisfy the constraint of another.

The single-version constraint. Unlike some package managers, Dart’s pub allows only a single version of any package in the application’s compilation tree. The reason is type safety: if two versions of a package both define a User class, the Dart compiler treats them as two distinct types, and a User from one version cannot be passed to a method that expects a User from the other. The single-version constraint also keeps the binary size down and avoids the inconsistent state that multiple copies of a package with global variables can cause.

The reproducible-build problem. A version constraint like ^1.2.3 allows a range of versions. If two developers run dart pub get at different times, they may resolve different versions within the range. The application may work on one machine and fail on another. The pubspec.lock file solves this by recording the exact versions that were resolved. Subsequent runs of dart pub get use the locked versions, so the build is reproducible.

The development-versus-runtime problem. A test framework is needed to run the tests, but it is not needed by the application at runtime. A build tool is needed to generate code, but it is not needed by the consumer of the package. The dev_dependencies section separates these from the dependencies section. A package’s consumers receive only its dependencies, so the dev_dependencies do not inflate the consumer’s dependency tree.

The workspace problem. A monorepo contains multiple packages that depend on each other. Without special support, each package would have its own lockfile and its own resolution, and the packages would have to be resolved independently. Pub workspaces allow a repository to use a single shared resolution for all its packages: one lockfile at the root, one package_config.json, and a single resolution that covers every package in the workspace.

The trade-off. The pubspec.lock file makes builds reproducible, but it also freezes the versions until dart pub upgrade is run. The upgrade command ignores the lockfile and resolves the latest versions that satisfy the constraints, which can introduce new bugs or break the build. The trade-off is between the stability of locked versions and the currency of upgraded ones. For applications, the lockfile is committed and the upgrade is a deliberate action. For libraries, the lockfile is not committed, and the package is expected to work with a range of versions.


a. The pubspec and the two dependency sections

The pubspec.yaml file is the package’s manifest. It declares the package name, the description, the SDK constraint, and the dependencies. The two dependency sections are dependencies and dev_dependencies.

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

dependencies:
  http: ^1.0.2
  intl: ^0.20.3
  path: ^1.9.1

dev_dependencies:
  test: ^1.24.0
  lints: ^3.0.0
  build_runner: ^2.4.0

The dependencies section lists the packages that the application needs at runtime. The http package is used to make network requests, the intl package is used for internationalization, and the path package is used for path manipulation. Every consumer of the package inherits these dependencies.

The dev_dependencies section lists the packages that are needed only during development. The test package is the test framework, the lints package provides lint rules, and the build_runner package is the code-generation tool. These packages are not inherited by consumers.

The environment section declares the Dart SDK constraint. The ^3.0.0 syntax means that the package works with Dart 3.0.0 and later versions that are compatible with 3.0.0. The SDK constraint is resolved before the package dependencies, and a package that requires a newer SDK than the one installed cannot be resolved.

A package can also have a dependency_overrides section, which temporarily forces a specific version of a dependency. This is useful during development when a transitive dependency has a bug that is fixed in an unreleased version. The overrides are not inherited by consumers, and they should be removed before publishing.


b. Version constraints and the lockfile

The version constraint after the package name determines which versions the solver can choose. The caret syntax (^) is the most common, and it means “compatible with the specified version.” For stable versions (1.0.0 and higher), ^1.2.3 is equivalent to >=1.2.3 <2.0.0, allowing updates that do not change the major version. For pre-1.0.0 versions, the caret is more restrictive: ^0.8.0 is equivalent to >=0.8.0 <0.9.0, and ^0.0.3 is equivalent to >=0.0.3 <0.0.4.

dependencies:
  http: ^1.0.2        # >=1.0.2 <2.0.0
  url_launcher: '>=5.4.0 <6.0.0'  # explicit range
  some_package: any    # any version (avoid)

The pubspec.lock file records the exact version of every direct and transitive dependency. It is generated by dart pub get and updated when the dependencies are changed. The lockfile is the source of truth for the resolved versions: when dart pub get runs and a lockfile exists, the solver uses the locked versions if they still satisfy the constraints in the pubspec.

# pubspec.lock (generated)
packages:
  http:
    dependency: "direct main"
    description:
      name: http
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"
  path:
    dependency: "direct main"
    description:
      name: path
      url: "https://pub.dev"
    source: hosted
    version: "1.9.1"

The lockfile distinguishes between direct main (a package listed in dependencies), direct dev (a package listed in dev_dependencies), and transitive (a package that a direct dependency depends on). The source field indicates where the package came from: hosted for pub.dev, git for a Git repository, or path for a local path.

The recommendation is to commit the lockfile for application packages and not for library packages. An application needs a reproducible build: every developer and every deployment should use the same versions. A library needs to work with a range of versions, and committing a lockfile would suggest that the library only works with the locked versions.


c. The commands and the workspace feature

The pub commands are the interface to the package manager. The most common are get, upgrade, add, remove, and deps.

dart pub get retrieves the dependencies and generates or updates the lockfile. If a lockfile exists, it uses the locked versions. If a dependency is added to the pubspec, get resolves the new dependency and any of its transitive dependencies without changing the versions of the already-resolved dependencies unless necessary.

dart pub upgrade retrieves the latest versions of all dependencies, ignoring the lockfile. It resolves the newest versions that satisfy the constraints in the pubspec and writes a new lockfile. This is the command to use when updating dependencies deliberately.

dart pub get
dart pub upgrade
dart pub upgrade http          # upgrade only the http package
dart pub add http              # add a dependency and run get
dart pub remove http           # remove a dependency and run get
dart pub deps                  # show the dependency tree
dart pub outdated              # show updatable dependencies

Pub workspaces allow a monorepo to use a single shared resolution. The root pubspec.yaml declares a workspace entry that lists the paths to the packages in the repository. Each package’s pubspec.yaml adds a resolution: workspace entry. Running dart pub get anywhere in the repository creates a single pubspec.lock at the root, a single .dart_tool/package_config.json, and deletes any stray lockfiles in the individual packages. Packages in the workspace can depend on each other, and the resolution uses the local versions.

# Root pubspec.yaml
name: my_workspace
environment:
  sdk: ^3.6.0
workspace:
  - packages/app
  - packages/shared

# packages/shared/pubspec.yaml
name: shared
environment:
  sdk: ^3.6.0
resolution: workspace

The workspace feature is available in Dart 3.6 and later. It is the recommended approach for a monorepo with multiple interdependent packages, because it avoids the duplicated resolution and the version skew that separate lockfiles can cause.


Complete Example Session

# ============================================
# PART 1: A BASIC PUBSPEC
# ============================================

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


# ============================================
# PART 2: ADDING DEPENDENCIES
# ============================================

dependencies:
  http: ^1.0.2
  intl: ^0.20.3
  path: ^1.9.1


# ============================================
# PART 3: ADDING DEV DEPENDENCIES
# ============================================

dev_dependencies:
  test: ^1.24.0
  lints: ^3.0.0
  build_runner: ^2.4.0


# ============================================
# PART 4: VERSION CONSTRAINTS
# ============================================

dependencies:
  # Caret: compatible with 1.2.3, up to <2.0.0
  http: ^1.2.3

  # Explicit range
  url_launcher: '>=5.4.0 <6.0.0'

  # Any version (avoid)
  # some_package: any

  # Exact version (not recommended)
  # fixed_package: 1.2.3


# ============================================
# PART 5: RUNNING PUB GET
# ============================================

# $ dart pub get
# Resolving dependencies...
# + http 1.2.2
# + intl 0.20.3
# + path 1.9.1
# Changed 3 dependencies!


# ============================================
# PART 6: THE LOCKFILE
# ============================================

# pubspec.lock (generated)
packages:
  http:
    dependency: "direct main"
    description:
      name: http
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"
  intl:
    dependency: "direct main"
    description:
      name: intl
      url: "https://pub.dev"
    source: hosted
    version: "0.20.3"
sdks:
  dart: ">=3.0.0 <4.0.0"


# ============================================
# PART 7: THE PACKAGE CONFIG
# ============================================

# .dart_tool/package_config.json (generated)
{
  "configVersion": 2,
  "packages": [
    {
      "name": "http",
      "rootUri": "file:///home/user/.pub-cache/hosted/pub.dev/http-1.2.2",
      "packageUri": "lib/",
      "languageVersion": "3.0"
    }
  ]
}


# ============================================
# PART 8: UPGRADING DEPENDENCIES
# ============================================

# $ dart pub upgrade
# Resolving dependencies...
# > http 1.2.2 (was 1.2.2)
# > intl 0.20.3 (was 0.20.3)
# No dependencies changed.

# $ dart pub upgrade http
# Resolving dependencies...
# > http 1.3.0 (was 1.2.2)
# Changed 1 dependency!


# ============================================
# PART 9: VIEWING THE DEPENDENCY TREE
# ============================================

# $ dart pub deps
# Dart SDK 3.0.0
# my_app 1.0.0
# ├── http 1.2.2
# │   ├── async 2.11.0
# │   ├── http_parser 4.0.2
# │   ├── meta 1.9.1
# │   └── web 0.3.0
# ├── intl 0.20.3
# └── path 1.9.1


# ============================================
# PART 10: A PUB WORKSPACE
# ============================================

# Root pubspec.yaml
name: my_workspace
environment:
  sdk: ^3.6.0
workspace:
  - packages/app
  - packages/shared

# packages/app/pubspec.yaml
name: app
environment:
  sdk: ^3.6.0
resolution: workspace
dependencies:
  shared: ^1.0.0

# packages/shared/pubspec.yaml
name: shared
environment:
  sdk: ^3.6.0
resolution: workspace

# $ dart pub get
# Resolving dependencies...
# Resolving workspace...
# + shared 1.0.0 (from path)
# + app 1.0.0 (from path)
# Changed 2 dependencies!

The ten parts show a basic pubspec, adding dependencies, adding dev dependencies, version constraints, running pub get, the lockfile, the package config, upgrading dependencies, viewing the dependency tree, and a Pub workspace.


Quick Reference

The Two Dependency Sections

SectionPurposeInherited by consumers
dependenciesRuntime packagesYes
dev_dependenciesDevelopment-only packagesNo
dependency_overridesTemporary version overridesNo

Version Constraints

SyntaxMeaning
^1.2.3>=1.2.3 <2.0.0 (stable)
^0.8.0>=0.8.0 <0.9.0 (pre-1.0.0)
'>=5.4.0 <6.0.0'Explicit range
anyAny version (avoid)
1.2.3Exact version (avoid)

The Lockfile

AspectApplicationLibrary
Commit pubspec.lockYesNo
Reproducible buildsRequiredNot required
Version range supportLockedExpected

Pub Commands

CommandPurpose
dart pub getResolve and download dependencies
dart pub upgradeUpgrade to latest compatible versions
dart pub upgrade <pkg>Upgrade a single package
dart pub add <pkg>Add a dependency
dart pub remove <pkg>Remove a dependency
dart pub depsShow the dependency tree
dart pub outdatedShow updatable dependencies

Pub Workspaces

FileEntry
Root pubspec.yamlworkspace: [packages/app, packages/shared]
Package pubspec.yamlresolution: workspace
LockfileSingle, at the workspace root

Best Practices

✅ Do This:

# Use caret syntax for version constraints
http: ^1.2.3                                                              # ✅
# Separate runtime and dev dependencies
dependencies: [http]                                                       # ✅
dev_dependencies: [test, lints]                                            # ✅
# Commit the lockfile for applications
# git add pubspec.lock                                                    # ✅
# Run pub get after modifying the pubspec
dart pub get                                                               # ✅
# Use pub upgrade deliberately
dart pub upgrade                                                           # ✅

❌ Don’t Do This:

# Don't use exact versions in the pubspec
http: 1.2.3  # prevents compatible updates                                 # ❌
# Don't use 'any'
some_package: any  # unpredictable                                         # ❌
# Don't commit the lockfile for libraries
# git add pubspec.lock (for a library)                                     # ❌
# Don't run pub upgrade without testing
dart pub upgrade  # may introduce breaking changes                         # ❌
# Don't edit the lockfile manually
# pubspec.lock is generated                                                # ❌

Common Pitfalls

PitfallWhy It HappensFix
Version conflictTwo packages require incompatible versionsAdjust constraints or use dependency_overrides
Lockfile out of syncPubspec changed without pub getRun dart pub get
Package not foundWrong name or not on pub.devCheck the package name
SDK constraint mismatchPackage requires a newer SDKUpdate the SDK or the package
pub upgrade breaks the buildNew versions have breaking changesTest after upgrading
Stray lockfiles in a monorepoMigrating to workspacesdart pub get deletes them

Real-World Examples

1. Basic Dependency

dependencies:
  http: ^1.0.2

2. Dev Dependency

dev_dependencies:
  test: ^1.24.0

3. Caret Constraint

http: ^1.2.3

4. Explicit Range

url_launcher: '>=5.4.0 <6.0.0'

5. Dependency Override

dependency_overrides:
  path: ^1.9.1

6. The Lockfile

version: "1.2.2"

7. Pub Get

dart pub get

8. Pub Upgrade

dart pub upgrade

9. Pub Deps

dart pub deps

10. Pub Workspace

workspace:
  - packages/app
  - packages/shared

Visual

The Dependency Resolution Flow

┌──────────────────────────────────────────────────────────────┐
│  DEPENDENCY RESOLUTION FLOW                                  │
│                                                              │
│  pubspec.yaml                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  dependencies:                                       │    │
│  │    http: ^1.0.2                                      │    │
│  │    intl: ^0.20.3                                     │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  dart pub get                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Version solver:                                     │    │
│  │    - reads the constraints                           │    │
│  │    - reads the transitive dependencies               │    │
│  │    - finds a single version for each package         │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  pubspec.lock                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  http: 1.2.2                                         │    │
│  │  intl: 0.20.3                                        │    │
│  │  path: 1.9.1                                         │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    ▼                                                         │
│  .dart_tool/package_config.json                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Maps package names to file locations                │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Dependencies vs Dev Dependencies

┌──────────────────────────────────────────────────────────────┐
│  DEPENDENCIES vs DEV DEPENDENCIES                            │
│                                                              │
│  dependencies:                                               │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  http    → needed at runtime                         │    │
│  │  intl    → needed at runtime                         │    │
│  │  path    → needed at runtime                         │    │
│  │                                                       │    │
│  │  Inherited by consumers of the package.              │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  dev_dependencies:                                           │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  test          → needed only for tests               │    │
│  │  lints         → needed only for linting             │    │
│  │  build_runner  → needed only for code generation     │    │
│  │                                                       │    │
│  │  NOT inherited by consumers.                         │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Lockfile

┌──────────────────────────────────────────────────────────────┐
│  THE LOCKFILE                                                │
│                                                              │
│  WITHOUT pubspec.lock:                                       │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Developer A: dart pub get → http 1.2.2              │    │
│  │  Developer B: dart pub get → http 1.3.0              │    │
│  │                                                       │    │
│  │  Different versions. Different behavior.             │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  WITH pubspec.lock:                                          │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  pubspec.lock: http 1.2.2                            │    │
│  │                                                       │    │
│  │  Developer A: dart pub get → http 1.2.2              │    │
│  │  Developer B: dart pub get → http 1.2.2              │    │
│  │                                                       │    │
│  │  Same version. Same behavior.                        │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Commit the lockfile for applications.                       │
│  Do not commit it for libraries.                             │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Pub Workspaces

┌──────────────────────────────────────────────────────────────┐
│  PUB WORKSPACES                                              │
│                                                              │
│  WITHOUT workspaces:                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  packages/app/pubspec.lock                           │    │
│  │  packages/shared/pubspec.lock                        │    │
│  │                                                       │    │
│  │  Separate resolutions. Potential version skew.       │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  WITH workspaces:                                            │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  pubspec.lock (at the root)                          │    │
│  │  .dart_tool/package_config.json (at the root)        │    │
│  │                                                       │    │
│  │  Single shared resolution.                           │    │
│  │  Packages depend on each other via the workspace.    │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  Root pubspec.yaml: workspace: [packages/app, packages/shared]│
│  Package pubspec.yaml: resolution: workspace                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
pubspec.yamlPackage manifest
dependenciesRuntime packages; inherited by consumers
dev_dependenciesDevelopment-only packages; not inherited
pubspec.lockRecords exact resolved versions
Commit lockfileApplications: yes; libraries: no
Version constraint^1.2.3 = >=1.2.3 <2.0.0
dart pub getResolve and download
dart pub upgradeUpgrade to latest compatible
dart pub addAdd a dependency
Pub workspacesSingle resolution for a monorepo

Key takeaways:

  • A Dart package is a directory with a pubspec.yaml file. The pubspec declares the package’s metadata, its SDK constraint, and its dependencies. The pub tool reads the pubspec and resolves the dependency graph.
  • dependencies are runtime packages; dev_dependencies are development-only packages. The distinction matters because a package’s consumers inherit its dependencies but not its dev_dependencies.
  • The pubspec.lock file records the exact resolved versions. It ensures that every developer and every build server uses the same versions. Commit the lockfile for applications, not for libraries.
  • The version solver resolves all constraints to a single version of each package. Dart allows only one version of a package in the compilation tree, for type safety, binary size, and global-state reasons.
  • The caret syntax is the most common version constraint. ^1.2.3 means >=1.2.3 <2.0.0 for stable versions, and it is more restrictive for pre-1.0.0 versions.
  • dart pub get uses the lockfile if it exists; dart pub upgrade ignores it. get resolves the dependencies and records the versions. upgrade resolves the latest compatible versions and writes a new lockfile.
  • Pub workspaces allow a monorepo to use a single shared resolution. The root pubspec declares the workspace, and each package adds resolution: workspace. A single lockfile and a single package_config.json are created at the root.
  • The pubspec and the lockfile are the two files that govern the dependency resolution. The pubspec declares the constraints, and the lockfile records the result. Together they make the build reproducible and the dependencies manageable.

Remember: A Dart project is a package, and its pubspec.yaml file declares its dependencies. The dependencies section lists the runtime packages, and the dev_dependencies section lists the development-only packages. The pubspec.lock file records the exact resolved versions and ensures reproducible builds. The version solver resolves all constraints to a single version of each package, because Dart allows only one version in the compilation tree. dart pub get resolves the dependencies and uses the lockfile if it exists. dart pub upgrade resolves the latest compatible versions and writes a new lockfile. Pub workspaces allow a monorepo to use a single shared resolution. The pubspec is the declaration, and the lockfile is the record.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!