Pub.dev #

No application is built entirely from scratch — almost every Dart project depends on external packages for battle-tested functionality: HTTP clients, JSON serializers, state management, date utilities, and hundreds of other needs. Pub.dev is the official repository of Dart and Flutter packages, and dart pub is the tool managing its entire lifecycle: searching, downloading, updating, and publishing. Understanding how the package system works — especially semantic version constraints and how to choose the right package — is the skill that determines whether your project’s dependencies become an asset or technical debt.

The Dart Package Ecosystem #

Pub.dev provides more than 40,000 packages you can use for free. Each package is assessed by several factors displayed as scores:

flowchart LR
    PD["pub.dev"] --> Likes["❤ Likes\n(community popularity)"]
    PD --> Points["🏆 Pub Points\n(automatic quality)"]
    PD --> Pop["📊 Popularity\n(actual usage)"]

    Points --> PA["📄 API documentation"]
    Points --> PB["🛡 null safety"]
    Points --> PC["🎯 platform support"]
    Points --> PD2["📜 LICENSE"]
    Points --> PE["✅ pub.dev guidelines"]

Three main metrics to watch when choosing a package:

MetricMeaningInterpretation
LikesHow many developers liked itSubjective community popularity
Pub PointsAutomatic score 0-160Technical quality: docs, null safety, platform support
PopularityPercentage of other packages depending on itReal usage in the ecosystem

pubspec.yaml — Project Configuration #

pubspec.yaml is the heart of every Dart project — this file defines the project’s identity, dependencies, and various configurations. Every project has exactly one pubspec.yaml in its root directory.

# Package identity
name: aplikasi_toko          # required — package name, all lowercase + underscore
version: 1.2.3               # required for public packages, SemVer format
description: >               # short description (> means a scalar block in YAML)
  Aplikasi toko online dengan fitur katalog produk,
  keranjang belanja, dan integrasi pembayaran.
homepage: https://github.com/username/aplikasi_toko
repository: https://github.com/username/aplikasi_toko
issue_tracker: https://github.com/username/aplikasi_toko/issues

# SDK constraint — supported Dart versions
environment:
  sdk: '>=3.0.0 <4.0.0'    # required — without it dart pub get fails

# Runtime dependencies — used by production code
dependencies:
  http: ^1.2.0
  intl: ^0.19.0
  shared_preferences: ^2.2.0
  path: ^1.9.0

# Development dependencies — only for testing and tooling
dev_dependencies:
  test: ^1.25.0
  mockito: ^5.4.0
  build_runner: ^2.4.0
  lints: ^3.0.0

# Overrides — force a specific version to resolve conflicts
dependency_overrides:
  some_package: '2.0.0'

Project Directory Structure #

aplikasi_toko/
  ├── pubspec.yaml          ← project configuration
  ├── pubspec.lock          ← locked versions (commit to repo for apps)
  ├── analysis_options.yaml ← analyzer and linter configuration
  ├── lib/
  │   ├── main.dart
  │   └── src/             ← internal code (not exposed as a public API)
  ├── bin/                 ← executable scripts
  ├── test/                ← test files
  ├── example/             ← usage examples (for packages)
  └── .dart_tool/          ← generated, DON'T commit to the repo

Version Constraints — Semantics You Must Understand #

A version constraint determines which package versions are compatible with your project. Dart follows Semantic Versioning (SemVer): MAJOR.MINOR.PATCH.

MAJOR — breaking changes (API not backward-compatible)
MINOR — new features that are backward-compatible
PATCH — bug fixes that are backward-compatible

Constraint Syntax #

dependencies:
  # Exact version — inflexible, avoid unless there's a strong reason
  paket_a: '1.2.3'

  # Caret syntax (most common) — allows MINOR and PATCH updates, not MAJOR
  # ^1.2.3 is equivalent to: >=1.2.3 <2.0.0
  paket_b: ^1.2.3

  # For 0.x.y versions — caret is stricter because MAJOR is still 0
  # ^0.3.0 is equivalent to: >=0.3.0 <0.4.0 (only PATCH is free)
  paket_c: ^0.3.0

  # Explicit range — full control
  paket_d: '>=1.0.0 <2.5.0'

  # Minimum version only — no upper bound (dangerous)
  paket_e: '>=1.0.0'          # ✗ avoid — a MAJOR update could be breaking

  # Any — accept any version (very dangerous)
  paket_f: any                  # ✗ don't use

  # Path — local package (for development)
  paket_lokal:
    path: ../paket_lokal

  # Git — package from a Git repository
  paket_git:
    git:
      url: https://github.com/user/paket.git
      ref: main                # branch, tag, or commit hash
// Guidance for choosing a constraint:
//
// ^x.y.z  — use this for almost every case
//           MINOR and PATCH updates automatic, MAJOR requires manual approval
//
// >=x.y.z <a.b.c — when you need a more specific range
//                   e.g. compatible with two MAJOR versions at once
//
// AVOID 'any' and unbounded ranges — they can cause conflicts that are
//         very hard to debug when packages depend on each other

pubspec.lock — Version Safety #

pubspec.lock records the exact version of every package (including transitive dependencies) currently in use. This matters for reproducibility:

# pubspec.lock — DON'T edit manually, let dart pub manage it
packages:
  http:
    dependency: "direct main"
    description:
      name: http
      sha256: "761a297c042deedc1ffbb156d6e3dde3800e0826b9a
      url: "https://pub.dev"
    source: hosted
    version: "1.2.2"         # the exact version in use
pubspec.lock RULES:
  ✓ Application packages (deployed apps) — COMMIT to version control
    Ensures all developers and production use the same versions
  ✗ Library packages (published packages) — DON'T commit
    Libraries must be compatible with various versions of their dependencies

dart pub Commands — Everything You Need to Know #

Installation and Synchronization #

# Download all dependencies from pubspec.yaml
dart pub get

# Force using the versions in pubspec.lock (for CI/production)
dart pub get --enforce-lockfile

# Add a new package and update pubspec.yaml directly
dart pub add http
dart pub add http intl path          # add several at once

# Add as a dev dependency
dart pub add dev:test
dart pub add dev:mockito dev:build_runner

# Add with a specific constraint
dart pub add 'http:^1.2.0'
dart pub add 'http:>=1.0.0 <2.0.0'

# Remove a package
dart pub remove http

Updates and Upgrades #

# Check which packages can be updated
dart pub outdated

# Example output:
# Package   Current  Upgradable  Resolvable  Latest
# http      1.1.0    1.2.2       1.2.2       1.2.2
# intl      0.18.0   0.18.1      0.19.0      0.19.0

# Upgrade all packages to the newest version still within constraints
dart pub upgrade

# Upgrade specific packages
dart pub upgrade http intl

# Upgrade BEYOND constraints (also updates pubspec.yaml)
dart pub upgrade --major-versions
dart pub upgrade --major-versions http   # only for http

Inspection and Diagnostics #

# Show all dependencies (direct and transitive)
dart pub deps

# Show in a different format
dart pub deps --style=list    # flat list
dart pub deps --style=compact # concise

# Check whether pubspec.yaml is valid and dependencies can be resolved
dart pub publish --dry-run    # for packages to be published

# Show downloaded package versions
dart pub cache list

# Clean the cache (useful if there are problems)
dart pub cache clean

# Info about a specific package
dart pub info http

Global Packages #

Packages can be installed globally — available in all projects without adding them to pubspec.yaml:

# Install a package as a global tool
dart pub global activate dart_style
dart pub global activate webdev
dart pub global activate flutterfire_cli

# Run a globally installed tool
dart pub global run dart_style:format .

# If PATH is configured, you can call it directly
dartfmt .

# List installed global packages
dart pub global list

# Deactivate a global package
dart pub global deactivate dart_style

Dependency Types #

dependencies — Runtime #

Packages needed by production code. Bundled with the application:

dependencies:
  # HTTP client
  http: ^1.2.0

  # Internationalization and date/number formatting
  intl: ^0.19.0

  # Persistent key-value storage
  shared_preferences: ^2.2.0

  # File path manipulation
  path: ^1.9.0

  # Additional collection utilities (groupBy, firstOrNull, etc.)
  collection: ^1.18.0

  # Structured logging
  logging: ^1.2.0

