Seeding
Database seeders let you populate your SQLite tables with test or default data using simple seeder classes and expressive model factories.
- Introduction
- Running Seeders
- Writing Seeders
- The DatabaseSeeder
- Writing Factories
- Using Factories
- CLI Commands
Introduction
Magic includes the ability to seed your database with data using seed classes. All seed classes are stored in the lib/database/seeders directory. Seeders are useful for populating your database with test data during development.
Running Seeders
The primary way to seed your database is using Magic.seed() in your main.dart:
void main() async {
await Magic.init(...);
// Seed database in development
if (kDebugMode) {
final count = await DB.table('users').count();
if (count == 0) {
await Magic.seed([DatabaseSeeder()]);
}
}
runApp(MagicApplication(...));
}
You may also seed specific seeders directly:
await Magic.seed([UserSeeder(), PostSeeder()]);
Writing Seeders
Generate a seeder using the CLI:
dart run magic:artisan make:seeder UserSeeder
A seeder class contains a single run method which is called when the seeder is executed:
import 'package:magic/magic.dart';
import '../factories/user_factory.dart';
class UserSeeder extends Seeder {
@override
Future run() async {
await UserFactory().count(50).create();
}
}
The DatabaseSeeder
The DatabaseSeeder is the entry point for all seeders. Use call() to run multiple seeders in order:
import 'package:magic/magic.dart';
import 'user_seeder.dart';
import 'post_seeder.dart';
import 'comment_seeder.dart';
class DatabaseSeeder extends Seeder {
@override
Future run() async {
await call([
UserSeeder(),
PostSeeder(),
CommentSeeder(),
]);
}
}
Writing Factories
Factories define how to generate fake data for a model. Generate a factory using the CLI:
dart run magic:artisan make:factory User
A factory extends Factory and defines the model's default attributes:
import 'package:magic/magic.dart';
import '../../app/models/user.dart';
class UserFactory extends Factory {
@override
Factory newFactory() => UserFactory();
@override
User newInstance() => User();
@override
Map definition() {
return {
'name': faker.person.name(),
'email': faker.internet.email(),
'password': 'password123', // Use a consistent test password
'is_active': true,
};
}
}
newFactory() returns a fresh factory of the same class. state() and count() never change the factory they are called on; they return a copy built through newFactory(), so a shared base factory can branch without one branch leaking into the other.
Like Laravel, a factory fills its models unguarded: id, timestamps, and any key missing from the model's fillable list are kept, so a factory can build a model exactly as the API returns it. Outside a factory, fill() still honours fillable and guarded. The same switch is available directly:
final user = Model.unguarded(() => User()..fill({'id': 1, 'name': 'Taylor'}));
Model.unguarded() restores the guard when the callback returns or throws, and it needs a synchronous callback: an async one would keep running after the guard is back on.
Available Faker Methods
Magic includes the Faker library for generating realistic test data:
// Person
faker.person.name() // "John Doe"
faker.person.firstName() // "John"
faker.person.lastName() // "Doe"
// Internet
faker.internet.email() // "[email protected]"
faker.internet.userName() // "johndoe123"
faker.internet.domainName() // "example.com"
faker.internet.uri('https') // "https://example.com/path"
// Lorem
faker.lorem.word() // "lorem"
faker.lorem.sentence() // "Lorem ipsum dolor sit amet."
faker.lorem.sentences(3) // Multiple sentences
// Address
faker.address.city() // "New York"
faker.address.country() // "United States"
faker.address.streetAddress() // "123 Main St"
// Date
faker.date.dateTime() // Random DateTime
faker.date.dateTimeBetween(start: startDate, end: endDate)
// Random
faker.randomGenerator.integer(100) // 0-100
faker.randomGenerator.boolean() // true/false
faker.randomGenerator.element(['a', 'b', 'c']) // Random from list
Using Factories
// Create a single model (saved to DB)
final user = (await UserFactory().create()).first;
// Create 50 models
final users = await UserFactory().count(50).create();
// Create with state override
final admins = await UserFactory()
.state({'role': 'admin'})
.count(5)
.create();
// Create without saving (in-memory only)
final users = UserFactory().count(10).make();
// Attribute maps only, one per model (a request payload, a faked response)
final payloads = UserFactory().count(3).raw();
make() leaves each model with exists false and every attribute dirty, as Laravel does. raw() always returns a list: a single map when no count() was set.
Factory States
Write reusable states as an extension on Factory. state() and count() return Factory, so an extension method chains before or after either of them:
extension UserFactoryStates on Factory {
Factory admin() => state({'role': 'admin'});
Factory unverified() => state({'email_verified_at': null});
Factory inactive() => state({'is_active': false});
}
// Usage
await UserFactory().admin().count(3).create();
await UserFactory().count(10).unverified().create();
A method declared on UserFactory itself would be unreachable after the first state() or count() call, whose result is typed Factory.
CLI Commands
Create Seeder
dart run magic:artisan make:seeder UserSeeder
dart run magic:artisan make:seeder User # Auto-appends 'Seeder'
Output: Creates lib/database/seeders/user_seeder.dart
Create Factory
dart run magic:artisan make:factory User
dart run magic:artisan make:factory UserFactory # Accepts either form
Output: Creates lib/database/factories/user_factory.dart
[!TIP] Use the
--allflag withmake:modelto generate model, migration, seeder, and factory in one command:dart run magic:artisan make:model User --all