Database Seeding Guide
Last Updated: 2026-01-29 Status: Active Audience: Developers Plan Reference: 116-comprehensive-database-seeding.md, 225-notification-workflow-seeder-refactor.md
This guide documents database seeding patterns and factories for the Client Portal application.
Table of Contents
- Overview
- Directory Structure
- JSON Data File System
- Quick Start
- Seeding Profiles
- Factories
- Seeders
- Seeding Strategies
- Test Data
- Commands
- Export Script
Directory Structure
Seeders are organized by purpose:
database/seeders/
├── BaseSeeder.php # Base class with helpers
├── DatabaseSeeder.php # Main entry point (default)
├── MasterSeeder.php # Production orchestrator
├── DemoSeeder.php # Demo data orchestrator
├── FullSeeder.php # Load test orchestrator
├── MinimalSeeder.php # Quick setup orchestrator
├── MasterAuditSeeder.php # Audit orchestrator
├── MasterNotificationSeeder.php # Notification orchestrator
│
├── Traits/ # Reusable seeder traits
│ └── JsonDataLoader.php # JSON file loading & validation
│
├── System/ # Production-required configuration
│ ├── NotificationSeeder.php # Notification types & templates (JSON-backed)
│ ├── WorkflowSeeder.php # Workflow definitions (JSON-backed)
│ ├── MeetingTypeSeeder.php # Meeting types
│ └── ...
│
├── SampleData/ # Development-only fake entities
│ ├── UserSeeder.php
│ ├── ClientSeeder.php
│ ├── ProjectSeeder.php
│ └── ...
│
├── Content/ # Production content (blog, FAQs)
│ ├── BlogSeeder.php
│ ├── CaseStudySeeder.php
│ ├── FaqSeeder.php
│ └── audit-data/ # Audit content data files
│
├── content/ # JSON data files for seeders (Plan 225)
│ ├── notifications/ # Notification data
│ │ ├── channels.json
│ │ ├── types.json
│ │ └── templates/ # By channel, then category
│ ├── workflows/ # Workflow data
│ │ └── definitions/ # By category
│ └── schemas/ # JSON Schema definitions
│
└── Audits/ # Audit-related seeders
├── AuditCategorySeeder.php
├── AuditTemplateSeeder.php
└── Phases/ # 30 phase seeders
Namespace Mapping
| Folder | Namespace | Usage |
|---|---|---|
| Root | Database\Seeders | Orchestrators only |
Traits/ | Database\Seeders\Traits | Reusable seeder traits |
System/ | Database\Seeders\System | Production config |
SampleData/ | Database\Seeders\SampleData | Dev-only entities |
Content/ | Database\Seeders\Content | Marketing content |
Audits/ | Database\Seeders\Audits | Audit templates |
content/ | N/A (JSON data) | JSON data files for seeders |
CLI Invocation
# Orchestrators (at root)
php artisan db:seed --class=DemoSeeder
# Subfolder seeders (use full namespace)
php artisan db:seed --class="Database\\Seeders\\System\\NotificationSeeder"
php artisan db:seed --class="Database\\Seeders\\SampleData\\UserSeeder"
JSON Data File System
Plan 225: Notification and workflow seeders now load data from external JSON files instead of inline PHP arrays. This provides better maintainability, portability between environments, and schema validation.
Data File Structure
database/seeders/Content/
├── notifications/
│ ├── channels.json # Notification channels (email, sms, slack, in_app)
│ ├── types.json # 60+ notification types
│ └── templates/
│ ├── email/
│ │ ├── leads.json # Lead/discovery email templates
│ │ ├── billing.json # Invoice/payment email templates
│ │ └── ...
│ ├── in_app/
│ │ └── general.json # In-app notification templates
│ ├── sms/
│ │ └── ...
│ └── slack/
│ └── ...
├── workflows/
│ └── definitions/
│ ├── billing.json # Billing workflow definitions
│ ├── projects.json # Project workflow definitions
│ ├── communication.json # Meeting/communication workflows
│ └── ...
└── schemas/
├── channels.json # JSON Schema for channels
├── types.json # JSON Schema for notification types
├── templates.json # JSON Schema for templates
├── workflows.json # JSON Schema for workflows
└── action_config.json # JSON Schema for workflow action config
JSON File Format
All JSON data files use a wrapper format with metadata:
{
"_meta": {
"generated_at": "2026-01-29T12:00:00+00:00",
"source_environment": "production",
"command": "php scripts/data/export-notification-workflow-seeders.php --env=production",
"warning": "AUTO-GENERATED - Manual edits will be overwritten on next export",
"record_count": 62,
"checksum": "sha256:abc123..."
},
"data": [
{ ... },
{ ... }
]
}
JsonDataLoader Trait
Seeders use the JsonDataLoader trait for loading and validating JSON files:
<?php
namespace Database\Seeders\System;
use Database\Seeders\BaseSeeder;
use Database\Seeders\Traits\JsonDataLoader;
class NotificationSeeder extends BaseSeeder
{
use JsonDataLoader;
public function run(): void
{
// Load with schema validation
$types = $this->loadAndValidateJson('notifications/types.json', 'types');
// Load from nested directories
$templates = $this->loadJsonNestedDirectory('notifications/templates', 'templates');
// Load with checksum verification
$channels = $this->loadJsonWithChecksum('notifications/channels.json', true);
// Load simple (no validation)
$data = $this->loadJsonData('notifications/channels.json');
}
}
Trait Methods
| Method | Description |
|---|---|
loadJsonData($path) | Load JSON file, extract data array |
loadAndValidateJson($path, $schema) | Load and validate against JSON Schema |
loadJsonWithChecksum($path, $verify) | Load with optional checksum verification |
loadJsonDirectory($dir, $schema) | Load all JSON files from directory |
loadJsonNestedDirectory($dir, $schema) | Load from nested subdirectories |
Fallback Behavior
Both NotificationSeeder and WorkflowSeeder support fallback to inline data:
// In NotificationSeeder.php
private function jsonDataFilesExist(): bool
{
return File::exists(database_path('seeders/Content/notifications/channels.json'))
&& File::exists(database_path('seeders/Content/notifications/types.json'));
}
public function run(): void
{
if ($this->jsonDataFilesExist()) {
$this->progress('Loading notification data from JSON files');
$channels = $this->loadAndValidateJson('notifications/channels.json', 'channels');
} else {
$this->progress('JSON data files not found - using inline data');
$channels = $this->getInlineChannels();
}
}
Schema Validation
JSON Schemas use absolute URIs and validate structure:
{
"$schema": "https://json-schema.org/draft-07/schema#",
"$id": "https://scopeforged.com/schemas/seeder/types.json",
"type": "array",
"items": {
"type": "object",
"required": ["key", "name", "category", "default_channels"],
"properties": {
"key": {
"type": "string",
"pattern": "^[a-z][a-z0-9_.]*$"
},
"name": { "type": "string", "minLength": 1 },
"category": { "type": "string" },
"default_channels": {
"type": "array",
"items": { "enum": ["email", "sms", "slack", "in_app", "push"] }
}
}
}
}
Concurrent Seeding Protection
Seeders use advisory locking to prevent concurrent execution:
private const LOCK_KEY = 'seeder:notifications';
private const LOCK_TIMEOUT = 300;
public function run(): void
{
$lock = Cache::lock(self::LOCK_KEY, self::LOCK_TIMEOUT);
if (! $lock->get()) {
$this->warn('Another seeder is running. Waiting...');
$lock->block(self::LOCK_TIMEOUT);
}
try {
DB::transaction(function () {
// Seed data atomically
});
} finally {
$lock->release();
}
}
Upsert by Key
Workflows and templates use a key column for stable upserts:
// Migration adds key column
Schema::table('workflows', function (Blueprint $table) {
$table->string('key')->nullable()->unique()->after('id');
});
// Seeder upserts by key
if ($key) {
$workflow = Workflow::updateOrCreate(
['key' => $key],
$workflowData
);
} else {
// Legacy: upsert by name
$workflow = Workflow::updateOrCreate(
['name' => $workflowDef['name']],
$workflowData
);
}
Backfilling Keys
Use the backfill script to generate keys for existing records:
# Dry run (preview changes)
php scripts/data/backfill-seeder-keys.php --env=production --dry-run
# Apply changes
php scripts/data/backfill-seeder-keys.php --env=production
# Force (skip confirmation)
php scripts/data/backfill-seeder-keys.php --env=production --force
Quick Start
# Fresh database with standard seeding
php artisan migrate:fresh --seed
# Quick development setup
php artisan db:seed --class=MinimalSeeder
# Full demo data
php artisan db:seed --class=DemoSeeder
# Load testing data
php artisan db:seed --class=FullSeeder
Default Login Credentials:
| Role | Password | |
|---|---|---|
| Admin | admin@example.com | password |
| Client | client@example.com | password |
Seeding Profiles
The application provides multiple seeding profiles for different use cases:
Minimal (Quick Development)
php artisan db:seed --class=MinimalSeeder
Creates minimal data for quick development setup:
- 2 users (1 admin, 1 client)
- 2 clients with 2 projects each
- 2 invoices
Standard (Default)
php artisan db:seed
# or
php artisan migrate:fresh --seed
Creates comprehensive data for testing:
- 6 users (1 admin, 5 clients)
- 5 client organizations
- 13 projects with tasks and files
- 16 invoices with line items
- Project plans and milestones
- Conversations and messages
- Dashboards and analytics
- Workflow templates
- Activity logs
Demo (Sales Presentations)
php artisan db:seed --class=DemoSeeder
Creates rich demo data for presentations:
- All standard data
- Named demo clients (Acme Corp, TechStart, etc.)
- Active conversations and document requests
- Impressive dashboard visualizations
- Complete project lifecycle examples
Full (Load Testing)
php artisan db:seed --class=FullSeeder
Creates large dataset for performance testing:
- 20 clients with 3+ projects each
- 60+ projects total
- Thousands of time entries
- Heavy analytics data
Overview
Database seeding provides:
- Development environment setup
- Testing with realistic data
- Demo/staging data population
- Initial production data (roles, settings)
Seeding Components
| Component | Purpose |
|---|---|
| Factories | Generate model instances with fake data |
| Seeders | Orchestrate data creation |
| Faker | Generate realistic fake data |
Factories
Generate a Factory
php artisan make:factory ClientFactory
php artisan make:factory InvoiceFactory --model=Invoice
Basic Factory Structure
<?php
namespace Database\Factories;
use App\Enums\ClientStatus;
use App\Models\Client;
use Illuminate\Database\Eloquent\Factories\Factory;
class ClientFactory extends Factory
{
protected $model = Client::class;
public function definition(): array
{
return [
'company_name' => fake()->company(),
'contact_name' => fake()->name(),
'email' => fake()->unique()->companyEmail(),
'phone' => fake()->phoneNumber(),
'address' => fake()->address(),
'city' => fake()->city(),
'state' => fake()->stateAbbr(),
'postal_code' => fake()->postcode(),
'country' => 'US',
'status' => fake()->randomElement(ClientStatus::cases()),
'notes' => fake()->optional(0.3)->paragraph(),
];
}
}
Factory States
<?php
namespace Database\Factories;
use App\Enums\ClientStatus;
use App\Models\Client;
use Illuminate\Database\Eloquent\Factories\Factory;
class ClientFactory extends Factory
{
public function definition(): array
{
return [
'company_name' => fake()->company(),
'contact_name' => fake()->name(),
'email' => fake()->unique()->companyEmail(),
'status' => ClientStatus::Active,
];
}
/**
* Active client state.
*/
public function active(): static
{
return $this->state(fn (array $attributes) => [
'status' => ClientStatus::Active,
]);
}
/**
* Inactive client state.
*/
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'status' => ClientStatus::Inactive,
]);
}
/**
* Client with pending approval.
*/
public function pending(): static
{
return $this->state(fn (array $attributes) => [
'status' => ClientStatus::Pending,
'approved_at' => null,
]);
}
/**
* VIP client with special terms.
*/
public function vip(): static
{
return $this->state(fn (array $attributes) => [
'is_vip' => true,
'discount_rate' => 0.15,
'payment_terms' => 60,
]);
}
/**
* Client with complete address.
*/
public function withFullAddress(): static
{
return $this->state(fn (array $attributes) => [
'address' => fake()->streetAddress(),
'city' => fake()->city(),
'state' => fake()->stateAbbr(),
'postal_code' => fake()->postcode(),
'country' => fake()->country(),
]);
}
}
// Usage
Client::factory()->active()->create();
Client::factory()->vip()->withFullAddress()->create();
Client::factory()->inactive()->count(5)->create();
Factory Relationships
<?php
namespace Database\Factories;
use App\Enums\InvoiceStatus;
use App\Models\Client;
use App\Models\Invoice;
use Illuminate\Database\Eloquent\Factories\Factory;
class InvoiceFactory extends Factory
{
public function definition(): array
{
$subtotal = fake()->randomFloat(2, 100, 10000);
$taxRate = 0.10;
$taxAmount = $subtotal * $taxRate;
return [
'client_id' => Client::factory(),
'number' => 'INV-' . fake()->unique()->numerify('######'),
'status' => InvoiceStatus::Draft,
'issue_date' => fake()->dateTimeBetween('-30 days', 'now'),
'due_date' => fn (array $attributes) =>
$attributes['issue_date']->modify('+30 days'),
'subtotal' => $subtotal,
'tax_rate' => $taxRate,
'tax_amount' => $taxAmount,
'total' => $subtotal + $taxAmount,
'notes' => fake()->optional(0.5)->sentence(),
];
}
/**
* Invoice that's been sent.
*/
public function sent(): static
{
return $this->state(fn (array $attributes) => [
'status' => InvoiceStatus::Sent,
'sent_at' => now(),
]);
}
/**
* Paid invoice.
*/
public function paid(): static
{
return $this->state(fn (array $attributes) => [
'status' => InvoiceStatus::Paid,
'sent_at' => now()->subDays(15),
'paid_at' => now()->subDays(5),
'payment_method' => fake()->randomElement(['card', 'bank_transfer', 'check']),
]);
}
/**
* Overdue invoice.
*/
public function overdue(): static
{
return $this->state(fn (array $attributes) => [
'status' => InvoiceStatus::Overdue,
'issue_date' => now()->subDays(45),
'due_date' => now()->subDays(15),
'sent_at' => now()->subDays(45),
]);
}
/**
* Invoice for a specific client.
*/
public function forClient(Client $client): static
{
return $this->state(fn (array $attributes) => [
'client_id' => $client->id,
]);
}
/**
* Create invoice with line items.
*/
public function withItems(int $count = 3): static
{
return $this->afterCreating(function (Invoice $invoice) use ($count) {
InvoiceItem::factory()
->count($count)
->forInvoice($invoice)
->create();
$invoice->recalculateTotals();
});
}
}
// Usage
Invoice::factory()->sent()->create();
Invoice::factory()->paid()->forClient($client)->create();
Invoice::factory()->withItems(5)->create();
Factory Sequences
use Illuminate\Database\Eloquent\Factories\Sequence;
// Alternate between states
Invoice::factory()
->count(6)
->state(new Sequence(
['status' => InvoiceStatus::Draft],
['status' => InvoiceStatus::Sent],
['status' => InvoiceStatus::Paid],
))
->create();
// Sequential values
Client::factory()
->count(3)
->state(new Sequence(
['company_name' => 'Alpha Corp'],
['company_name' => 'Beta Inc'],
['company_name' => 'Gamma LLC'],
))
->create();
// With index access
Invoice::factory()
->count(5)
->state(new Sequence(
fn (Sequence $sequence) => [
'number' => 'INV-' . str_pad($sequence->index + 1, 6, '0', STR_PAD_LEFT),
]
))
->create();
Factory Callbacks
class ClientFactory extends Factory
{
public function definition(): array
{
return [
'company_name' => fake()->company(),
// ...
];
}
/**
* Configure the factory.
*/
public function configure(): static
{
return $this->afterMaking(function (Client $client) {
// Called after make() but before save
$client->slug = Str::slug($client->company_name);
})->afterCreating(function (Client $client) {
// Called after save
activity()->log("Client {$client->company_name} created via factory");
});
}
}
Seeders
Generate a Seeder
php artisan make:seeder ClientSeeder
php artisan make:seeder DemoDataSeeder
Basic Seeder Structure
<?php
namespace Database\Seeders;
use App\Models\Client;
use Illuminate\Database\Seeder;
class ClientSeeder extends Seeder
{
public function run(): void
{
// Create specific clients
Client::factory()->create([
'company_name' => 'Acme Corporation',
'email' => 'contact@acme.com',
]);
// Create random clients
Client::factory()
->count(20)
->create();
// Create clients with relationships
Client::factory()
->count(5)
->has(Project::factory()->count(3))
->has(Invoice::factory()->count(2))
->create();
}
}
Database Seeder (Main Orchestrator)
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Production-safe seeders (roles, permissions, settings)
$this->call([
RoleSeeder::class,
PermissionSeeder::class,
SettingsSeeder::class,
]);
// Development/staging only
if (app()->environment(['local', 'staging'])) {
$this->call([
AdminUserSeeder::class,
ClientSeeder::class,
ProjectSeeder::class,
InvoiceSeeder::class,
]);
}
}
}
Specialized Seeders
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class AdminUserSeeder extends Seeder
{
public function run(): void
{
// Only create if doesn't exist
User::firstOrCreate(
['email' => 'admin@clientportal.test'],
[
'name' => 'Admin User',
'password' => Hash::make('password'),
'email_verified_at' => now(),
'is_admin' => true,
]
);
User::firstOrCreate(
['email' => 'client@clientportal.test'],
[
'name' => 'Demo Client',
'password' => Hash::make('password'),
'email_verified_at' => now(),
'is_admin' => false,
]
);
}
}
<?php
namespace Database\Seeders;
use App\Enums\InvoiceStatus;
use App\Models\Client;
use App\Models\Invoice;
use Illuminate\Database\Seeder;
class InvoiceSeeder extends Seeder
{
public function run(): void
{
$clients = Client::all();
if ($clients->isEmpty()) {
$this->command->warn('No clients found. Run ClientSeeder first.');
return;
}
// Create invoices for each client
$clients->each(function (Client $client) {
// Draft invoices
Invoice::factory()
->count(2)
->forClient($client)
->create();
// Sent invoices
Invoice::factory()
->count(3)
->sent()
->forClient($client)
->create();
// Paid invoices
Invoice::factory()
->count(5)
->paid()
->forClient($client)
->create();
// One overdue invoice
Invoice::factory()
->overdue()
->forClient($client)
->create();
});
$this->command->info('Created invoices for ' . $clients->count() . ' clients.');
}
}
Seeding Strategies
Production-Safe Seeding
<?php
namespace Database\Seeders;
use App\Models\Role;
use Illuminate\Database\Seeder;
class RoleSeeder extends Seeder
{
public function run(): void
{
$roles = [
['name' => 'admin', 'display_name' => 'Administrator'],
['name' => 'manager', 'display_name' => 'Manager'],
['name' => 'client', 'display_name' => 'Client'],
];
foreach ($roles as $role) {
Role::firstOrCreate(
['name' => $role['name']],
$role
);
}
}
}
Idempotent Seeding
<?php
namespace Database\Seeders;
use App\Models\Setting;
use Illuminate\Database\Seeder;
class SettingsSeeder extends Seeder
{
public function run(): void
{
$settings = [
'company_name' => 'Client Portal',
'invoice_prefix' => 'INV-',
'invoice_due_days' => 30,
'tax_rate' => 0.10,
'currency' => 'USD',
];
foreach ($settings as $key => $value) {
Setting::updateOrCreate(
['key' => $key],
['value' => $value]
);
}
}
}
Demo Data Seeder
<?php
namespace Database\Seeders;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Seeder;
class DemoDataSeeder extends Seeder
{
public function run(): void
{
$this->command->info('Creating demo data...');
// Create demo admin
$admin = User::factory()->create([
'name' => 'Demo Admin',
'email' => 'demo@example.com',
'is_admin' => true,
]);
// Create clients with full data
$clients = Client::factory()
->count(10)
->create();
$bar = $this->command->getOutput()->createProgressBar($clients->count());
$clients->each(function (Client $client) use ($bar) {
// Create user for client
$user = User::factory()->create([
'email' => "client-{$client->id}@example.com",
]);
$client->update(['user_id' => $user->id]);
// Create projects
$projects = Project::factory()
->count(rand(1, 5))
->forClient($client)
->create();
// Create invoices
Invoice::factory()
->count(rand(3, 10))
->withItems(rand(2, 5))
->forClient($client)
->create();
$bar->advance();
});
$bar->finish();
$this->command->newLine();
$this->command->info('Demo data created successfully!');
}
}
Truncate Before Seeding
<?php
namespace Database\Seeders;
use App\Models\Client;
use App\Models\Invoice;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class FreshDataSeeder extends Seeder
{
public function run(): void
{
// Disable foreign key checks
DB::statement('SET FOREIGN_KEY_CHECKS=0');
// Truncate tables
Invoice::truncate();
Client::truncate();
// Re-enable foreign key checks
DB::statement('SET FOREIGN_KEY_CHECKS=1');
// Seed fresh data
$this->call([
ClientSeeder::class,
InvoiceSeeder::class,
]);
}
}
Test Data
Using Factories in Tests
<?php
namespace Tests\Feature;
use App\Models\Client;
use App\Models\Invoice;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class InvoiceTest extends TestCase
{
use RefreshDatabase;
public function test_can_view_client_invoices(): void
{
$client = Client::factory()
->has(Invoice::factory()->count(5))
->create();
$user = User::factory()->admin()->create();
$response = $this->actingAs($user)
->get(route('admin.clients.invoices', $client));
$response->assertOk();
$response->assertViewHas('invoices');
}
public function test_overdue_invoices_are_highlighted(): void
{
$invoice = Invoice::factory()->overdue()->create();
$this->assertTrue($invoice->isOverdue());
$this->assertEquals('overdue', $invoice->status->value);
}
public function test_invoice_total_calculation(): void
{
$invoice = Invoice::factory()
->withItems(3)
->create();
$expectedSubtotal = $invoice->items->sum('total');
$expectedTax = $expectedSubtotal * $invoice->tax_rate;
$expectedTotal = $expectedSubtotal + $expectedTax;
$this->assertEquals($expectedSubtotal, $invoice->subtotal);
$this->assertEquals($expectedTotal, $invoice->total);
}
}
Factory Helpers in Tests
<?php
namespace Tests;
use App\Models\User;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected function createAdmin(): User
{
return User::factory()->admin()->create();
}
protected function createClientUser(): User
{
return User::factory()
->hasClient()
->create();
}
protected function actingAsAdmin(): static
{
return $this->actingAs($this->createAdmin());
}
}
Commands
Running Seeders
# Run all seeders
php artisan db:seed
# Run specific seeder
php artisan db:seed --class=ClientSeeder
# Fresh migration + seed
php artisan migrate:fresh --seed
# Seed in production (requires confirmation)
php artisan db:seed --force
Custom Seed Command
<?php
namespace App\Console\Commands;
use Database\Seeders\DemoDataSeeder;
use Illuminate\Console\Command;
class SeedDemoData extends Command
{
protected $signature = 'app:seed-demo
{--fresh : Truncate tables before seeding}
{--clients=10 : Number of clients to create}';
protected $description = 'Seed demo data for development';
public function handle(): int
{
if (app()->environment('production')) {
$this->error('Cannot run demo seeder in production!');
return Command::FAILURE;
}
if ($this->option('fresh')) {
$this->call('migrate:fresh');
}
$this->call('db:seed', [
'--class' => DemoDataSeeder::class,
]);
$this->info('Demo data seeded successfully!');
return Command::SUCCESS;
}
}
Best Practices
Do
- Use factories for all model creation in tests
- Create meaningful factory states
- Make seeders idempotent (safe to run multiple times)
- Separate production and development seeders
- Use
firstOrCreatefor required data - Provide realistic fake data
- Document factory states
Don't
- Hardcode IDs in factories
- Create circular dependencies
- Run development seeders in production
- Skip factories and create models manually
- Forget to handle relationships
- Use random data for required lookups
Factory Checklist
- All model attributes defined
- Appropriate states for common scenarios
- Relationships properly configured
- Sequences for unique values
- Callbacks for complex setup
- Used in tests consistently
Export Script
Plan 225: The export-notification-workflow-seeders.php script exports notification and workflow data from the database to JSON files.
Usage
# Export all data from production
php scripts/data/export-notification-workflow-seeders.php --env=production
# Export only notifications
php scripts/data/export-notification-workflow-seeders.php --env=production --notifications-only
# Export only workflows
php scripts/data/export-notification-workflow-seeders.php --env=production --workflows-only
# Dry run (show what would be exported)
php scripts/data/export-notification-workflow-seeders.php --env=production --dry-run
# Compare database with existing files
php scripts/data/export-notification-workflow-seeders.php --env=production --compare
# Validate database integrity before export
php scripts/data/export-notification-workflow-seeders.php --env=production --validate
# Skip confirmation prompts (for CI/CD)
php scripts/data/export-notification-workflow-seeders.php --env=production --force
Script Options
| Flag | Description |
|---|---|
--env=<environment> | Override the application environment |
--notifications-only | Export only notification data |
--workflows-only | Export only workflow data |
--dry-run | Show what would be exported without writing |
--compare | Compare database state with existing files |
--validate | Validate database integrity before export |
--force | Skip confirmation prompts |
Output
The script exports to:
database/seeders/Content/notifications/channels.jsondatabase/seeders/Content/notifications/types.jsondatabase/seeders/Content/notifications/templates/{channel}/{category}.jsondatabase/seeders/Content/workflows/definitions/{category}.json
Example Output
===========================================
Export Notification & Workflow Seeders
Plan 225
===========================================
Environment: production
Database: client_portal
Mode: EXPORT
Exporting Notifications...
--------------------------------------------------
Written: database/seeders/Content/notifications/channels.json
Written: database/seeders/Content/notifications/types.json
Written: database/seeders/Content/notifications/templates/email/leads.json
Written: database/seeders/Content/notifications/templates/email/billing.json
...
Exporting Workflows...
--------------------------------------------------
Written: database/seeders/Content/workflows/definitions/billing.json
Written: database/seeders/Content/workflows/definitions/projects.json
...
===========================================
Summary
===========================================
Export complete!
Files written: 60
Total records: 322
Workflow: Syncing Environments
-
Export from production:
php scripts/data/export-notification-workflow-seeders.php --env=production --force -
Commit JSON files:
git add database/seeders/Content/ git commit -m "Export notification/workflow data from production" -
On staging/development, run seeders:
php artisan db:seed --class="Database\\Seeders\\System\\NotificationSeeder" php artisan db:seed --class="Database\\Seeders\\System\\WorkflowSeeder"
Compare Mode
Compare database with existing files without making changes:
php scripts/data/export-notification-workflow-seeders.php --env=production --compare
Output:
Channels:
No changes detected.
Types:
Added (2):
+ new.notification.type
+ another.new.type
Modified (1):
~ existing.type
Workflows (billing):
Removed (1):
- old.workflow.key