dev_dependencies — Development Only #

Packages only needed during development — not bundled into production:

dev_dependencies:
  # Testing framework
  test: ^1.25.0

  # Mock generation
  mockito: ^5.4.0
  build_runner: ^2.4.0

  # Linting rules
  lints: ^3.0.0

  # Coverage reporting
  coverage: ^1.7.0

  # JSON serialization code generation
  json_serializable: ^6.7.0

dependency_overrides — Resolving Conflicts #

When two packages need different versions of the same dependency, use dependency_overrides to force a specific version:

# Situation: package_a needs http ^0.13.0, package_b needs http ^1.0.0
# Solution: override to a version compatible with both

dependency_overrides:
  http: ^1.2.0    # force all packages to use this version
// ANTI-PATTERN: using dependency_overrides carelessly
// ✗ Can cause runtime errors if packages aren't truly compatible
// ✗ Only use as a temporary solution while waiting for a package update

// CORRECT: report the conflict to the package maintainer as an issue
// and use the override only if there's no other way

Choosing the Right Package #

Not all packages on pub.dev are equal in quality. Here’s a checklist for evaluating a package before adding it:

EVALUATE A PACKAGE BEFORE USING IT:

Popularity and trust:
  □ Pub Points ≥ 120 (out of 160)
  □ Popularity ≥ 70%
  □ Likes sufficient for the topic (relative)
  □ Maintained by dart.dev, flutter.dev, or a trusted organization

Maintenance activity:
  □ Last update < 6 months ago
  □ Still actively accepting and closing issues
  □ CHANGELOG exists and is well written
  □ Version ≥ 1.0.0 (stable) or you're ready for breaking changes

Technical quality:
  □ Null safety supported (Dart 2.12+)
  □ API documentation available (pub.dev shows the %)
  □ Example code on the pub.dev page
  □ Test coverage exists (check GitHub)
  □ An appropriate LICENSE (MIT, Apache, BSD — not GPL for commercial products)

Compatibility:
  □ Supports the platforms you target (mobile, web, desktop)
  □ SDK constraint compatible with your Dart/Flutter version
  □ No problematic transitive dependencies
dependencies:
  # Networking
  http: ^1.2.0                    # Dart's official HTTP client
  dio: ^5.4.0                     # HTTP client with interceptors and cancellation

  # JSON
  json_annotation: ^4.8.0        # annotations for code generation

  # State management (Flutter)
  provider: ^6.1.0               # simple state management
  riverpod: ^2.5.0               # more powerful state management

  # Utilities
  collection: ^1.18.0            # groupBy, firstOrNull, etc.
  path: ^1.9.0                   # cross-platform path manipulation
  intl: ^0.19.0                  # date, number, and text formatting
  logging: ^1.2.0                # structured logging
  equatable: ^2.0.5              # value equality without boilerplate
  uuid: ^4.3.0                   # generate UUIDs

dev_dependencies:
  test: ^1.25.0                  # testing framework
  mockito: ^5.4.0                # mocking for unit tests
  build_runner: ^2.4.0           # code generation runner
  json_serializable: ^6.7.0     # JSON code generation
  lints: ^3.0.0                  # official Dart lint rules
  dart_code_metrics: ^5.7.0     # code quality analysis

Creating Your Own Package #

When there’s functionality you want to share across projects or publish to the community, create your own package.

Package Structure #

# Create a new package
dart create --template=package nama_package

# Generated structure:
nama_package/
  ├── lib/
  │   ├── nama_package.dart      ← public entry point
  │   └── src/                   ← internal implementation
  │       └── fitur_utama.dart
  ├── test/
  │   └── nama_package_test.dart
  ├── example/
  │   └── nama_package_example.dart
  ├── pubspec.yaml
  ├── README.md                  ← required for pub points
  ├── CHANGELOG.md              ← required for pub points
  └── LICENSE                   ← required for pub points

Exposing the Public API #

// lib/nama_package.dart — the package's public entry point
// Export only what package users need to know

library nama_package;

