Learn how to add, update and manage Flutter packages using pubspec.yaml. Understand dependencies, version constraints, package sources, conflict resolution and best practices for real-world Flutter apps.

One of the biggest strengths of Flutter is its rich ecosystem of packages. Instead of building everything from scratch, you can install packages that add features like state management, networking, authentication, local storage, animations, charts, and much more.
Every Flutter project manages these packages through the pubspec.yaml file. Whether you’re installing Provider, HTTP, Firebase, Hive, Shared Preferences, or Intl, you’ll register each package inside this configuration file before Flutter can use it.
If you’re new to Flutter, terms like dependencies, dev_dependencies, version constraints, and dependency_overrides can seem confusing at first.
You might also wonder why some tutorials tell you to run flutter pub get, why a package doesn’t update after flutter pub upgrade, or how to use packages stored on GitHub or your local computer.
In this beginner-friendly guide, you’ll learn how Flutter pubspec.yaml dependencies work, how to add packages correctly, update them safely, manage different dependency types, resolve common package conflicts, and follow best practices used in production Flutter applications.
By the end of this tutorial, you’ll be able to confidently manage your project’s packages without guessing which commands or configuration options to use.
- What Are Dependencies?
- dependencies vs dev_dependencies
- Understanding Version Constraints
- Updating Packages in Flutter
- Using Git Packages in Flutter
- Using Local (Path) Packages
- Understanding dependency_overrides
- Resolving Package Conflicts
- Flutter Package Management Best Practices
- Take Your Flutter Skills to the Next Level
- Ready to Build Professional Flutter Apps?
What Are Dependencies?
A dependency is a reusable package that adds functionality to your Flutter application.
Instead of writing every feature yourself, you can install packages created by the Flutter community or the Dart team and use them in your project.
Think about some common app features. If your app needs to make API requests, save data locally, format dates, connect to Firebase, or manage application state, you could build each of those features from scratch.
However, that would take a significant amount of time and require a lot of testing. By using well-maintained packages, you can focus on building your app instead of reinventing common solutions.
Flutter manages these packages through the pubspec.yaml file. Every package your application depends on is listed in the dependencies section.
When you run flutter pub get, Flutter downloads those packages, resolves their versions, and makes them available to your project.
For example, if you want to make HTTP requests to a REST API, you can add the popular http package to your project.
dependencies:
flutter:
sdk: flutter
http: ^1.5.0
Code language: YAML (yaml)
After saving the file, run:
flutter pub getCode language: JavaScript (javascript)
Flutter downloads the package and stores it in your local package cache. You can then import it into your Dart files and start using it immediately.
import 'package:http/http.dart' as http;
Code language: Dart (dart)
Packages aren’t limited to networking. Flutter developers commonly use dependencies for many different purposes, including:
- State management using Provider, Riverpod, or Bloc
- Local storage using Hive or Shared Preferences
- Networking using HTTP or Dio
- Authentication using Firebase Authentication
- Date and number formatting using Intl
- Animations, charts, maps, camera access, and much more
One of Flutter’s greatest strengths is its package ecosystem. Thousands of high-quality packages are available on pub.dev, allowing you to add powerful features to your app with just a few lines in your pubspec.yaml file.
Learning how Flutter pubspec.yaml dependencies work is an essential skill because nearly every real-world Flutter application relies on third-party packages.
dependencies vs dev_dependencies
When you open a Flutter project’s pubspec.yaml file, you’ll usually see two sections for packages:
dependencies:
dev_dependencies:
Code language: YAML (yaml)
Although they may look similar, they serve two very different purposes. Understanding the difference will help you organize your project correctly and avoid installing unnecessary packages in your final application.
What Are dependencies?
The dependencies section contains packages that your Flutter application needs while it is running. These packages become part of your app and are used to provide features that users interact with.
For example, if your app needs to make API requests, save user preferences, connect to Firebase, or manage application state, those packages belong in the dependencies section.
dependencies:
flutter:
sdk: flutter
http: ^1.5.0
provider: ^6.1.5
shared_preferences: ^2.5.3
intl: ^0.20.2
Code language: YAML (yaml)
Each package has a specific purpose:
httplets your app communicate with REST APIs.providerhelps manage and share application state.shared_preferencesstores small pieces of data such as user settings.intlformats dates, times, numbers, and currencies for different locales.
Because your application uses these packages while it’s running, Flutter bundles them as part of your project.
What Are dev_dependencies?
The dev_dependencies section is reserved for packages that help you develop, test, or generate code, but are not included in the final application.
These packages improve your development workflow without affecting your app’s runtime behavior.
For example:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
build_runner: ^2.5.4
Code language: YAML (yaml)
Here:
flutter_testis used for writing and running automated tests.flutter_lintschecks your code for common mistakes and encourages Flutter best practices.build_runnergenerates code automatically for packages that require it, such as JSON serialization or Hive adapters.
Since users never interact with these packages directly, Flutter doesn’t include them in your released application.
How Do You Decide Where a Package Belongs?
A simple question usually gives you the answer:
Does my app need this package while it’s running?
If the answer is yes, place it under dependencies.
If the package is only helping you, the developer, while building, testing, or generating code, place it under dev_dependencies.
For example:
| Package | Section | Reason |
|---|---|---|
http | dependencies | Makes API requests while the app is running. |
provider | dependencies | Manages application state at runtime. |
firebase_core | dependencies | Connects the app to Firebase services. |
shared_preferences | dependencies | Stores user preferences and settings. |
flutter_lints | dev_dependencies | Improves code quality during development. |
build_runner | dev_dependencies | Generates source code automatically. |
flutter_test | dev_dependencies | Runs unit and widget tests. |
Best Practice
As a beginner, you’ll spend most of your time adding packages to the dependencies section because those packages provide the features your app needs.
Use dev_dependencies only for tools that make development easier, such as testing libraries, code generators, and linting packages.
Keeping these two sections organized makes your pubspec.yaml file easier to understand and helps ensure your Flutter project contains only the packages it truly needs.
Understanding Version Constraints
When you add a package to your pubspec.yaml file, you’ll usually specify a version number. For example:
dependencies:
http: ^1.5.0
Code language: YAML (yaml)
The version tells Flutter which release of the package your project should use. Choosing the correct version is important because packages receive regular updates that introduce new features, fix bugs, improve performance, and sometimes make breaking changes.
Instead of always using the latest version, Flutter uses version constraints to determine which package versions are compatible with your project.
What Does the ^ Symbol Mean?
The caret (^) is the most commonly used version constraint in Flutter projects.
For example:
http: ^1.5.0
Code language: YAML (yaml)
This tells Flutter:
Use version 1.5.0 or any newer compatible version that doesn’t introduce breaking changes.
For example, Flutter may install:
1.5.11.6.01.9.3
But it won’t automatically install:
2.0.0
because a new major version may contain breaking changes that could cause your app to stop working.
For most Flutter applications, using the caret (^) is the recommended approach because it keeps your project up to date with bug fixes while reducing the risk of unexpected issues.
Other Common Version Constraints
Although the caret is the most popular choice, Flutter supports several ways to specify package versions.
Use an Exact Version
http: 1.5.0
Code language: YAML (yaml)
Flutter will install exactly version 1.5.0.
This approach provides predictable builds, but you’ll need to manually update the version whenever a newer release becomes available.
Allow Any Version
http: any
Code language: YAML (yaml)
Flutter chooses the best available version that satisfies all package requirements.
While this may seem convenient, it’s generally not recommended because different developers or build environments could end up using different package versions.
Specify a Version Range
You can also define a range of acceptable versions.
http: ">=1.5.0 <2.0.0"
Code language: YAML (yaml)
This tells Flutter to install any version starting from 1.5.0 up to, but not including, 2.0.0.
Version ranges are useful when you need more control over which package updates are allowed.
Which Version Constraint Should You Use?
For most beginners and production Flutter projects, the caret (^) is the best choice.
It provides a good balance between stability and flexibility by allowing compatible updates while protecting your project from major breaking changes.
Use an exact version only when you have a specific reason to lock your project to a single release.
Version ranges are typically used in more advanced scenarios, while any should generally be avoided unless you fully understand its implications.
Best Practice
Whenever you add a new package from pub.dev, use the version recommended on the package’s installation page. In most cases, that version already includes the appropriate caret (^) constraint.
Keeping your package versions up to date helps you benefit from bug fixes, security improvements, and new features.
At the same time, using sensible version constraints makes your Flutter project more stable and easier to maintain over time.
Updating Packages in Flutter
As your Flutter project grows, the packages you use will continue to receive updates. Package authors regularly release new versions to fix bugs, improve performance, add features, and address security issues.
Keeping your dependencies up to date helps ensure your application remains stable and benefits from these improvements.
Flutter provides several commands for managing package updates. Understanding when to use each one will help you avoid confusion and unexpected changes in your project.
Install Packages with flutter pub get
When you add a new package or edit your pubspec.yaml file, the first command you’ll usually run is:
flutter pub getCode language: JavaScript (javascript)
This command does not update your packages to newer versions. Instead, it reads your pubspec.yaml file, downloads any missing packages, and installs versions that satisfy your existing version constraints.
For example, suppose your project contains:
dependencies:
http: ^1.5.0
Code language: YAML (yaml)
If version 1.5.2 is already installed on your computer and still satisfies the version constraint, flutter pub get simply uses that version. It doesn’t search for newer compatible releases every time you run the command.
Update Packages with flutter pub upgrade
If you want Flutter to check for newer compatible package versions, use:
flutter pub upgrade
This command looks at the version constraints in your pubspec.yaml file and installs the newest versions that satisfy those constraints.
For example:
dependencies:
http: ^1.5.0
Code language: YAML (yaml)
If a newer compatible version such as 1.9.0 is available, running flutter pub upgrade updates your project to use that version. However, it won’t automatically install 2.0.0, because that falls outside the allowed version constraint.
Why Didn’t My Package Update?
A common beginner question is:
“I ran
flutter pub upgrade, but my package version didn’t change.”
In many cases, the version constraint is preventing Flutter from installing a newer release.
For example:
dependencies:
provider: ^6.1.5Code language: YAML (yaml)
If the latest available version is 7.0.0, Flutter won’t install it because it’s a new major version that may contain breaking changes.
To use the newer release, you’ll need to update the version constraint in your pubspec.yaml file.
dependencies:
provider: ^7.0.0
Code language: YAML (yaml)
Then run:
flutter pub upgrade
flutter pub get vs flutter pub upgrade
Although these commands are often used together, they serve different purposes.
| Command | Purpose |
|---|---|
flutter pub get | Installs packages using the existing version constraints. |
flutter pub upgrade | Updates packages to the newest compatible versions allowed by those constraints. |
A good way to remember the difference is:
- Use
flutter pub getafter modifying yourpubspec.yamlfile. - Use
flutter pub upgradewhen you want to check for newer compatible package versions.
Best Practice
Avoid updating every package simply because a newer version is available. Before upgrading, read the package’s release notes to check for breaking changes, especially when moving to a new major version.
For most Flutter projects, it’s a good habit to update your dependencies periodically instead of letting them become several versions behind.
Smaller, regular updates are usually much easier to manage than upgrading dozens of packages all at once.
Using Git Packages in Flutter
Most Flutter packages are published on pub.dev, making them easy to install by adding their name and version to your pubspec.yaml file.
However, not every package is available there. Sometimes a developer may only publish the source code on GitHub, or you may want to use a newer version that hasn’t been released to pub.dev yet.
Flutter allows you to install packages directly from a Git repository. This can be useful when you’re testing new features, using a private package, or contributing to an open-source project.
Installing a Package from Git
To install a package from GitHub, replace the version number with a git section in your pubspec.yaml file.
For example:
dependencies:
my_package:
git:
url: https://github.com/username/my_package.git
Code language: YAML (yaml)
After saving the file, run:
flutter pub getCode language: JavaScript (javascript)
Flutter downloads the package directly from the Git repository instead of pub.dev.
Using a Specific Branch
By default, Flutter downloads the package from the repository’s default branch, which is usually main or master.
If you want to use a different branch, you can specify it using the ref property.
dependencies:
my_package:
git:
url: https://github.com/username/my_package.git
ref: develop
Code language: YAML (yaml)
This tells Flutter to download the package from the develop branch instead of the default branch.
Using a specific branch is useful when you’re testing features that haven’t been merged into the main release yet.
Using a Git Tag or Commit
Instead of a branch, you can also reference a specific tag or commit.
For example, using a release tag:
dependencies:
my_package:
git:
url: https://github.com/username/my_package.git
ref: v1.2.0
Code language: YAML (yaml)
Or using a commit hash:
dependencies:
my_package:
git:
url: https://github.com/username/my_package.git
ref: 7b91f8c
Code language: YAML (yaml)
Pinning your project to a tag or commit helps ensure that every developer on your team uses the exact same version of the package.
When Should You Use Git Packages?
Although Git dependencies are powerful, they aren’t needed for most Flutter projects.
They are commonly used when:
- A package hasn’t been published on pub.dev.
- You need a bug fix that hasn’t been released yet.
- You’re testing a new feature from the package author.
- You’re using a private package stored in your organization’s Git repository.
- You’re contributing to an open-source package.
For most public packages, it’s still better to install them from pub.dev because published versions are generally more stable and easier to maintain.
Best Practice
Whenever possible, prefer installing packages from pub.dev instead of directly from Git. Published packages are versioned, thoroughly tested, and easier to update over time.
Use Git dependencies only when you have a specific reason, such as testing an unreleased feature or working with a private repository.
If you do use a Git package, consider referencing a specific tag or commit instead of a moving branch like main. This helps keep your project stable and ensures everyone working on the project uses the same package version.
Using Local (Path) Packages
As you gain experience with Flutter, you may find yourself writing code that you want to reuse across multiple projects.
Instead of copying the same files into every application, you can place that code inside a separate package and reference it locally.
Flutter supports this through path packages, sometimes called local packages. Instead of downloading the package from pub.dev or GitHub, Flutter loads it directly from a folder on your computer.
Project Structure
Suppose you have two projects stored on your computer:
Projects/
├── my_app/
└── my_widgets/
- my_app is your Flutter application.
- my_widgets is a reusable Flutter package that contains custom widgets.
Instead of publishing my_widgets to pub.dev, you can reference it directly using a local path.
Adding a Path Package
Inside your application’s pubspec.yaml file, add the package like this:
dependencies:
my_widgets:
path: ../my_widgets
Code language: YAML (yaml)
The path property tells Flutter where to find the package relative to your current project.
After saving the file, run:
flutter pub getCode language: JavaScript (javascript)
Flutter reads the package directly from your local machine without downloading anything from the internet.
Importing the Package
Once the package has been added successfully, you can import it just like any other dependency.
import 'package:my_widgets/my_widgets.dart';
Code language: Dart (dart)
From this point onward, Flutter treats your local package exactly like a package downloaded from pub.dev.
When Should You Use Path Packages?
Path packages are especially useful during development because they allow you to make changes in one place and reuse those changes across multiple applications.
Some common use cases include:
- Sharing custom widgets between projects.
- Building your own Flutter libraries.
- Developing plugins before publishing them.
- Testing reusable packages locally.
- Working on multiple projects at the same time.
Many companies use local packages to share common UI components, themes, utilities, and business logic across several Flutter applications.
Path Packages vs Git Packages
Although both approaches allow you to use packages outside pub.dev, they solve different problems.
| Feature | Path Package | Git Package |
|---|---|---|
| Stored on your computer | ✅ Yes | ❌ No |
| Requires internet | ❌ No | ✅ Usually |
| Easy for local development | ✅ Yes | ⚠️ Less convenient |
| Good for team collaboration | ⚠️ Limited | ✅ Yes |
During development, path packages are often the easiest choice because changes are immediately available without pushing code to a remote repository.
Best Practice
Use path packages while you’re actively developing or testing reusable code on your own computer.
Once the package becomes stable and needs to be shared with other developers or projects, consider publishing it to pub.dev or storing it in a Git repository.
This workflow gives you the best of both worlds. You can develop quickly using local packages and later distribute them through Git or pub.dev when they’re ready for wider use.
Understanding dependency_overrides
As your Flutter projects become larger, you’ll eventually use packages that depend on other packages.
For example, your app might use the Provider package, while another package in your project depends on a different version of the same library.
Most of the time, Flutter automatically resolves these dependency versions for you. However, there are situations where two packages require different versions of the same dependency, causing a version conflict.
In these cases, you can use dependency_overrides to tell Flutter which version should take priority.
How Does It Work?
Suppose your project already uses the http package, but another package depends on an older version.
Normally, Flutter tries to find a version that satisfies both packages. If that’s not possible, you’ll receive a dependency resolution error when running flutter pub get.
Using dependency_overrides, you can explicitly tell Flutter which version to use.
dependencies:
http: ^1.5.0
dependency_overrides:
http: ^1.5.0
Code language: YAML (yaml)
When Flutter sees the dependency_overrides section, it gives that version higher priority than the versions requested by other packages.
Why Would You Use It?
Although dependency_overrides can solve version conflicts, it’s intended for special situations, not everyday package management.
Some common use cases include:
- Testing a newer version of a package before it’s officially supported.
- Temporarily resolving dependency conflicts between packages.
- Using a locally modified version of a package.
- Testing a bug fix while waiting for an official package update.
For most Flutter applications, you won’t need to use dependency_overrides very often.
Overriding a Local Package
You can also override a package with a local path.
dependency_overrides:
my_widgets:
path: ../my_widgets
Code language: YAML (yaml)
This tells Flutter to use the local version of my_widgets, even if another version is referenced elsewhere.
This approach is particularly useful when developing reusable packages because you can test changes immediately without publishing a new release.
Be Careful When Using Overrides
Although dependency_overrides is a powerful feature, it should be used carefully.
Forcing Flutter to use a version that another package wasn’t designed for can introduce unexpected bugs or runtime errors.
Even if your project compiles successfully, the package may not behave correctly if its expected dependency version has changed.
Whenever possible, it’s better to update the conflicting packages or wait for compatible releases rather than relying on overrides.
Best Practice
Think of dependency_overrides as a temporary solution rather than a permanent one.
If you find yourself leaving overrides in your project for a long time, it’s often a sign that one or more of your dependencies should be updated.
Before releasing your application, review the dependency_overrides section and remove any entries that are no longer necessary.
For most Flutter developers, this section of the pubspec.yaml file will remain empty most of the time.
When you do need it, use it intentionally and understand exactly which package version you’re overriding and why.
Resolving Package Conflicts
As your Flutter application grows, you’ll likely install more packages to add features such as state management, networking, authentication, local storage, and analytics.
While Flutter does an excellent job of managing dependencies automatically, there may be times when two packages require different versions of the same library.
When this happens, Flutter reports a dependency conflict. Although these errors can look intimidating at first, they’re usually caused by version incompatibilities and can often be resolved with a few simple steps.
Why Do Package Conflicts Happen?
Most Flutter packages depend on other packages. For example, imagine your project depends on both Package A and Package B.
- Package A requires
httpversion 1.x - Package B requires
httpversion 2.x
Since Flutter can only install one version of the http package, it has to decide which version to use. If no version satisfies both requirements, dependency resolution fails and Flutter displays an error.
Fortunately, these situations are relatively uncommon in beginner projects because package authors usually keep their dependencies compatible.
A Typical Dependency Resolution Error
When Flutter can’t find compatible package versions, you may see an error similar to this:
Because package_a depends on http ^1.5.0
and package_b depends on http ^2.0.0,
version solving failed.Code language: CSS (css)
The wording may look complicated, but the message is simply telling you that two packages are requesting incompatible versions of the same dependency.
The important part of the error is usually near the end, where Flutter identifies which packages are causing the conflict.
Check for Outdated Packages
Before making changes, it’s a good idea to see whether newer package versions are available.
Run:
flutter pub outdated
This command compares the packages in your project with the latest versions available on pub.dev.
The report shows:
- Your current version
- The newest compatible version
- The latest available version
- Packages that may require an upgrade
Sometimes simply updating one or two packages resolves the conflict automatically.
Update Your Dependencies
If compatible package updates are available, update your version constraints in pubspec.yaml and run:
flutter pub upgrade
Many dependency conflicts disappear after upgrading to package versions that support the same dependency versions.
Use dependency_overrides Only When Necessary
If updating your packages doesn’t resolve the problem, you may temporarily use dependency_overrides to force Flutter to use a specific package version.
However, this should generally be your last option.
Overrides can solve dependency conflicts, but they may also introduce unexpected bugs if a package wasn’t designed to work with the overridden version.
Before using an override, check whether newer package releases are available or whether the package author has already addressed the issue.
Tips for Avoiding Package Conflicts
Although dependency conflicts can’t always be prevented, you can reduce the chances of encountering them by following a few simple practices.
- Keep your packages reasonably up to date.
- Use well-maintained packages with active development.
- Avoid installing multiple packages that provide the same functionality unless necessary.
- Read package documentation before upgrading to a new major version.
- Test your application after updating dependencies.
Following these habits helps keep your Flutter pubspec.yaml dependencies organized and makes dependency resolution much smoother as your project grows.
Best Practice
When you encounter a dependency conflict, don’t immediately start changing version numbers at random. Instead, read the error message carefully, identify the packages involved, and update your dependencies one step at a time.
In most cases, Flutter’s dependency resolver provides enough information to guide you toward the solution.
With a little patience and a systematic approach, even complex package conflicts become much easier to understand and resolve.
Flutter Package Management Best Practices
Managing packages in Flutter isn’t just about adding dependencies to your pubspec.yaml file. Good package management helps keep your projects stable, easier to maintain, and simpler to update as your application grows.
Whether you’re building a small personal app or a production application used by thousands of people, following a few best practices can save you hours of debugging and reduce unexpected dependency issues.
1. Only Add Packages You Actually Need
It’s tempting to install a package for every small feature, but every dependency increases the size and complexity of your project. Before adding a package, ask yourself whether Flutter already provides the functionality through its built-in widgets or libraries.
Fewer dependencies generally mean fewer updates to manage and fewer opportunities for version conflicts.
2. Prefer Well-Maintained Packages
Before installing a package from pub.dev, spend a few moments reviewing it.
Look for signs that the package is actively maintained, such as:
- Recent updates
- Good documentation
- Regular bug fixes
- Community adoption
- Compatibility with recent Flutter versions
Choosing actively maintained packages makes it more likely that future Flutter updates will continue to work smoothly.
3. Keep Your Dependencies Updated
Package authors regularly release updates that fix bugs, improve performance, and address security issues.
Instead of waiting months or years, update your dependencies periodically using:
flutter pub outdated
to check for newer versions, followed by:
flutter pub upgrade
when you’re ready to install compatible updates.
Updating a few packages every so often is usually much easier than upgrading dozens of outdated packages all at once.
4. Read Release Notes Before Major Updates
Major version updates often introduce breaking changes.
Before updating from one major version to another, take a few minutes to read the package’s release notes or migration guide. Understanding what’s changed beforehand can save you from unexpected compilation errors or runtime issues.
5. Remove Unused Dependencies
Over time, your project may accumulate packages that are no longer used.
Review your pubspec.yaml file occasionally and remove dependencies that your application no longer needs. This keeps your project cleaner and reduces unnecessary maintenance.
6. Use dependency_overrides Sparingly
Although dependency_overrides can resolve version conflicts, it should generally be considered a temporary solution.
If your project relies on overrides for long periods, it’s worth investigating whether newer package versions are available or whether the conflicting packages have already been updated.
7. Test Your App After Updating Packages
Even compatible package updates can occasionally introduce unexpected behavior.
After updating your dependencies, run your application, execute your tests, and verify that important features still work correctly before deploying your app.
8. Organize Your pubspec.yaml
As your project grows, your pubspec.yaml file may contain many dependencies, assets, fonts, and configuration settings.
Keeping the file well organized and properly formatted makes it much easier to maintain and reduces the chances of indentation mistakes or configuration errors.
Key Takeaways
Flutter’s package management system makes it easy to add powerful functionality to your applications, but understanding how dependencies work is just as important as knowing how to install them.
In this guide, you’ve learned how to add packages, understand version constraints, update dependencies, use Git and local packages, resolve dependency conflicts, and manage packages more effectively as your projects grow.
As you continue building Flutter applications, these concepts will become part of your everyday workflow, helping you create projects that are easier to maintain, update, and scale with confidence.



