Skip to content

Dart Packages Complete Guide — Create, Publish, and Manage Dependencies

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about Dart Packages Complete Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Dart packages are reusable units of code distributed through the pub.dev registry, enabling modular architecture through dependency declarations in pubspec.yaml, version constraints, and semantic versioning.

What You Will Learn

  • What Dart packages are and how the pub ecosystem works
  • Creating a package from scratch with proper structure
  • Managing dependencies with pubspec.yaml and version constraints
  • Publishing a package to pub.dev
  • Using package features like exports, libraries, and privacy
  • Best practices for package maintenance and versioning

Why It Matters

Software is built on the shoulders of reusable components. The Dart package ecosystem contains over 40,000 packages covering networking, state management, Serialization, testing, and UI components. Understanding how to create and consume packages transforms you from a code writer into a library author who can share solutions across projects and teams. This skill is critical in production environments like Durga Antivirus Pro, where modular packages handle file scanning, signature updates, and reporting as independently versioned components that can be tested and updated separately.

Real-World Use

Doda Browser uses a custom package called doda_http that wraps the http package with automatic retry logic, token refresh, and request logging. This package is shared across the browser app, a companion desktop tool, and the backend admin panel. When the retry logic needed an update, only doda_http was republished, and all three applications picked up the fix by running dart pub upgrade.

Learning Path

flowchart LR
  A[Build Runner] --> B[Packages\nYou are here]
  B --> C[WebSockets]
  style B fill:#f90,color:#fff

Package Structure

A Dart package is a directory containing a pubspec.yaml file and Dart source files under lib/. The minimal structure looks like this:

my_package/
  lib/
    my_package.dart
  test/
    my_package_test.dart
  pubspec.yaml
  README.md
  CHANGELOG.md
  LICENSE

The lib/my_package.dart file is the main entry point. Everything declared in files that are exported from this entry point is visible to consumers of the package.

// lib/my_package.dart
/// A utility package for string operations.
library my_package;

export 'src/validators.dart';
export 'src/formatters.dart';

Supporting files live in lib/src/ by convention. Files in src/ are considered private — they can be imported by other files within the package but are not exposed to consumers unless re-exported.

// lib/src/validators.dart
bool isValidEmail(String email) {
  return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}

bool isValidUrl(String url) {
  return Uri.tryParse(url)?.hasScheme ?? false;
}
// lib/src/formatters.dart
String capitalize(String text) {
  if (text.isEmpty) return text;
  return text[0].toUpperCase() + text.substring(1);
}

String truncate(String text, int maxLength) {
  if (text.length <= maxLength) return text;
  return '${text.substring(0, maxLength)}...';
}

The pubspec.yaml File

The pubspec.yaml is the manifest file that defines your package metadata, dependencies, and constraints.

name: my_package
description: A utility package for string validation and formatting.
version: 1.0.0
homepage: https://github.com/username/my_package
repository: https://github.com/username/my_package.git
issue_tracker: https://github.com/username/my_package/issues
documentation: https://github.com/username/my_package/wiki

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  collection: ^1.18.0

dev_dependencies:
  test: ^1.24.0
  lints: ^3.0.0

Version Constraints

Dart uses semantic versioning (major.minor.patch) with the following constraint syntax:

  • Caret constraints (^1.2.3): Allows any version from 1.2.3 to below 2.0.0. Equivalent to >=1.2.3 <2.0.0. This is the most common constraint.
  • Exact version (1.2.3): Only version 1.2.3 is accepted.
  • Range (>=1.2.0 <2.0.0): Explicit lower and upper bounds.
  • Any (any): Accepts any version. Avoid in production packages.
dependencies:
  # Caret constraint — allows patches and minors up to next major
  http: ^1.2.0

  # Exact version — locks to one specific version
  path: 1.9.0

  # Range constraint — explicit boundaries
  meta: '>=1.9.0 <2.0.0'

Dependency Sources

Dependencies can come from various sources beyond pub.dev:

dependencies:
  # From pub.dev
  http: ^1.2.0

  # Git repository
  my_toolkit:
    git:
      url: https://github.com/username/my_toolkit.git
      ref: main

  # Local path (useful during development)
  local_utils:
    path: ../local_utils

Creating a Package Step by Step

Use the dart create command to scaffold a new package:

dart create --template package my_package

This creates the basic structure with a default pubspec.yaml, a main library file, and a test file.

// lib/src/string_utils.dart
String reverse(String input) {
  return input.split('').reversed.join('');
}

int countVowels(String input) {
  return input.split('').where((c) => 'aeiou'.contains(c.toLowerCase())).length;
}

bool isPalindrome(String input) {
  final cleaned = input.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '').toLowerCase();
  return cleaned == cleaned.split('').reversed.join('');
}
// lib/my_package.dart
export 'src/string_utils.dart';
// test/my_package_test.dart
import 'package:test/test.dart';
import 'package:my_package/my_package.dart';

void main() {
  group('StringUtils', () {
    test('reverse reverses the string', () {
      expect(reverse('hello'), equals('olleh'));
    });

    test('countVowels counts correctly', () {
      expect(countVowels('hello world'), equals(3));
    });

    test('isPalindrome detects palindromes', () {
      expect(isPalindrome('A man, a plan, a canal: Panama'), isTrue);
      expect(isPalindrome('hello'), isFalse);
    });
  });
}

Run the tests:

dart test

Output:

00:01 +1: StringUtils reverse reverses the string
00:01 +2: StringUtils countVowels counts correctly
00:01 +3: StringUtils isPalindrome detects palindromes
00:01 +3: All tests passed!

