Packages, pub, and Everyday Tooling
So far in this tutorial, you have written Dart programs in single files. Real software is different: projects grow, they need reusable parts, and they need tools to test, format, and run them. This chapter introduces the ecosystem that makes Dart practical for real work. You will learn how Dart bundles reusable code into packages (a package is a folder of Dart code plus a small description file), how the pub tool downloads and manages them, and which commands you will use every day.
What pub Is and Why It Exists
Imagine every Dart programmer writing their own code for dates, web requests, or JSON parsing. That would waste enormous effort. Instead, the community shares solved problems as packages, and a central store called [pub.dev](https://pub.dev) hosts them. Anyone can publish there, and anyone can download from there.
The tool that talks to pub.dev is the dart pub command. It reads a description of your project, figures out which packages you need and which versions work together, downloads them, and keeps them organized. The description lives in a file called pubspec.yaml. It is written in YAML (a simple text format that uses indentation to describe structured information).
Anatomy of a Project
Create a project with one command:
dart create my_app
cd my_appThe generated folder looks like this:
pubspec.yaml— the project description: its name, dependencies, and settings.bin/— code you run directly. The filebin/my_app.dartholds themainfunction.lib/— reusable code others (or your own other files) import. Most of a package's code lives here.test/— automated tests that check your code behaves as expected..dart_tool/— internal bookkeeping pub manages. Do not edit it.
A minimal pubspec.yaml looks like this:
name: my_app
description: A sample command-line application.
version: 1.0.0
environment:
sdk: ^3.5.0
dependencies:
http: ^1.2.0
dev_dependencies:
lints: ^4.0.0The two most important sections:
dependencies— packages your program needs when it runs.dev_dependencies— packages needed only while developing, such as test tools or lint rules (lints are automatic style checks).
Version Constraints and the Lock File
Notice http: ^1.2.0. The caret symbol ^ means "this version or newer, as long as it stays below 2.0.0." Dart uses semantic versioning (a convention where the first number changes only for breaking changes). So ^1.2.0 accepts 1.2.0, 1.3.1, or 1.9.9, but never 2.0.0, because a new major number may break your code.
When pub resolves your dependencies, it records exact chosen versions in pubspec.lock. This lock file makes builds reproducible (everyone on the team gets identical versions). Commit it to version control for applications; for reusable packages it is usually left out.
The basic commands:
dart pub get # download dependencies listed in pubspec.yaml
dart pub add http # add a dependency and fetch it
dart pub upgrade # get the newest allowed versions
dart pub outdated # show which dependencies have newer releasesImport a package's code by its name. If you added http, you can write:
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://example.com'),
);
print(response.statusCode);
}For your own files, import with relative paths or package: paths. Code in lib/util/math.dart inside a project named my_app is imported as package:my_app/util/math.dart.
A Map of Everyday Tooling
Show diagram source
flowchart TD
A[pubspec.yaml] --> B[dart pub get]
B --> C[pubspec.lock]
B --> D[package cache]
E[lib/ code] --> F[dart analyze]
G[bin/ code] --> H[dart run]
I[test/ code] --> J[dart test]
F --> H
J --> H
D --> HThe Commands You Will Use Daily
dart run # run the main program (bin/my_app.dart)
dart run bin/other.dart # run a specific file
dart analyze # check code for errors and style problems
dart format . # automatically format all files consistently
dart test # run the automated tests in test/
dart doc # generate API documentation from doc comments
dart fix --apply # automatically fix common problemsdart format deserves a special mention. It settles all arguments about style: spacing, line breaks, indentation. Teams use it so code reviews focus on logic instead of formatting. Run dart format . before every commit and your whole codebase stays uniform.
dart analyze uses static analysis (examining your code without running it) to catch bugs early: unused variables, type errors, questionable patterns. You can tighten or loosen the rules by adding a analysis_options.yaml file.
Writing and Using Your Own Package
You can publish your own package to pub.dev with dart pub publish, or keep it private. Even without publishing, splitting code into lib/ folders with clean imports makes projects easier to navigate.
To use a package without publishing it, such as a folder sitting next to your project, use a path dependency:
dependencies:
my_helper:
path: ../my_helperThis is common in large organizations, where several applications share internal packages.
Choosing Packages Wisely
Anyone can publish to pub.dev, so quality varies. Before adding a dependency, check:
- Publisher: a verified publisher badge means a real organization stands behind it.
- Popularity and likes: widely used packages are more likely to be maintained.
- Last publish date: a package untouched for years may be abandoned.
- Supported SDK version: it must be compatible with your Dart version.
Fewer dependencies generally means fewer things that can break. Add a package when it solves a real problem, not on a hunch.
Common First-Week Mistakes
- Editing
.dart_tool/by hand. Pub regenerates it; rundart pub getinstead. - Forgetting
dart pub getafter pulling teammates' changes, causing "package not found" import errors. - Putting runnable scripts in
lib/. Keep entry points inbin/. - Over-constraining versions, forcing exact versions that conflict with other packages.
Putting It Together
Try the full workflow on a fresh project:
dart create toolbox
cd toolbox
dart pub add collectionThen edit bin/toolbox.dart:
import 'package:collection/collection.dart';
void main() {
final numbers = [3, 1, 4, 1, 5];
final sorted = numbers.sorted((a, b) => a.compareTo(b));
print(sorted);
}Run it with dart run. You have just: created a project, declared a dependency, imported community code, and executed it — the exact loop you will repeat for the rest of your Dart career.
Exercise
A teammate pulls your project from version control and sees "Target of URI doesn't exist: 'package:http/http.dart'". What command fixes it, and why was the error possible?
Show answer
Run dart pub get. The downloaded packages live in a local cache that is not shared through version control; the lock file records versions, but the files themselves must be fetched into each machine's cache before imports can resolve.
Exercise
Your pubspec has http: ^1.2.0. Which of these versions will pub accept: 1.2.0, 1.9.0, 2.0.0, 1.1.0?
Show answer
1.2.0 and 1.9.0 only. The caret allows versions at or above 1.2.0 and below 2.0.0; 2.0.0 is a new major version and 1.1.0 is below the minimum.
Exercise
You want a package that provides lint rules used only during development, never at runtime. Which pubspec section does it belong in?
Show answer
dev_dependencies. Packages there are available while developing and testing but are not bundled with the code you ship.
Spotted a mistake, or code that does not work?