ZetCode

Dart Package Management with pub

last modified October 22, 2026

In this article we explore pub, the package manager that ships with the Dart SDK. It resolves, downloads, and links the libraries your project depends on, and it records exactly which versions were chosen.

Package Management Overview

A Dart package is a reusable unit of code described by a pubspec.yaml file. The pub tool reads that file, works out a compatible set of dependency versions, downloads them from pub.dev, and links them into your project. The chosen versions are recorded in a lock file so that every machine resolves the same set.

File or term Role
pubspec.yaml Package metadata and the dependencies you want
pubspec.lock Exact resolved versions, checked in for applications
.dart_tool/package_config.json Generated map from package names to local paths
dependencies Packages needed at runtime
dev_dependencies Packages needed only for development and tests

Creating a Package

Every package starts with a pubspec.yaml. The required fields are name, environment, and a version constraint for the SDK. The publish_to: none line marks the project as private so it cannot be published by accident.

pubspec.yaml
name: hello_cli
description: A minimal Dart package.
version: 0.1.0
publish_to: none

environment:
  sdk: ^3.13.0

Running dart pub get reads the file and prepares the package. With no dependencies declared, there is nothing to download, but pub still resolves the environment and writes the metadata files that tooling relies on.

$ dart pub get
Resolving dependencies...
Downloading packages...
Got dependencies!

Runtime and Development Dependencies

Dependencies are listed under two keys. Anything the running program needs goes in dependencies, while tools used only during development, such as test runners and linters, go in dev_dependencies. Development dependencies are not pulled in when your package is used as a dependency by someone else.

pubspec.yaml
name: cli_demo
description: Command-line and pub examples.
version: 1.0.0
publish_to: none

environment:
  sdk: ^3.13.0

dependencies:
  args: ^2.5.0
  path: ^1.9.0

With dependencies declared, dart pub get downloads each package and reports what changed. The plus lines show the versions that were added, and the final line summarises the change count.

$ dart pub get
Resolving dependencies...
Downloading packages...
+ args 2.7.0
+ path 1.9.1
Changed 2 dependencies!

Tools that are only needed while developing the package belong under dev_dependencies instead. A test runner and a linter are typical entries. They are installed when working on the package itself, but they are not pulled in when someone else lists your package as a dependency.

pubspec.yaml
dev_dependencies:
  test: ^1.25.0
  lints: ^5.0.0

Version Constraints

A dependency value is a version constraint, not just a version. The caret syntax ^1.2.0 is the most common: it allows any version from 1.2.0 up to, but not including, 2.0.0. Explicit ranges and the special value any are also available.

pubspec.yaml
dependencies:
  http: ^1.2.0            # >=1.2.0 <2.0.0
  path: '>=1.8.0 <2.0.0' # explicit range
  collection: any         # any version
  meta: 1.15.0            # exactly this version

The caret is preferred for libraries because it accepts compatible updates. An explicit range gives finer control when a package makes frequent breaking changes. Pinning an exact version is the strictest option and is usually reserved for applications that need fully deterministic builds.

Using a Package

After dart pub get, a dependency is imported with a package: URI. The convention is to import it under a prefix, which keeps names from different packages from colliding.

pub_import.dart
import 'package:path/path.dart' as p;

void main() {
  print(p.join('home', 'user', 'file.txt'));
  print(p.extension('archive.tar.gz'));
  print(p.basenameWithoutExtension('/tmp/report.final.pdf'));
}

The as p prefix means the package's functions are reached through p.. join builds a path using the current platform's separator, extension returns the last extension, and basenameWithoutExtension strips the directories and the final extension. Using a package this way is no different from importing a library from the SDK.

$ dart run pub_import.dart
home/user/file.txt
.gz
report.final

The Lock File

dart pub get writes a pubspec.lock file that pins the exact version of every package, including transitive ones. Applications commit this file so that collaborators and build servers install exactly the same versions. Each entry records the source, the resolved version, and a hash used to verify the download.

pubspec.lock
packages:
  args:
    dependency: "direct main"
    description:
      name: args
      url: "https://pub.dev"
    source: hosted
    version: "2.7.0"
sdks:
  dart: ">=3.13.0 <4.0.0"

The direct main value marks args as a dependency listed in dependencies. Packages pulled in indirectly are marked transitive, and development-only packages are marked direct dev. A later dart pub get preserves locked versions when possible, but can change them if updated constraints or new dependencies require it. dart pub upgrade deliberately seeks newer versions within the constraints. For strict CI reproducibility, use dart pub get --enforce-lockfile, which fails if the lock file does not describe a valid resolution or a hosted package's content hash has changed.

Common pub Commands

Most day-to-day package work uses a handful of commands. Adding a package updates the pubspec.yaml and resolves it in one step, while removing it does the reverse. The outdated command reports whether newer versions are available without changing anything.

terminal
$ dart pub get            # resolve and link dependencies
$ dart pub add collection # add a dependency to pubspec.yaml
$ dart pub remove collection
$ dart pub upgrade        # upgrade within the constraints
$ dart pub outdated       # list newer versions

The following session adds a package, removes it again, and then checks for updates. Each command reports what it changed, which makes the flow of versions easy to follow.

$ dart pub add collection
Resolving dependencies...
Downloading packages...
+ collection 1.19.1
Changed 1 dependency!

$ dart pub remove collection
Resolving dependencies...
Downloading packages...
These packages are no longer being depended on:
- collection 1.19.1
Changed 1 dependency!

$ dart pub outdated
Showing outdated packages.
[*] indicates versions that are not the latest available.

Found no outdated packages

Workspaces

When several related packages live in one repository, a pub workspace lets them share a single resolution and a single lock file. The root pubspec.yaml declares which directories are members, and each member is resolved together with the others.

pubspec.yaml
name: my_workspace
publish_to: none

environment:
  sdk: ^3.13.0

workspace:
  - packages/core
  - packages/app

Each member adds resolution: workspace to its own pubspec.yaml. Using a workspace keeps every package on the same set of dependency versions, which avoids the conflicts that arise when each package resolves independently.

The workspace list also accepts a glob pattern, which is useful when packages are added frequently:

workspace:
  - packages/*

A workspace member can declare its own nested workspace. The root only lists packages/server; pub discovers auth and api through the server package:

packages/server/pubspec.yaml
name: server
resolution: workspace

environment:
  sdk: ^3.13.0

workspace:
  - auth
  - api

The sub-packages also set resolution: workspace. All packages in the root and nested workspaces share one lock file and one .dart_tool/package_config.json. Use dart pub workspace list to see the resolved layout:

$ dart pub workspace list
Package  Path
_        ./
server   packages/server/
auth     packages/server/auth/
api      packages/server/api/

Commands that operate on a single package accept -C, so you do not need to change directories. For example, dart pub -C packages/app add http adds a dependency to the packages/app member.

Newer pub Commands

Recent Dart releases added several commands that are useful in day-to-day package maintenance:

CommandPurpose
dart pub bump majorIncrement the major, minor, or patch version of the current package; breaking applies the pre-1.0 convention.
dart pub unpack fooDownload and unpack a package into a local directory without adding it as a dependency.
dart pub get --enforce-lockfileFail if pubspec.lock is missing, incomplete, or no longer matches the constraints.
dart pub workspace listList the root and member packages of a workspace.

Since Dart 3.12, Git dependencies with large files are also supported directly: when git lfs is installed, dart pub get fetches Git LFS content automatically. No extra configuration is needed in pubspec.yaml.

Source

Dart: The pubspec file
Dart: Dependencies
Dart: pub commands
Dart: Lock file

pub manages the libraries a Dart project depends on. The pubspec.yaml declares what the project needs, separating runtime and development dependencies, while version constraints control how flexible each requirement is. The lock file pins the resolved versions for reproducible builds, and a small set of commands covers adding, removing, upgrading, and inspecting dependencies.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.