Publishing to pub.dev

Before publishing, ensure your package is ready:

  1. Add a descriptive README.md with usage examples.
  2. Maintain a CHANGELOG.md documenting changes per version.
  3. Include a LICENSE file (MIT, Apache 2.0, or BSD-3 are common).
  4. Verify the package with dart pub publish --dry-run.
# Dry run — checks for issues without publishing
dart pub publish --dry-run

# Actual publish
dart pub publish

The publish command creates an account on pub.dev (if needed), uploads your package, and makes it available to the Dart community. After publishing, anyone can add your package as a dependency:

dependencies:
  my_package: ^1.0.0

Package Scoring

pub.dev scores packages on three dimensions:

  • Popularity: Downloads and usage across the ecosystem.
  • Health: Tests, documentation, license, and maintenance activity.
  • Maintenance: Time since last update, open issues, and responsiveness.

Aim for a score of 100 by including tests, documentation, a license, and regularly updating dependencies.

Using Packages in Your Project

Consuming a package is straightforward — add it to your pubspec.yaml, run dart pub get, and import it.

dependencies:
  http: ^1.2.0
  convert: ^3.1.0
dart pub get
import 'package:http/http.dart' as http;
import 'package:convert/convert.dart';

void main() async {
  final response = await http.get(Uri.parse('https://api.example.com/data'));
  print('Status: ${response.statusCode}');
  print('Body: ${response.body}');
}

Resolving Version Conflicts

When two dependencies require incompatible versions of the same package, pub reports a conflict. Use dart pub deps to inspect the dependency tree:

dart pub deps

Output:

my_app 1.0.0
├── http 1.2.0
│   └── async 2.11.0
├── collection 1.18.0
└── my_package 1.0.0
    └── collection 1.18.0

If a conflict arises, you can either update the conflicting dependencies to compatible versions or use dependency overrides:

dependency_overrides:
  collection: 1.19.0

Dependency overrides force a specific version regardless of constraints. Use them temporarily during Migration, not as a permanent solution.

Private Packages

For packages you do not want to publish publicly, use a hosted private pub server or reference them by git or path:

dependencies:
  internal_tools:
    git:
      url: https://github.com/company/internal_tools.git
      ref: v1.2.0

Some teams use pub.dev with a private scope, or self-host a pub server using Google Cloud or a custom Dart server.

Common Mistakes

  1. Forgetting to export files: Declaring a class in lib/src/ without re-exporting it from the main library file makes it invisible to consumers. Always export public API surfaces from the main entry point.

  2. Using broad version constraints: any or >=0.0.0 constraints allow breaking changes to be pulled in unexpectedly. Always use caret constraints like ^1.2.0 to cap the major version.

  3. Not running tests before publishing: A package with failing tests erodes trust. Run dart test and ensure all tests pass before every publish.

  4. Ignoring the CHANGELOG: The changelog is the first place users look when upgrading. Document every breaking change, new feature, and bug fix with the corresponding version number.

  5. Publishing without a license: Without a license, the default copyright laws in many jurisdictions restrict usage. Always include a permissive license like MIT or Apache 2.0.

  6. Not following file conventions: Placing implementation files outside lib/src/ or using non-standard directory structures confuses both users and tooling. Follow the standard layout.

  7. Hard-coding absolute paths: Library code should never use absolute file paths. Use Platform utilities from dart:io or accept paths as parameters for testability.

Practice Questions

  1. What does the caret constraint ^2.1.0 mean in terms of allowable versions?
  2. Why should implementation details go in lib/src/ rather than the main library file?
  3. How do you resolve a dependency conflict between two packages requiring different versions of the same library?
  4. What is the purpose of dart pub publish --dry-run?
  5. Challenge: Create a package called string_toolkit with at least five string utility functions (camelCase, snakeCase, kebabCase, truncate, padLeft). Write tests for each function, publish the package locally, and consume it from a separate Dart application.

Mini Project

Build a Dart package called config_loader that:

  • Reads YAML or JSON configuration files from a specified path
  • Supports nested key access using dot notation (e.g., config.get('database.host'))
  • Falls back to environment variables when keys are not in the file
  • Includes comprehensive tests with both YAML and JSON fixtures
  • Publishes to pub.dev with README, CHANGELOG, and MIT license
  • Add a usage example in the README showing how to load a config file and access values

FAQ

What is the difference between a Dart package and a Flutter plugin?

A Dart package contains pure Dart code and can be used in any Dart project. A Flutter plugin includes platform-specific code (Android/Kotlin, iOS/Swift) and can only be used in Flutter projects.

How do I update all dependencies in my project?

Run dart pub upgrade to update all dependencies to the latest allowed versions. Use dart pub upgrade --major-versions to also allow major version bumps, which may include breaking changes.

Can I have multiple entry points in a package?

Yes. You can define multiple library files under lib/ and users import whichever they need. Common patterns include package:my_package/my_package.dart for the full API and package:my_package/src/core.dart for a subset.

How do I mark a dependency as optional?

Use dev_dependencies for test-only dependencies and dependencies for required ones. There is no formal optional dependency mechanism — instead, suggest users add the optional package themselves and document the integration.

What happens if I delete a published version from pub.dev?

Published versions are immutable and cannot be deleted. You can retract a version (mark it as withdrawn) using dart pub publish --retract <version>, which prevents new projects from depending on it but does not remove it for existing users.

What is Next

Proceed to WebSockets for real-time communication over persistent connections. Then explore GraphQL for query-based API integration patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro