> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Publishing and updating package migrations

> Based on the Laravel 13 implementation, this guide explains the difference between publishesMigrations and loadMigrationsFrom, timestamp changes on publish, the risks of republishing, and how to deliver schema changes to existing users.

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](/en/advanced/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?

| Approach | Provider call | User action | Where files live |
| - | - | - | - |
| Publish | `publishesMigrations()` | `vendor:publish`, then `migrate` | The application's `database/migrations` |
| Load directly | `loadMigrationsFrom()` | `migrate` | Inside the installed 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.

```mermaid theme={null}
flowchart TD
    A["Package service provider"] --> B["publishesMigrations()<br>Register source and destination"]
    B --> C["vendor:publish<br>Copy into the application"]
    C --> E["migrate<br>Run pending files"]
    A --> D["loadMigrationsFrom()<br>Add to the Migrator search path"]
    D --> E
    E --> F["Record run file names<br>in the migrations table"]
```

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.

<Warning>
  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.
</Warning>

## Implementing the publish approach

Give the migrations a package-specific tag so users can publish them separately from other resources.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->publishesMigrations([
            __DIR__.'/../database/migrations' => database_path('migrations'),
        ], 'courier-migrations');
    }
}
```

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.

```bash theme={null}
php artisan vendor:publish --provider="Acme\Courier\CourierServiceProvider" --tag=courier-migrations
php artisan migrate:status
php artisan migrate
```

### 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.

```php theme={null}
'migrations' => [
    'table' => 'migrations',
    'update_date_on_publish' => true,
],
```

`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.

```text theme={null}
Source:
2026_09_01_000000_create_courier_deliveries_table.php

Example after publishing:
2026_10_02_120001_create_courier_deliveries_table.php
```

The date after publishing above is illustrative. The actual file name depends on when you publish.

<Info>
  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.
</Info>

## 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.

| Condition | What to watch for when republishing |
| - | - |
| Date updates enabled, and the pre-rename destination does not exist | A copy with a different date may be added |
| Date updates disabled, and a destination with the same name exists | The existing file is normally skipped |
| `--force` given | If the copy conditions are met and date updates are enabled, the file may get a different name |
| `--existing` given | Because it checks for the pre-rename destination, a file whose date was already changed is not necessarily included |

### 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.

```text theme={null}
2026_10_02_120001_create_courier_deliveries_table
2026_10_03_090001_create_courier_deliveries_table
```

These are two different migration names. Even if the first has run, that history alone does not mark the second as run.

<Warning>
  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.
</Warning>

## 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.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
    }
}
```

`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.

<Warning>
  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.
</Warning>

## 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.

```php database/migrations/2026_10_02_000000_add_tracking_code_to_courier_deliveries_table.php theme={null}
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('courier_deliveries', function (Blueprint $table) {
            $table->string('tracking_code')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('courier_deliveries', function (Blueprint $table) {
            $table->dropColumn('tracking_code');
        });
    }
};
```

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.

## Related pages

<Columns cols={2}>
  <Card title="Migrations" icon="database" href="/en/migrations">
    Review the basics of schema definitions, migration history, and rollbacks.
  </Card>

  <Card title="Package Testing" icon="flask" href="/en/advanced/package-testing">
    Test package service providers and database behavior.
  </Card>

  <Card title="Package Config Merging and Caching" icon="sliders" href="/en/advanced/package-config-merging">
    Review update procedures that account for user config and config caching.
  </Card>

  <Card title="Version Compatibility Management" icon="code-branch" href="/en/advanced/package-versioning">
    Tie update procedures and compatibility changes to your release policy.
  </Card>
</Columns>

## Primary sources

* [Laravel official documentation: Package migrations](https://github.com/laravel/docs/blob/156fc7fde114548640e13c39aa79b991291c3f91/packages.md#migrations)
* [ServiceProvider: Registering publishables and search paths](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Support/ServiceProvider.php)
* [VendorPublishCommand: Copying and timestamp changes](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Foundation/Console/VendorPublishCommand.php)
* [Migrator: File discovery and pending checks](https://github.com/laravel/framework/blob/v13.34.0/src/Illuminate/Database/Migrations/Migrator.php)
* [Laravel 13 default application: Database config](https://github.com/laravel/laravel/blob/06d016a364a37430eec9cbc52209adce3d7667ce/config/database.php)


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Advanced Topics](/en/advanced/index.md)
- [Migration guide from laravel/ui to Fortify](/en/blog/ui-to-fortify.md)
- [Package config merging and caching](/en/advanced/package-config-merging.md)
- [Package Version Compatibility Management](/en/advanced/package-versioning.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.