Seed data is application-owned data required for the system to operate, such as the {system} account or built-in roles. Add required seed data in a migration, after the tables and referenced rows exist.
Migrations should not overwrite user-created data. Later migrations may update seeded data as needed.
Constant identifiers
Define constant type-safe identifiers in the domain module and use them in both the application and migration:
pub static HIDDEN_ROLE_ID: RoleId =
RoleId(uuid!("019ba866-d0ac-7573-8a40-1ef73b86b097"));
Stable IDs make migrations deterministic and allow foreign keys to refer to required rows reliably. When creating seeded data, use specific values for even timestamps to ensure produces consistent results.
Ordering and conflicts
Insert parent rows before child rows so foreign keys are satisfied. For a set of required rows, use conflict handling that matches the intended ownership:
groups::Entity::insert_many([groups::ActiveModel {
group_id: Set(ALL_ACCOUNTS_GROUP_ID),
account_id: Set(SYSTEM_ACCOUNT_ID),
group_name: Set("All Accounts".to_owned()),
group_summary: Set(None),
group_detail: Set(None),
requires_display_name: Set(false),
requires_group_password: Set(false),
}])
.on_conflict_do_nothing()
.exec(pool)
.await?;
on_conflict_do_nothing() is appropriate only when leaving an existing row unchanged is safe. If a seed row must have specific values, use an explicit conflict target and verify the existing values before deciding whether to update or fail.
Seed migrations should have a documented down policy. Required application rows are usually retained on rollback rather than deleted.