Seed Command
On this page 8
buddy seed fills your database with realistic test data. There are no seeder
files to write or register: seeding reads the models you already have.
buddy seed
How it works
Two pieces on the model drive everything:
- the
useSeedertrait says how many rows to generate, and optionally pins specific ones - each attribute's
factoryfunction says what a single value looks like
buddy seed walks every model that declares useSeeder, generates that many
rows from the attribute factories, and inserts them. A model without the trait
is never seeded.
// app/Models/Post.ts
import { defineModel } from '@stacksjs/orm'
import { schema } from '@stacksjs/validation'
export default defineModel({
name: 'Post',
table: 'posts',
traits: {
useTimestamps: true,
useSeeder: { count: 100 },
},
belongsTo: ['Author'],
attributes: {
title: {
required: true,
fillable: true,
validation: { rule: schema.string().min(3).max(255) },
factory: faker => faker.lorem.sentence(),
},
content: {
fillable: true,
validation: { rule: schema.string() },
factory: faker => faker.lorem.paragraphs(3),
},
publishedAt: {
fillable: true,
validation: { rule: schema.timestamp() },
factory: faker => faker.date.past().toISOString().slice(0, 19).replace('T', ' '),
},
status: {
fillable: true,
default: 'draft',
validation: { rule: schema.enum(['draft', 'published', 'archived']) },
factory: faker => faker.helpers.arrayElement(['draft', 'published', 'archived']),
},
},
} as const)
The faker argument comes from @stacksjs/faker - you never import it inside a
model.
Options
| Option | Description |
|---|---|
--only [models] | Comma-separated list of models to seed |
--except [models] | Comma-separated list of models to skip |
--include-defaults | Also seed the framework's built-in models |
--fresh | Truncate each table before seeding |
--allow-protected | Seed auth/oauth models on a non-fresh database |
--attach-accounts | Point seeded rows at existing User/Team/Customer rows, not only ones this run seeded |
-p, --project [project] | Target a specific project |
--verbose | Enable verbose output |
buddy db:seed is an alias for buddy seed.
Examples
buddy seed # every model with a useSeeder trait
buddy seed --fresh # truncate first, then re-seed
buddy seed --only Post,Comment # just these two
buddy seed --except User # everything but this one
buddy seed --include-defaults # framework built-ins too
buddy migrate:fresh --seed # drop, re-migrate, seed - the usual reset
Pinning specific rows
fixtures sets specific rows over the generated ones. Keys use the model's
camelCase attribute names and are stored as snake_case columns.
traits: {
useSeeder: {
count: 50,
fixtures: [
{ name: 'Admin', email: 'admin@example.com' },
{ name: 'Support', email: 'support@example.com' },
],
},
},
The first two rows are those two; the remaining 48 come from the factories. If
fixtures is longer than count, every fixture is still inserted.
Rules worth knowing
Tables that already have rows are skipped. Seeding is not additive by
default, so re-running it will not pile up duplicates. Pass --fresh to
truncate and regenerate.
Auth and OAuth models are protected. On a non-fresh database,
OauthClient, OauthAccessToken, OauthRefreshToken and
PersonalAccessToken are skipped, because
re-rolling the Personal Access Client secret would silently invalidate every
live session. --fresh seeds them (you are wiping the database anyway), and
--allow-protected is the explicit override.
Seeded rows only attach to seeded accounts. A belongsTo to User, Team
or Customer is filled from the account rows the same run inserted, so
buddy migrate:fresh --seed gives every issue an author and every review a
reviewer. An account that was in the table before the run may be a real person,
so it is never used, and the key is left empty with a notice saying why. On a
scratch database whose accounts are fixtures from an earlier run, pass
--attach-accounts to use them.
Composite unique indexes are respected. A model-level
indexes: [{ columns: ['team_id', 'user_id'], unique: true }], or a
unique: true foreign key, never gets a repeated combination. When the parents
allow fewer distinct combinations than count asks for, fewer rows are seeded
and the seeder says so, instead of the whole insert failing on a duplicate.
Your models win over the defaults. app/Models/Post.ts replaces the
built-in Post; only one of them seeds. Framework models are excluded entirely
unless you pass --include-defaults.
Foreign keys need their parents. A model that belongsTo another needs the
parent seeded too. If a seed fails on a constraint, check that the parent model
also carries a useSeeder trait.
Environment
Seeding respects APP_ENV and connects to whatever config/database.ts
resolves for it.
APP_ENV=local buddy seed
APP_ENV=staging buddy seed
Be deliberate about anything other than local: --fresh truncates real tables.
Troubleshooting
"No models declare a useSeeder trait - nothing to seed."
Your models have no seeding configured. Add useSeeder: { count: N } to the
ones you want filled, or pass --include-defaults to seed the framework's.
Unique-constraint violations. Faker can repeat values across a large run. Make the factory itself unique rather than relying on chance:
email: {
unique: true,
validation: { rule: schema.string().email() },
factory: faker => `${faker.string.alphanumeric(10)}@example.com`,
},
Foreign-key failures.
The referenced parent has no rows. Give the parent model a useSeeder trait, or
seed it first with --only Parent.
Related
- buddy migrate - run database migrations
- Models - the full
defineModel()reference - Database package - queries, transactions, and seeding