Custom Fresh
A Laravel package to specify the tables that you do not want to drop while refreshing the database.
#Custom Fresh
Custom Fresh offers fine-grained control over migrations within your Laravel project, enabling you to select which tables will not be dropped when refreshing the database.
[!TIP] Always consider upgrading the package to the latest version, which is the most stable release.
#Installation
Install the package by using Composer:
composer require ramadan/custom-fresh
(Optional) publish the config file:
php artisan vendor:publish --tag=custom-fresh-config
#Usage
After installing the package, you will see a new fresh:custom command.
[!NOTE] Since
v1.2.0, the package scans your migration files more accurately, including nested folders, custom--pathlocations, and package migration paths registered through Laravel.
#Refreshing migrations
You can exclude specific tables while refreshing the database inside your project:
php artisan fresh:custom users,foo
The same can be expressed with the --keep option (which can be combined with the positional argument):
php artisan fresh:custom --keep=users,personal_access_tokens
When nothing is passed and the config is empty, an interactive picker lists the discovered tables.
If a kept table is created in the same migration file as other tables (Laravel's default users / password_reset_tokens / sessions file), those sibling tables are preserved too.
[!IMPORTANT] Do not forget always to use the
-hof the command to check out all supported options.
#Glob patterns
Anything containing *, ?, or […] is expanded with fnmatch against the database tables, so you can preserve whole groups at once:
php artisan fresh:custom "users,oauth_*,telescope_*"
#Drop only some tables
You can invert the default and drop just a few tables, while everything else is preserved:
php artisan fresh:custom --drop=posts,comments
The --drop option can be combined with --keep or --preset. Explicit --drop always wins.
#Keep tables without migrations
You can preserve tables that have no migration file (common for Laravel 11+ sessions, cache, or jobs):
php artisan fresh:custom --keep=users --keep-raw=sessions,cache
The same list can be set in keep_without_migrations inside the config.
#Except
You can temporarily drop a table that is otherwise always kept:
php artisan fresh:custom --except=users
#Presets
You can group tables in the config and apply them by name:
php artisan fresh:custom --preset=auth
'presets' => [
'auth' => ['users', 'password_reset_tokens', 'sessions', 'personal_access_tokens'],
],
#Pending alters
By default, new alter migrations that touch a kept table still run, so add_phone_to_users_table is applied even when users is preserved.
Use --freeze-schema to mark every migration for kept tables as already run:
php artisan fresh:custom users --freeze-schema
#Related tables
Pass --with-related to also preserve tables linked by foreign keys:
php artisan fresh:custom --keep=posts --with-related
The command warns you when a kept table references a table that would be dropped (or the other way around).
#Multiple connections
Pass --database= to target a non-default connection. The connection is also forwarded to the migrate command:
php artisan fresh:custom users --database=tenant
Per-connection overrides can be set under the connections key in the config.
#Dry run
Use --explain to preview exactly what would happen without dropping a single table:
php artisan fresh:custom users --explain
It prints the resolved connection, the tables that would be preserved, the tables that would be dropped, the migration rows that would be re-inserted, and any pending alters that will still run.
The same plan can be printed as JSON:
php artisan fresh:custom users --explain --json
#List tables
Use --list to inspect the tables the scanner sees and the migration files that touch them:
php artisan fresh:custom --list
The same can be expressed as JSON with --json:
php artisan fresh:custom --list --json
#Seeding
Use --seed to re-run DatabaseSeeder after migrate. If you preserved tables with unique columns, that often inserts duplicates.
Use --seed-fresh to seed only the dropped tables, through the table_seeders map in the config:
php artisan fresh:custom --keep=users --seed-fresh
'table_seeders' => [
'posts' => Database\Seeders\PostSeeder::class,
],
#Views and types
Pass --drop-views and --drop-types to match Laravel's migrate:fresh when leftover views or PostgreSQL types would break the next run:
php artisan fresh:custom users --drop-views --drop-types
The --drop-types option is supported on PostgreSQL only.
#Replace migrate:fresh
Set replace_migrate_fresh to true in the config. Then php artisan migrate:fresh delegates to fresh:custom whenever always_keep, patterns, or keep_without_migrations is set. Otherwise Laravel's original command still runs.
#Configuration
Publishing the config (see above) gives you config/custom-fresh.php:
return [
'always_keep' => ['users', 'personal_access_tokens'],
'patterns' => ['oauth_*', 'telescope_*'],
'keep_without_migrations' => ['sessions', 'cache'],
'presets' => [
'auth' => ['users', 'password_reset_tokens', 'sessions', 'personal_access_tokens'],
],
'table_seeders' => [
'posts' => Database\Seeders\PostSeeder::class,
],
'connections' => [
'tenant' => [
'always_keep' => ['tenant_settings'],
],
],
'confirm_in' => ['production', 'staging'],
'replace_migrate_fresh' => false,
];
always_keep— tables that are preserved on every run, even if you don't list them on the command line.patterns— glob patterns expanded against the database on every run.keep_without_migrations— tables preserved even when they have no migration file.presets— named groups applied with--preset=.table_seeders— dropped-table seeders used by--seed-fresh.connections— extra lists merged when--database=matches the key.confirm_in— environments where the command must ask for confirmation. Use--forceto bypass.replace_migrate_fresh— letmigrate:freshhonor the lists above.
#Events
Three events are dispatched during a run, perfect for backups, audit logs, or Slack notifications:
Ramadan\CustomFresh\Events\RefreshingDatabase— fired before any destructive work, with the resolved preserve list and migration rows.Ramadan\CustomFresh\Events\TablesDropped— fired right after the drop step, with both the preserved and dropped tables.Ramadan\CustomFresh\Events\DatabaseRefreshed— fired after the underlyingmigratefinishes successfully.
#Credits
#Support me
#License
The MIT License (MIT).