ramadan/custom-fresh
| Install | |
|---|---|
composer require ramadan/custom-fresh |
|
| Latest Version: | v2.1.0 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Sep 11, 2026 |
| Links: | GitHub · Packagist |
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).
Related Packages
Powerful PHP database abstraction layer (DBAL) with many features for database s...
Laravel Facade and ServiceProvider for The Lightest PHP database framework to ac...
Framework-agnostic PHP database library with unified API for MySQL, MariaDB, Pos...