Your Flutter app won’t build correctly until you understand this one file.
Every Flutter project has a file that quietly controls everything — which packages get downloaded, which images get bundled, which Dart version is required, and what version number appears in the App Store. That file is pubspec.yaml. Most beginners treat it as boilerplate and edit it blindly. That’s why they hit cryptic build errors, missing asset crashes, and version conflicts they can’t diagnose.
This guide walks through every section of pubspec.yaml from scratch — what each field does, why it matters, and exactly how to edit it without breaking things. By the end, you’ll understand it well enough to explain it in an interview.
Table of Contents
1. What Is pubspec.yaml?
pubspec.yaml is the central configuration file of every Flutter project. Think of it as your app’s recipe card — Flutter reads it before every build to know exactly what to fetch, what version to use, and what files to bundle. It lives at the root of your project folder and is automatically generated when you run flutter create.
my_flutter_app/
├── lib/
├── android/
├── ios/
├── test/
├── pubspec.yaml ← lives right here, at the root
└── pubspec.lock ← auto-generated sibling (more on this later)
It’s written in YAML (Yet Another Markup Language) — a human-readable format that uses indentation to define structure instead of brackets or tags.
YAML is whitespace-sensitive. It uses spaces — never tabs. One wrong indent level, one stray tab character, and the entire file becomes invalid. Your editor’s Tab key is your enemy here. Set your editor to insert 2 spaces when Tab is pressed.
2. Full File Anatomy (Annotated)
Here is a complete pubspec.yaml showing every major section you’ll encounter. Read through the comments — they explain what each line is actually doing:
name: my_flutter_app # Your project's unique package ID
description: "A new Flutter project."
publish_to: 'none' # Prevents accidental pub.dev publishing
version: 1.0.0+1 # User-visible version + internal build number
environment:
sdk: ^3.10.0 # Minimum Dart SDK version required to build
dependencies:
flutter:
sdk: flutter # The Flutter framework itself — always required
cupertino_icons: ^1.0.8 # Apple-style icons for iOS look-and-feel
http: ^1.2.1 # For making HTTP/API calls
shared_preferences: ^2.2.3 # For persisting simple data locally
dev_dependencies:
flutter_test:
sdk: flutter # Widget and unit testing tools
flutter_lints: ^6.0.0 # Flags code quality issues at write time
flutter:
uses-material-design: true # Enables Material icons and components
assets:
- assets/images/ # Declares a folder of bundled image assets
fonts:
- family: Poppins # The name you'll use in code: fontFamily: 'Poppins'
fonts:
- asset: fonts/Poppins-Regular.ttf
- asset: fonts/Poppins-Bold.ttf
weight: 700
3. name, description, publish_to & version
name & description
name: my_cool_app
description: "A beginner Flutter notes app."
publish_to: 'none'
The name is your project’s official package identifier — Flutter and Dart’s package manager use it internally. It must follow strict naming rules:
| Rule | ✅ Valid | ❌ Invalid |
|---|---|---|
| All lowercase | my_app |
MyApp |
| Underscores only (no spaces or hyphens) | notes_app |
notes-app, notes app |
| No special characters | flutter_demo |
flutter.demo, flutter@demo |
| Starts with a letter | app_one |
1_app |
publish_to: 'none' is a safety flag. It prevents you from accidentally running flutter pub publish and uploading a personal project to the public pub.dev package registry. Always keep this for apps that aren’t intended as public packages.
version
version: 1.0.0+1
| Part | Example | Purpose | Visible to users? |
|---|---|---|---|
major |
1 |
Increment for breaking changes | ✅ Yes (Play Store / App Store) |
minor |
0 |
New features, backwards-compatible | ✅ Yes |
patch |
0 |
Bug fixes only | ✅ Yes |
build number |
+1 |
Internal store upload counter — must increase every upload | ❌ No — internal only |
version: 1.0.0+5 immediately after 1.0.0+4 without changing anything the user sees. The build number just has to be higher than the previous upload — Google Play and the App Store enforce this strictly.
4. environment — SDK Constraints
environment:
sdk: ^3.10.0
This sets the minimum Dart SDK version your project needs to compile. If someone tries to build your app with an older Dart installation, Flutter blocks the build immediately with a clear version error — rather than letting it fail mysteriously later.
Check your currently installed Dart version any time with:
dart --version
# Dart SDK version: 3.x.x (stable)
The ^ symbol here means “this version or any newer non-breaking version.” So ^3.10.0 allows Dart 3.10.x, 3.11.x, 3.12.x, but NOT Dart 4.0.0 (which would be a new major version with potential breaking changes).
5. dependencies — Runtime Packages
This is the section you’ll edit most often. Every package your running app needs goes here. When you run flutter pub get, Flutter downloads them all from pub.dev and makes them importable in your Dart code.
dependencies:
flutter:
sdk: flutter # Always present — this IS the Flutter framework
cupertino_icons: ^1.0.8 # iOS-style icon pack
http: ^1.2.1 # HTTP requests and REST API calls
shared_preferences: ^2.2.3 # Simple local key-value storage
go_router: ^14.0.0 # Declarative navigation / deep linking
cached_network_image: ^3.4.0 # Efficient image loading and caching
How to add a package — step by step
| Step | Action |
|---|---|
| 1 | Search for the package at pub.dev |
| 2 | Copy the install snippet from the “Installing” tab (e.g., http: ^1.2.1) |
| 3 | Paste it under dependencies: with exactly 2 spaces of indentation |
| 4 | Run flutter pub get in your terminal |
| 5 | Import it in your Dart file: import 'package:http/http.dart' as http; |
Or use the CLI shortcut — it edits pubspec.yaml and runs pub get in one command:
flutter pub add http
6. dev_dependencies — Development-Only Packages
Packages listed under dev_dependencies are downloaded during development but not included in your final app build. This keeps your production APK or IPA lean — users never download code that was only needed by the developer.
dev_dependencies:
flutter_test:
sdk: flutter # Widget and unit testing framework
flutter_lints: ^6.0.0 # Highlights bad practices in your editor
mockito: ^5.4.4 # Mock objects for testing
build_runner: ^2.4.9 # Code generation (needed by mockito, freezed, etc.)
| Package type | Goes under | In final app build? | Example |
|---|---|---|---|
| Used at runtime by users | dependencies |
✅ Yes | http, shared_preferences |
| Used only during development | dev_dependencies |
❌ No | flutter_lints, mockito |
7. flutter: — Assets & Fonts
The flutter: section at the bottom is Flutter-specific. This is where you declare which files get physically bundled into the app binary — images, icons, JSON files, custom fonts.
Adding Image Assets
First, create the folder in your project. Then declare it in pubspec.yaml:
# 1. Create the folder structure in your project:
my_flutter_app/
├── assets/
│ ├── images/
│ │ ├── logo.png
│ │ └── banner.jpg
│ └── data/
│ └── config.json
# 2. Then declare them in pubspec.yaml:
flutter:
assets:
- assets/images/logo.png # Declare a single file
- assets/images/ # OR declare the entire folder ✅ (simpler)
- assets/data/config.json # JSON files work too
// 3. Use the asset in your Dart code:
Image.asset('assets/images/logo.png')
// Or load JSON:
final String data = await rootBundle.loadString('assets/data/config.json');
pubspec.yaml but forgetting to physically create the file or folder. Flutter compiles fine but crashes at runtime with Unable to load asset. Always create the file/folder first, then declare it.
Adding Custom Fonts
# 1. Create the fonts folder and download your .ttf files:
my_flutter_app/
├── fonts/
│ ├── Poppins-Regular.ttf
│ ├── Poppins-Bold.ttf
│ └── Poppins-Italic.ttf
# 2. Declare the font family in pubspec.yaml:
flutter:
fonts:
- family: Poppins # The name you'll use in code
fonts:
- asset: fonts/Poppins-Regular.ttf # Regular weight (default)
- asset: fonts/Poppins-Bold.ttf
weight: 700 # Bold
- asset: fonts/Poppins-Italic.ttf
style: italic # Italic
// 3. Use it anywhere in your app:
Text(
'Hello Flutter!',
style: TextStyle(
fontFamily: 'Poppins',
fontWeight: FontWeight.bold, // Uses Poppins-Bold.ttf automatically
fontSize: 20,
),
)
// Or set it globally in your theme:
MaterialApp(
theme: ThemeData(
fontFamily: 'Poppins', // Every Text widget uses Poppins by default
),
)
8. Version Constraint Symbols Explained
When listing packages, the version number after the colon controls which versions Flutter is allowed to install. Getting this wrong causes conflicts when multiple packages need different versions of the same dependency.
| Symbol | Example | Equivalent range | When to use |
|---|---|---|---|
^ (caret) |
^1.2.0 |
>=1.2.0 <2.0.0 |
✅ Default choice — safe, allows minor updates |
>= |
>=1.0.0 |
At least this version, no upper bound | When a minimum version is required |
| Range | '>=1.0.0 <2.0.0' |
Explicitly between two versions | When you need tight control over a known-stable range |
| Exact | 1.2.3 |
Only this exact version | When a known bug exists in newer versions |
any |
any |
No restriction at all | ⚠️ Avoid — can pull in breaking changes |
The caret (^) operator is the right choice for almost everything. It lets Flutter pull in bug fixes and new features, but blocks breaking major-version changes. Use it by default unless you have a specific reason not to.
9. Visual: How pubspec.yaml Fits Into Flutter
You edit pubspec.yaml
│
▼
flutter pub get
│
▼
Flutter reads pubspec.yaml
│
▼
┌─────────────────────────────────────┐
│ Downloads packages from pub.dev │
│ Validates SDK version constraint │
│ Registers asset paths for bundling │
│ Registers custom font families │
│ Writes exact versions to │
│ pubspec.lock │
└─────────────────────────────────────┘
│
▼
flutter run / flutter build
│
▼
✅ App builds with correct packages,
bundled assets, and fonts
What is pubspec.lock? It’s an auto-generated sibling file that pins every package to an exact version number, including all indirect dependencies (packages that your packages depend on). You should always commit pubspec.lock to version control — it guarantees every team member and every CI build gets the exact same package versions, eliminating “works on my machine” bugs.
10. 5 Common Mistakes (and How to Fix Them)
Mistake 1: Wrong indentation
The most common cause of “Invalid YAML” errors. Every nested level must be exactly 2 more spaces than its parent — no tabs, no mixing 1-space and 2-space indents.
# ❌ WRONG — inconsistent indentation
dependencies:
http: ^1.2.1 # 1 space
cupertino_icons: ^1.0.8 # 2 spaces — YAML will reject this
# ✅ CORRECT — 2 spaces consistently throughout
dependencies:
http: ^1.2.1
cupertino_icons: ^1.0.8
Mistake 2: Declaring an asset that doesn’t exist yet
# pubspec.yaml says:
flutter:
assets:
- assets/images/logo.png
# But you never created the file → runtime crash:
# "Unable to load asset: assets/images/logo.png"
# Fix: Create the folder AND the file FIRST, then declare it.
Mistake 3: Forgetting to run flutter pub get after every edit
# Any time you change pubspec.yaml, run this immediately:
flutter pub get
# Without it, your code sees the OLD configuration.
# VS Code and Android Studio can trigger this automatically on save — enable that.
Mistake 4: Using tabs instead of spaces
# YAML strictly forbids tab characters for indentation.
# Error message: "found character 't' that cannot start any token"
# Fix in VS Code: open settings and set:
# "editor.insertSpaces": true
# "editor.tabSize": 2
# Then your Tab key inserts 2 spaces instead of a tab character.
Mistake 5: Putting a runtime package under dev_dependencies
# ❌ WRONG — http is needed when the app runs, not just during development
dev_dependencies:
http: ^1.2.1
# ✅ CORRECT — http belongs under dependencies
dependencies:
http: ^1.2.1
# Symptoms if you get this wrong: app works in debug mode
# but crashes or can't find the package in release builds.
11. Quick Reference: pub Commands
| Command | What it does | When to use it |
|---|---|---|
flutter pub get |
Downloads all packages listed in pubspec.yaml | After every pubspec.yaml change |
flutter pub add <name> |
Adds a package to pubspec.yaml and runs pub get in one step | When adding a new dependency quickly |
flutter pub upgrade |
Upgrades all packages to the latest versions allowed by your constraints | Periodically to pull in bug fixes |
flutter pub outdated |
Lists all packages with newer versions available | When auditing your dependencies |
flutter pub deps |
Prints the full dependency tree (your packages + their packages) | When debugging version conflicts |
flutter pub cache repair |
Re-downloads all cached packages from scratch | When packages are corrupted or pub get fails unexpectedly |
12. Beginner Interview Q&A
These are the pubspec.yaml questions that appear in entry-level Flutter interviews. Clean, confident answers to these will set you apart.
Q1: What is pubspec.yaml used for?
Q2: What is the difference between dependencies and dev_dependencies?
dependencies are packages your app needs to run — they’re included in the final build. dev_dependencies are only needed during development (testing, linting, code generation) and are stripped out of the production build to keep it lean.
Q3: What does flutter pub get do?
pubspec.yaml, downloads all listed packages from pub.dev to your local cache, and writes the exact resolved versions to pubspec.lock. You must run it every time you change pubspec.yaml, or your app still uses the previous configuration.
Q4: What is pubspec.lock and should you commit it?
Q5: What does the ^ symbol mean in ^1.2.0?
>=1.2.0 <2.0.0. It allows Flutter to install any patch or minor update (bug fixes and new features) but blocks major version bumps that might contain breaking changes. It’s the safest and most common version constraint for package dependencies.
See pubspec.yaml in Action
The best way to internalize this is to work through a real project that uses all of it — dependencies, assets, and proper lifecycle management. Our notes app series covers exactly this:
| Article | pubspec.yaml concepts used |
|---|---|
| Notes App Part 1: Project Setup & Core UI | Fresh pubspec.yaml walkthrough, project structure, Flutter SDK dependency |
| Notes App Part 2: Local Persistence & Polish | Adding shared_preferences to dependencies, running flutter pub get |

Pingback: Flutter FutureBuilder Explained: Loading Data from an API (Beginner Tutorial)
Pingback: Flutter Local Storage: SharedPreferences vs Hive vs sqflite — Which to Use?
Pingback: Flutter SharedPreferences Tutorial: Save User Data Without a Database
Pingback: Flutter 3.41 Release Notes (2026): What's New for Beginners - Flutter for beginners