Skip to main content
Maintaining a package that uses the database over time requires more than a first-time install. You also need a process for delivering changes to users whose tables already exist. Design publishing, running, and tracking migration history as separate concerns. This page builds on Laravel Package Development and walks through Laravel 13’s ServiceProvider, VendorPublishCommand, and Migrator. Framework references point to v13.34.0.

Copy to the application, or load from the package?

publishesMigrations() only registers a source and destination as publishable. Booting the provider does not copy files or run any SQL. loadMigrationsFrom(), on the other hand, registers a search path with the Migrator. A normal migrate run will include files in that path, but booting the provider alone does not run them. If your design lets users adjust table names or columns before running, publishing is a good candidate. If the package owns the schema and does not expect users to edit the files, consider loading directly. The two provider examples below are alternatives.
Avoid publishing a migration while also loading it directly. When the timestamp changes on publish, the source and the copy are tracked as separate migrations, and the same table creation may run twice.

Implementing the publish approach

Give the migrations a package-specific tag so users can publish them separately from other resources.
On first install, copy the files by specifying the provider and tag, review the contents, then run them. When both are given, Laravel selects the publishable items with that tag that belong to that provider.

Timestamp changes depend on configuration

The official documentation describes migration timestamps being updated to the current date and time on publish. However, the implementation of ServiceProvider::publishesMigrations() only adds the source to the set of timestamp-updated paths when database.migrations.update_date_on_publish is enabled. The fallback when reading this setting is false. The default Laravel 13 application ships with the following in config/database.php. For applications carried over from older structures, check that the setting exists.
VendorPublishCommand then rewrites the date when the file matches the real path of a registered source and the destination name has the YYYY_MM_DD_HHMMSS_ format. Starting from the time the command began, it adds one second per file. If the name lacks this format, that step does not prepend a date.
The date after publishing above is illustrative. The actual file name depends on when you publish.
Timestamp updates depend on both the package’s registration and the consuming application’s configuration. Do not change this setting globally from the package’s provider; document the requirement in your installation instructions. If the application uses config caching, the cache must also be rebuilt after changing the setting.

Republishing does not “add only what has not run”

vendor:publish does not check the database’s migration history. In addition, the copy logic in v13.34.0 checks for an existing file at the destination path before the timestamp is changed. Even when publishing a directory, it first checks whether the same relative path as the source exists at the destination, and only then rewrites the date. As a result, if the first publish changed the dates and no file with the source’s original name exists in the application, publishing the same tag again can add files with different dates. Do not assume that omitting --force always prevents duplicates.

Whether a migration has run is decided by file name

Migrator::getMigrationName() returns the file’s base name without .php. The pending check compares this name against the migration history. It does not compare PHP contents or table names.
These are two different migration names. Even if the first has run, that history alone does not mark the second as run.
Do not unconditionally rerun the first-install publish command on every package update, and do not make --force part of the standard process. Besides overwriting edits to published files, date changes can add duplicate migrations.

Implementing the direct loading approach

To run the package’s migrations as they are, register a search path. With this approach, do not also publish the same files.
loadMigrationsFrom() calls path() when the Migrator is resolved. Migrator::path() deduplicates search paths, and getMigrationFiles() keys the found files by migration name and sorts them by that name. When users update the package, new files are picked up by the next migrate. Do not rename existing files; add a new file for each new schema change. To avoid collisions with other packages, include the feature name, as in create_courier_deliveries_table. Files with the same name share the same key, so both are not run independently.
Switching from the publish approach to direct loading is more than a provider change. If users’ migration history records the names given at publish time, they will not match the original names in the package. You need a migration plan that covers existing users’ history, published files, and rollbacks.

Delivering schema changes to existing users

For example, to add a tracking code to the deliveries table, add a new migration for the change instead of editing the published create_courier_deliveries_table. Editing an existing create migration does not apply the change for users who have already run it.
database/migrations/2026_10_02_000000_add_tracking_code_to_courier_deliveries_table.php
Because this example adds a column to a table that already has rows, the column is nullable. If you need to make it required or backfill data, design those steps and their order separately. With the publish approach, provide an update procedure that checks against the user’s published files and delivers only the files added in this release. You could create a publish tag dedicated to new files, but if date updates are enabled, repeatedly running that tag carries the same caveats. Do not make the update procedure simply rerun the first-install tag. With direct loading, the updated code detects new files automatically. In either approach, the database does not change just because a file exists, so state in your release notes that migrations must be run.

Checks before release

In addition to the package’s database tests, verify the publish and update procedures in a consuming application. Loading migrations directly in tests does not verify the publish approach, where file names change.
  • A first install on an empty database creates the required tables.
  • Updating from a previous release’s database and migration history applies only the new changes.
  • Repeating the same publish command and inspecting the file list confirms the update procedure does not create duplicates.
  • The procedure accounts for date updates being enabled or disabled, and for edits to published files.
  • Rollback of new migrations and their execution order relative to the application’s other migrations are verified.

Migrations

Review the basics of schema definitions, migration history, and rollbacks.

Package Testing

Test package service providers and database behavior.

Package Config Merging and Caching

Review update procedures that account for user config and config caching.

Version Compatibility Management

Tie update procedures and compatibility changes to your release policy.

Primary sources

Last modified on October 2, 2026