Installation
Add Drift to your Dart or Flutter project
What is it?
Installation is the process of adding Drift to your project and setting up all the necessary dependencies, code generators, and configuration files to start building type-safe databases.
Drift requires multiple packages – the core library, SQLite drivers, and code generation tools. Getting this right is crucial for a smooth development experience.
# pubspec.yaml - The complete Drift setup
dependencies:
drift: ^2.14.0 # Core Drift library
sqlite3_flutter_libs: ^0.5.0 # SQLite for Flutter
path_provider: ^2.1.0 # Find database directory
path: ^1.9.0 # Path manipulation
dev_dependencies:
drift_dev: ^2.14.0 # Code generator
build_runner: ^2.4.0 # Run code generation
What's happening here? - drift: The main package with all the ORM features - sqlite3_flutter_libs: Bundles SQLite with your Flutter app - drift_dev: Development tool that generates Dart code from your table definitions - build_runner: The engine that runs the code generation
Why does it exist?
- Modular Architecture – Different packages for different needs (core, drivers, generators)
- Platform-Specific Drivers – SQLite works differently on mobile, desktop, and web
- Development vs Production – Code generation tools are only needed during development
- Version Management – Keeping core and generator versions in sync prevents issues
- Minimal Production Footprint – dev_dependencies aren't bundled with your app
Step 1: Add Dependencies
The complete pubspec.yaml setup for a Flutter project
name: my_app
description: A Drift-powered Flutter app
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
# Core Drift
drift: ^2.14.0
# SQLite driver for Flutter
sqlite3_flutter_libs: ^0.5.0
# File system helpers
path_provider: ^2.1.0
path: ^1.9.0
# For web support (optional)
# sqlite3: ^2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
# Drift code generator
drift_dev: ^2.14.0
# Build system for code generation
build_runner: ^2.4.0
# For better linting (optional)
flutter_lints: ^3.0.0
Key insights: - Version matching: Keep
driftanddrift_devversions in sync - Flutter-specific: Usesqlite3_flutter_libsfor Flutter, notsqlite3- Web support: Addsqlite3andsqlite3_wasmfor web builds - Desktop support: Most desktop platforms use the same Flutter libs
Step 2: Platform-Specific Setup
Different platforms need different SQLite drivers
Flutter (Mobile & Desktop)
// No additional setup needed - sqlite3_flutter_libs handles it
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
Future<Database> openDatabase() async {
final db = await databaseFactoryIo.openDatabase(
await getDatabasesPath() + '/app.db',
);
return db;
}
Web
// Web requires wasm support
import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3/wasm.dart';
Future<Database> openDatabase() async {
// Use sqlite3_wasm for web
final db = await databaseFactoryWeb.openDatabase(
'/app.db',
);
return db;
}
Pure Dart (CLI)
// Use sqlite3 package directly
import 'package:sqlite3/sqlite3.dart';
Future<Database> openDatabase() async {
final db = await databaseFactoryIo.openDatabase(
'app.db',
);
return db;
}
What's happening here? - Flutter: Uses the bundled native SQLite library - Web: Uses WebAssembly SQLite (sqlite3.wasm) - CLI: Uses system SQLite via dart:ffi - Same API: All platforms use the same Drift API
Step 3: Configure Build Runner
Setting up the code generation system
// Create a file: database.dart
import 'package:drift/drift.dart';
// 1. Define your tables
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
// 2. Define your database
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
}
// 3. Database connection
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final db = await databaseFactoryIo.openDatabase(
await getDatabasesPath() + '/app.db',
);
return NativeDatabase(db);
});
}
Key insights: - @DriftDatabase annotation – Tells Drift which tables to generate - _$AppDatabase – The generated class that extends your database - LazyDatabase – Opens the connection only when first used - NativeDatabase – Wraps the native SQLite connection
Step 4: Run Code Generation
Generate the Drift code
# One-time build
flutter pub run build_runner build
# Watch mode (recommended)
flutter pub run build_runner watch
# Delete old generated files first
flutter pub run build_runner build --delete-conflicting-outputs
# For web-specific builds
flutter pub run build_runner build --define "drift_dev:builder=web"
What's happening here? - build_runner watch – Automatically regenerates code when files change - Generated files – Look for
*.g.dartnext to your source files - Delete conflicting outputs – Fixes issues when generators conflict - Web builds – Requires different configuration for web platform
Step 5: Verify Installation
Test that everything works
// test_database.dart
import 'package:drift/drift.dart';
import 'database.dart';
void main() async {
// 1. Initialize database
final db = AppDatabase();
// 2. Insert a test user
await db.into(db.users).insert(
UsersCompanion(
name: Value('Test User'),
),
);
// 3. Query users
final users = await db.select(db.users).get();
print('Users: $users'); // Should print the user data
// 4. Clean up
await db.close();
}
Expected output:
Users: [User(id: 1, name: Test User)]
Real-World Example
Complete project setup with folder structure
my_app/
├── lib/
│ ├── database/
│ │ ├── database.dart # Main database class
│ │ ├── tables/
│ │ │ ├── users.dart # Users table definition
│ │ │ └── posts.dart # Posts table definition
│ │ └── database.g.dart # GENERATED - don't edit
│ ├── models/
│ │ ├── user.dart # Custom business logic
│ │ └── post.dart
│ └── main.dart
├── test/
│ └── database_test.dart
├── pubspec.yaml
└── analysis_options.yaml # Optional: Lint rules
// lib/database/tables/users.dart
import 'package:drift/drift.dart';
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
DateTimeColumn get createdAt => dateTime()();
}
// lib/database/database.dart
import 'package:drift/drift.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
import 'tables/users.dart';
part 'database.g.dart'; // Important: Include generated part
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 1;
}
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = p.join(dbFolder.path, 'app.db');
// Copy bundled database if needed (optional)
// await copyDatabase(file);
final db = await databaseFactoryIo.openDatabase(file);
return NativeDatabase(db);
});
}
Benefits of this approach: - Clear separation – Tables, database, and models in separate files - Scalable – Easy to add more tables and queries - Testable – Database can be swapped for an in-memory version - Maintainable – Each file has a single responsibility
Best Practices
- Keep drift and drift_dev versions in sync – Mismatched versions cause cryptic errors
- Use
watchmode – Saves time during development by regenerating automatically - Add
*.g.dartto.gitignore– Generated files shouldn't be committed - Use
partdirective – Include generated code withpart 'database.g.dart' - Don't manually edit generated files – They're overwritten every build
- Separate table definitions – Put each table in its own file for better organization
- Use
LazyDatabase– Delay database initialization until needed
Common Mistakes
Mistake 1: Mismatched versions
Wrong:
dependencies:
drift: ^2.14.0
dev_dependencies:
drift_dev: ^2.13.0 # Different version!
Correct:
dependencies:
drift: ^2.14.0
dev_dependencies:
drift_dev: ^2.14.0 # Same version!
Mistake 2: Forgetting the part directive
Wrong:
// database.dart
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
// ...
}
// ERROR: _$AppDatabase not found
Correct:
// database.dart
part 'database.g.dart'; // Must include this!
@DriftDatabase(tables: [Users])
class AppDatabase extends _$AppDatabase {
// ...
}
Mistake 3: Not running code generation
Wrong:
flutter run # Compilation errors!
Correct:
flutter pub run build_runner build # First generate
flutter run # Now it works
Summary
| Step | Command/File | Purpose |
|---|---|---|
| 1 | pubspec.yaml |
Add dependencies |
| 2 | database.dart |
Define tables and database |
| 3 | build_runner watch |
Generate code automatically |
| 4 | flutter run |
Run your app |
| 5 | .gitignore |
Exclude generated files |
Next Steps
Now that Drift is installed, let's set up your first database:
- Setting Up a Database – Create your first Drift database
- Code Generation – Deep dive into build_runner
- Your First Query – Write your first type-safe query
Did You Know?
-
Drift supports both SQLite and PostgreSQL – though SQLite is the default
-
The code generator can produce over 10,000 lines of code – from just a few table definitions
-
Build runner watch mode uses hot-reload – It regenerates code faster than you can switch to your IDE
-
Drift's generator is written in Dart – Unlike many ORMs that use external languages
-
The
sqlite3_flutter_libspackage is ~2MB – It bundles a full SQLite engine with your app