Asset Management in Flutter: Images, JSON, Fonts, and Files Done Right
Declare assets in pubspec, load them through rootBundle or DefaultAssetBundle, and ship resolution-aware images, fonts, and package assets efficiently.
Published on • November 5, 2026
AI Assistant

Assets are the files Flutter bundles into your app and exposes at runtime — JSON configs, icons, fonts, audio, and images. Getting the declarations right in pubspec.yaml, and loading them through the right API, is the difference between a tidy bundle and a mystery Error: unable to find directory entry in pubspec.yaml.
Declaring assets in pubspec.yaml
The official assets and images guide shows the two basic forms:
flutter:
assets:
- assets/my_icon.png
- assets/background.png
- directory/
- directory/subdirectory/
A trailing / includes every file directly inside that directory — subdirectories need their own entry, with resolution-aware image variants as the only exception. Indentation matters: assets: must sit exactly two spaces below flutter: or the build fails with “unable to find directory entry”. At build time Flutter packs these files into the asset bundle.
Since Flutter 3.41.0 you can also scope an asset to platforms, which keeps large desktop-only files out of mobile builds:
flutter:
assets:
- path: assets/desktop_config.json
platforms:
- macos
- windows
Loading: AssetBundle, rootBundle, DefaultAssetBundle
Everything loads through an AssetBundle with two main methods: loadString() for text and load() for binary. Each app has a global rootBundle from package:flutter/services.dart, but inside a widget tree the guide recommends DefaultAssetBundle.of(context) so a parent can substitute a different bundle for localization or testing:
import 'package:flutter/services.dart' show rootBundle;
// Outside a widget context:
Future<Map<String, dynamic>> loadConfig() async {
final raw = await rootBundle.loadString('assets/config.json');
return jsonDecode(raw) as Map<String, dynamic>;
}
// Inside a widget tree:
final bundle = DefaultAssetBundle.of(context);
final data = await bundle.load('assets/model.bin'); // ByteData
For images, prefer the widget layer: Image.asset('assets/background.png') or const Image(image: AssetImage('assets/background.png')), which plugs into the framework’s image cache.
Resolution-aware images
AssetImage maps a logical asset onto the variant closest to the device pixel ratio, using this folder layout:
.../my_icon.png (mdpi baseline)
.../1.5x/my_icon.png (hdpi)
.../2.0x/my_icon.png (xhdpi)
.../3.0x/my_icon.png (xxhdpi)
.../4.0x/my_icon.png (xxxhdpi)
A device pixel ratio of 1.8 picks the 2.0x file; 2.7 picks 3.0x. If a 72×72 baseline should render the same physical size at higher density, the 3.0x variant must be 216×216 — Flutter scales both down to 72 logical pixels. Declare only the main asset (or its parent directory) in pubspec.yaml; Flutter bundles the variants automatically. Resolution awareness requires a MaterialApp or CupertinoApp ancestor so MediaQuery is available.
Fonts
Fonts live in the same manifest under their own key, as the custom font cookbook shows:
flutter:
fonts:
- family: Raleway
fonts:
- asset: fonts/Raleway-Regular.ttf
- asset: fonts/Raleway-Italic.ttf
style: italic
- family: RobotoMono
fonts:
- asset: fonts/RobotoMono-Regular.ttf
- asset: fonts/RobotoMono-Bold.ttf
weight: 700
Flutter supports .ttf, .otf, and .ttc collections — but not .woff/.woff2 on desktop. Weight and style are never inferred from filenames: declaring weight: 700 is what makes FontWeight.w700 map to the real bold file instead of a simulated one.
Assets from package dependencies
To load an image that belongs to a package, pass the package argument:
return const AssetImage('icons/heart.png', package: 'my_icons');
For assets under a package’s lib/ folder that the package does not declare itself, the app’s pubspec must list them with the packages/ prefix (- packages/fancy_backgrounds/backgrounds/background1.png) — the lib/ part is implied and must not appear in the path.
Size and performance guidance
Bundled assets ship in every build, so treat them as download weight: compress images, keep only the variants you need, and use platform-scoped assets (3.41.0+) or deferred components for large, optional content. Load text with loadString only when you need it, prefer DefaultAssetBundle.of(context) over the global rootBundle inside widgets, and let Image.asset do resolution selection rather than shipping one oversized PNG for every screen.
Takeaway: Declare assets and fonts precisely in pubspec.yaml, load them through DefaultAssetBundle/rootBundle and AssetImage, and lean on resolution-aware variants plus platform-scoped entries to keep bundles small and sharp.