Public vs. Private File Storage in Laravel
In a Laravel application, the storage choice determines whether a browser can fetch a file directly or must ask Laravel to check…
When a Laravel package needs its own database tables, its maintainers must decide how the host application will find the package’s migration files. A migration is a PHP file that describes a database change, such as creating a table. Laravel runs migrations that have not yet been applied with php artisan migrate.
A package’s service provider is the PHP class that registers the package’s resources with Laravel. In the provider, you can register the migration directory so Laravel runs files from the installed package, or let users publish those files into the host application. Choose an approach, name migrations and database objects so users can identify which package owns them, and explain what users must do after an upgrade.
For migrations that should stay in the installed package, call loadMigrationsFrom in the service provider’s boot method:
public function boot(): void
{
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
The path names the package’s migration directory. Laravel registers it with its migration runner, so users can run php artisan migrate without copying the files into their project. Laravel’s Laravel 9 package documentation describes this approach, and the Laravel 13 service provider source still includes loadMigrationsFrom.
Laravel’s package documentation has emphasized different approaches across versions: Laravel 9 and 10 documentation describes direct registration, while Laravel 11 documentation demonstrates publishing migrations. That change in emphasis does not mean loadMigrationsFrom was removed. Check the Laravel versions your package supports, then test the chosen method against those versions.
Publishing copies migration files into the host application’s database/migrations directory. Use this approach when users need to inspect or customize the files in their own code repository. Laravel’s publishesMigrations method also updates migration filename timestamps when it publishes the files, as shown in the Laravel package documentation.
Register a dedicated publish tag so users can publish migrations without selecting unrelated package resources:
public function boot(): void
{
$this->publishesMigrations([
__DIR__.'/../database/migrations' => database_path('migrations'),
], 'courier-migrations');
}
Users then run the package-specific command and apply the migrations:
php artisan vendor:publish --tag=courier-migrations
php artisan migrate
Tell users the exact tag or service provider name to pass to vendor:publish. A generic command can prompt them to choose among resources or publish more than the migrations.
Keep the two delivery methods distinct. If Laravel loads migrations from the package while users also publish the same files into the host project, test how migration discovery handles both copies before shipping that configuration. Do not assume that publishing merges later package changes into files users have already copied or edited.
Use Laravel’s anonymous migration classes, which define migrations without globally named PHP classes, to avoid class-name collisions. Laravel shows this form in its migration documentation. Start each migration filename with a timestamp, then add a descriptive name specific to the package. Laravel orders migrations by their timestamps, and the migration repository tracks applied migration names. Distinctive names help users and maintainers see which package owns each change.
Use recognizable names for package-owned tables. Apply the same care to indexes and constraints. For example, a package-specific prefix helps distinguish a package table from a similarly named table created by the host application or another package. Laravel does not prescribe a universal prefix format, so document the naming pattern your package uses.
Timestamp changes during publication reduce the chance that copied migration files share timestamps. They do not guarantee unique migration names or database object names, so maintain those distinctions yourself.
Tell users whether Laravel loads migrations from the installed package or whether they must publish files. For published migrations, include the exact publish command and tag. When a package release adds a migration, state that users should publish the new file and run php artisan migrate. If an update adds no migration, say whether any database command is required.
Laravel records the names of migrations users have applied in its migration repository. Editing an old migration does not make Laravel run it again. Put later schema changes in new migration files, and explain any manual data conversion, renamed columns, changed defaults, or deployment ordering users must handle. The migration repository API describes the records Laravel uses to track migration history.
If users publish migration files and edit their local copies, explain how to handle package updates. Name any new migrations, describe what they change, and list any manual steps. Do not promise that publishing will merge package changes into an existing edited file.
Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.
Take a look at vroni.com