export 'src/fitur_utama.dart' show KelasUtama, FungsiUtama;
export 'src/model.dart' show ModelData;
// src/internal_helper.dart is NOT exported — stays private

// Package users import it with:
// import 'package:nama_package/nama_package.dart';

Publishing to Pub.dev #

# Make sure you're logged in with a Google account
dart pub login

# Check whether the package is ready to publish
dart pub publish --dry-run
# Output shows all files to be uploaded
# and validates pubspec.yaml and the package structure

# Publish (can't be undone for the same version!)
dart pub publish

# Update to a new version — increment the version in pubspec.yaml first
# Then publish again
# For packages not ready to publish, prevent accidental publication
publish_to: none   # rejects dart pub publish

# pubspec.yaml for a package to be published
name: nama_package
version: 1.0.0                    # required
description: >
  A short, clear description (60-180 characters is ideal for pub.dev display)  
homepage: https://github.com/user/nama_package
repository: https://github.com/user/nama_package
topics:                           # pub.dev categories (max 5)
  - utility
  - dart
environment:
  sdk: '>=3.0.0 <4.0.0'

Package Management Anti-Patterns #

Adding a Package for One Small Function #

// ANTI-PATTERN: adding a big package for one small function
// dependencies:
//   uuid: ^4.3.0   ← just to generate a simple random string

// CORRECT: evaluate whether it can be implemented yourself
import 'dart:math';

String generateId() {
  final random = Random.secure();
  final bytes = List.generate(16, (_) => random.nextInt(256));
  // simple UUID v4 implementation
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
  return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
// For more serious UUID needs, the uuid package is still worth using

Not Locking Versions for Applications #

# ANTI-PATTERN: overly loose constraints for an application
dependencies:
  http: any           # ✗ could change at any time
  intl: '>=0.17.0'   # ✗ no upper bound — a MAJOR update possible

# CORRECT: use caret syntax — flexible but controlled
dependencies:
  http: ^1.2.0        # ✓ minor/patch updates, but not major
  intl: ^0.19.0       # ✓ consistent and predictable

Not Committing pubspec.lock for Applications #

# ANTI-PATTERN: a .gitignore that ignores pubspec.lock
echo "pubspec.lock" >> .gitignore   # ✗ for application packages

# CORRECT: commit pubspec.lock for application packages
# Make sure pubspec.lock is NOT in .gitignore

# For library packages — indeed not committed
# Add to .gitignore only for libraries:
# pubspec.lock  # only if this is a library package

Using dependency_overrides Permanently #

# ANTI-PATTERN: dependency_overrides as a permanent solution
dependency_overrides:
  http: 0.13.6         # ✗ forced to an old version with no update plan

# CORRECT: use only temporarily while reporting the issue
# and remove it as soon as the upstream package is updated

Summary #

  • pubspec.yaml is the center of project configuration — defining the name, version, SDK constraint, and all dependencies. environment.sdk is required or dart pub get fails.
  • Caret syntax ^x.y.z is the standard for almost all dependencies — allowing automatic MINOR and PATCH updates, but requiring explicit approval for potentially breaking MAJOR updates.
  • pubspec.lock must be committed for application packages — ensuring the entire team and production use the exact same versions. Library packages don’t need to commit the lock file.
  • dart pub add is the easiest way to add a package — automatically determining the right constraint and running dart pub get. No manual pubspec.yaml editing needed.
  • dart pub outdated shows which packages can be updated before deciding on dart pub upgrade — always check first to avoid surprises.
  • dev_dependencies for packages only needed during development (testing, code generation, linting) — not bundled into production and not affecting your package’s users.
  • Evaluate packages before adding: Pub Points ≥ 120, actively maintained, null safety, good README and CHANGELOG, and a license that fits your needs.
  • publish_to: none to prevent accidental publication to pub.dev during development — remove or replace it when ready to publish.
  • Avoid dependency_overrides unless truly forced — it’s a workaround that masks compatibility problems that should be solved at the package level.
  • Global packages (dart pub global activate) are useful for developer tools used across many projects without adding them to every pubspec.yaml.

← Previous: Regex   Next: Multithreading →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact