database-seeding
Idempotent seeder patterns with core/reference/dev separation and environment-specific execution using firstOrCreate/updateOrCreate
When & Why to Use This Skill
This Claude skill implements professional-grade idempotent database seeding patterns to ensure consistent and error-free data population across different environments. By utilizing 'firstOrCreate' and 'updateOrCreate' logic, it allows developers to safely run seeders multiple times without causing duplicate record errors or integrity constraint violations, while maintaining a clear separation between core system data, reference catalogs, and development-only test data.
Use Cases
- Initial Environment Setup: Automatically populating a fresh local development environment with essential admin users, roles, and permissions.
- Continuous Integration/Deployment: Safely updating core system configurations and service lists in staging or production environments without disrupting existing data.
- Automated Testing: Seeding specific datasets for browser testing (e.g., Laravel Dusk) to ensure predictable states for QA automation.
- Reference Data Management: Managing large, static datasets like product catalogs or geographical data that need to be updated periodically without duplication.
- Team Synchronization: Ensuring all team members have access to the same standardized set of development data to minimize 'works on my machine' issues.
| name | Database Seeding |
|---|---|
| description | Idempotent seeder patterns with core/reference/dev separation and environment-specific execution using firstOrCreate/updateOrCreate |
Database Seeding
Create and manage idempotent database seeders following PCR Card's core/reference/dev pattern.
When to Use
- Creating new seeders
- Resetting database with test data
- Seeding specific data sets (core, reference, development)
- Understanding seeder organization
- Troubleshooting seeding issues
Quick Commands
# Full database reset with all seeders
./scripts/dev.sh fresh
# Core seeders only (all environments)
./scripts/dev.sh seed:core
# Reference data (trading card catalog)
./scripts/dev.sh seed:reference
# Development test data
./scripts/dev.sh seed:dev
# Specific seeder class
php artisan db:seed --class=MenuSeeder
php artisan db:seed --class=Database\\Seeders\\Development\\DuskTestSeeder
Idempotent Pattern
Critical Rule: All seeders must be safe to run multiple times.
Use firstOrCreate() or updateOrCreate()
// ✅ CORRECT: Idempotent (safe to re-run)
User::firstOrCreate(
['email' => 'admin@pcrcard.com'],
[
'name' => 'PCR Card Admin',
'password' => Hash::make('admin123!'),
]
);
// ✅ CORRECT: Updates existing records
Service::updateOrCreate(
['service_code' => 'BASE_RESTORATION'],
[
'name' => 'Base Restoration',
'base_price' => 3500,
'is_active' => true,
]
);
❌ NEVER Use create()
// ❌ WRONG: Not idempotent (fails on second run)
User::create([
'email' => 'admin@pcrcard.com',
'name' => 'PCR Card Admin',
'password' => Hash::make('admin123!'),
]);
// Second run: SQLSTATE[23000]: Integrity constraint violation
Seeder Organization
Three Categories
| Category | Purpose | When to Run | Example |
|---|---|---|---|
| Core | Essential data for all environments | Always | Roles, Permissions, Services, Admin User |
| Reference | Static catalog data | Optional | Trading card sets, card catalog |
| Development | Test data for local development | Local only | Dusk test users, sample submissions |
Directory Structure
database/seeders/
├── DatabaseSeeder.php # Main orchestrator
├── Core/ # Core seeders (run in all envs)
│ ├── CoreRolesAndPermissionsSeeder.php
│ ├── CoreServicesSeeder.php
│ ├── CoreAdminUserSeeder.php
│ └── MenuSeeder.php
├── Reference/ # Reference data (optional)
│ ├── TradingCardSetsSeeder.php
│ └── TradingCardsSeeder.php
└── Development/ # Dev-only test data
├── DuskTestSeeder.php
├── DevSubmissionsSeeder.php
└── DevSubmissionImagesSeeder.php
Core Seeders
Always Run (All Environments)
1. CoreRolesAndPermissionsSeeder
- 3 roles: Admin, Technician, Customer
- 39 permissions across 11 resources
- Uses Spatie Permission package
2. CoreServicesSeeder
- 10 baseline services (restoration types, addons, shipping)
- Base prices and service codes
- Active/inactive status
3. CoreAdminUserSeeder
- admin@pcrcard.com with Admin + Technician roles
- Password: admin123! (development)
- Email verified, beta access granted
4. MenuSeeder
- 29 Nova admin menu items
- 5 groups: Dashboards, Library, Workflow, Payments, System
- Hierarchical structure (3 levels)
Run via:
./scripts/dev.sh seed:core
# Or manually:
php artisan db:seed --class=Database\\Seeders\\Core\\CoreRolesAndPermissionsSeeder
php artisan db:seed --class=Database\\Seeders\\Core\\CoreServicesSeeder
php artisan db:seed --class=Database\\Seeders\\Core\\CoreAdminUserSeeder
php artisan db:seed --class=MenuSeeder
Reference Data Seeders
Optional Catalog Data
TradingCardSetsSeeder - Card set catalog TradingCardsSeeder - Individual card catalog
When to use: If you need realistic trading card data for testing submissions
Run via:
./scripts/dev.sh seed:reference
Development Seeders
Test Data (Local Only)
DuskTestSeeder - Browser test users:
- dusk.approved@test.com (verified + beta)
- dusk.customer@test.com (verified, no beta)
- dusk.unverified@test.com (not verified, no beta)
- dusk.admin@test.com (admin role)
- dusk.tech@test.com (technician role)
DevSubmissionsSeeder - Sample submissions with cards
DevSubmissionImagesSeeder - Submission-level images:
- Package arrival photos
- Batch photography
- Shipping documentation
Run via:
./scripts/dev.sh seed:dev
Seeder Output Format
Structured Summary
After seeding, DatabaseSeeder provides:
╔═══════════════════════════════════════════════════════════════╗
║ DEVELOPMENT ENVIRONMENT SEEDING ║
╔═══════════════════════════════════════════════════════════════╝
📋 CORE SEEDERS (Always Run)
✅ Roles & Permissions
✅ Services
✅ Admin User
✅ Nova Menus
📚 REFERENCE DATA (Included)
✅ Trading Card Sets
✅ Trading Cards
🔧 DEVELOPMENT SEEDERS (Environment-Specific)
✅ Dusk Test Users
✅ Sample Submissions
✅ Submission Images
✅ Seeding completed successfully!
📝 MANUAL COMMANDS AVAILABLE:
php artisan db:seed --class=Database\\Seeders\\Development\\DuskTestSeeder
php artisan db:seed --class=Database\\Seeders\\Development\\DevSubmissionsSeeder
Environment-Specific Execution
DatabaseSeeder Logic
public function run(): void
{
// CORE: Always run
$this->call([
CoreRolesAndPermissionsSeeder::class,
CoreServicesSeeder::class,
CoreAdminUserSeeder::class,
MenuSeeder::class,
]);
// REFERENCE: Optional (flag-controlled)
if ($this->includeReferenceData()) {
$this->call([
TradingCardSetsSeeder::class,
TradingCardsSeeder::class,
]);
}
// DEVELOPMENT: Local only
if (App::environment('local')) {
$this->call([
DuskTestSeeder::class,
DevSubmissionsSeeder::class,
DevSubmissionImagesSeeder::class,
]);
}
// STAGING: Staging-specific seeders
if (App::environment('staging')) {
// Staging seeders here
}
// PRODUCTION: Production-specific seeders
if (App::environment('production')) {
// Production seeders here (usually none)
}
}
Creating New Seeders
Template
<?php
namespace Database\Seeders\Core; // or Development, Reference
use App\Models\YourModel;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class YourSeeder extends Seeder
{
public function run(): void
{
// Use firstOrCreate for unique records
YourModel::firstOrCreate(
['unique_field' => 'value'],
[
'other_field' => 'value',
'created_at' => now(),
]
);
// Use updateOrCreate to update existing
YourModel::updateOrCreate(
['unique_field' => 'value'],
[
'other_field' => 'new_value',
'updated_at' => now(),
]
);
// For multiple records
$records = [
['name' => 'Record 1', 'code' => 'REC1'],
['name' => 'Record 2', 'code' => 'REC2'],
];
foreach ($records as $record) {
YourModel::updateOrCreate(
['code' => $record['code']],
$record
);
}
$this->command->info('✅ YourModel seeded successfully');
}
}
Artisan Command
# Create seeder
php artisan make:seeder YourSeeder
# Move to appropriate directory
mv database/seeders/YourSeeder.php database/seeders/Core/
# or Development/, Reference/
Common Patterns
Seeding with Relationships
// Create parent with children
$submission = Submission::firstOrCreate(
['submission_number' => 'SUB-001'],
['user_id' => $user->id, 'status' => 'draft']
);
// Attach related models
$submission->cards()->firstOrCreate(
['card_number' => 1],
['description' => 'Card 1']
);
Seeding with Factories (Development Only)
// In Development seeders, you can use factories
if (App::environment('local')) {
User::factory()->count(10)->create();
}
Conditional Seeding
// Only seed if table is empty
if (Service::count() === 0) {
// Seed services
}
// Or use firstOrCreate (handles this automatically)
Common Pitfalls
❌ WRONG: Using create() instead of firstOrCreate()
Role::create(['name' => 'Admin']);
// Second run: duplicate key error
✅ CORRECT:
Role::firstOrCreate(['name' => 'Admin']);
// Second run: no error, returns existing record
❌ WRONG: Not specifying unique keys
Service::updateOrCreate([
'name' => 'Base Restoration',
'base_price' => 3500,
]);
// Creates duplicate if name changed
✅ CORRECT: Use service_code as unique identifier
Service::updateOrCreate(
['service_code' => 'BASE_RESTORATION'],
[
'name' => 'Base Restoration',
'base_price' => 3500,
]
);
❌ WRONG: Hardcoding IDs
Submission::create([
'user_id' => 1, // Assumes user ID is 1
]);
✅ CORRECT: Look up by unique field
$user = User::where('email', 'admin@pcrcard.com')->first();
Submission::firstOrCreate([
'submission_number' => 'SUB-001',
'user_id' => $user->id,
]);
Testing Seeders
Verify Idempotence
# Run seeder twice
php artisan db:seed --class=YourSeeder
php artisan db:seed --class=YourSeeder
# Should complete without errors
# Check record count hasn't doubled
php artisan tinker
>>> YourModel::count();
Reset and Reseed
# Full reset
./scripts/dev.sh fresh
# Check all seeders ran successfully
php artisan tinker
>>> User::where('email', 'admin@pcrcard.com')->exists();
=> true
>>> Service::count();
=> 10
>>> MenuItem::count();
=> 29
Documentation Links
- Database Seeders Guide:
docs/DATABASE-SEEDERS.md - Service Architecture:
docs/SERVICE-ARCHITECTURE.md - Nova Menus Guide:
docs/features/NOVA-MENUS-GUIDE.md - Submission Images Guide:
docs/features/SUBMISSION-IMAGES-GUIDE.md - Laravel Seeding: https://laravel.com/docs/12.x/seeding