---
title: "Publish-only migrations — Package Toolkit for Laravel | Roundly"
description: "Package migrations are never auto-loaded: they publish with ordered timestamps, republish in place, and the host runs migrate."
url: https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel/migrations
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Package Toolkit for Laravel](https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel.md)

# Publish-only migrations

A package built on the toolkit never auto-loads its migrations. The host publishes them and runs the migrator:

```bash
php artisan vendor:publish --tag=comments-migrations
php artisan migrate
```

## Declaring migrations

hasMigrations() publishes every database/migrations/\*.php file. hasMigration() adds a single database/migrations/<name>.php.stub source — use it only for .php.stub files, because .php files are already picked up by hasMigrations(). Both publish under <name>-migrations:

```php
$package
    ->name('comments')
    ->hasMigrations()                                  // every database/migrations/*.php
    ->hasMigration('create_comment_reactions_table');  // database/migrations/create_comment_reactions_table.php.stub
```

## Timestamps and order

Each file is published to database/migrations/<Y\_m\_d\_His>\_<name>.php, so it orders against the host’s own migrations. When a package ships several migrations, their timestamps step forward one second per file in the package directory’s sort order — migrations that depend on each other’s tables still run in the right sequence. .php.stub migrations declared with hasMigration() follow the .php files, in declaration order. All providers draw from one process-wide cursor, so when one vendor:publish run publishes several packages, each package’s files follow the previous package’s instead of sharing timestamps. Any timestamp the package prefixed its source file with is replaced by the publish timestamp:

```
package: database/migrations/
    2020_01_01_000000_create_comments_table.php
    create_comment_votes_table.php
    create_comment_reactions_table.php.stub

host, after vendor:publish --tag=comments-migrations: database/migrations/
    2026_07_15_101500_create_comments_table.php
    2026_07_15_101501_create_comment_votes_table.php
    2026_07_15_101502_create_comment_reactions_table.php
```

## Republishing is safe

The destination resolver reuses the file a migration was already published to, whatever timestamp it carries. A forced republish therefore overwrites in place instead of dropping a second, differently timestamped copy of the same Schema::create():

```bash
php artisan vendor:publish --tag=comments-migrations --force
```

## Your own migrations are never overwritten

A file only counts as the earlier copy when it has the same name and the package source’s contents — whitespace differences such as line endings are ignored. A same-named migration with different contents — your app’s own create\_comments\_table, another package’s, or a copy you edited after publishing — is never overwritten, not even with --force. The package’s migration is published beside it under a fresh timestamp, and you decide which one to keep:

```
host, before: database/migrations/
    2025_03_02_090000_create_comments_table.php   your app's own — different contents

host, after vendor:publish --tag=comments-migrations --force:
    2025_03_02_090000_create_comments_table.php   untouched
    2026_07_15_101500_create_comments_table.php   the package's, published beside it
```

## Edited a published migration? Delete the duplicate

An edited copy no longer matches the package source, so the next publish of the tag — with or without --force — adds the package’s migration again beside it, under a new timestamp. The same happens when a package release changes a migration you have already published. Two create\_comments\_table files make php artisan migrate fail with “table already exists”. Keep the copy that has already run, delete the new one, and put any change you want from it in a new migration of your own.

## The MigrationPublisher helper

The naming and destination logic is also available on its own, as RoundlyConsulting\\PackageToolkit\\Support\\MigrationPublisher. destination() takes the package source file, so it can compare contents before reusing a host file. nextTimestamp() hands out the next publish timestamp from the cursor the base provider uses — the current second, or one second past the last one handed out in this process — so a custom publish command’s migrations order after everything else published in the same run:

```php
use RoundlyConsulting\PackageToolkit\Support\MigrationPublisher;

MigrationPublisher::nameFor('2020_01_01_000000_create_comments_table.php');
// 'create_comments_table' — extension and any timestamp prefix stripped

MigrationPublisher::nameFor('create_comment_reactions_table.php.stub');
// 'create_comment_reactions_table'

MigrationPublisher::destination(
    __DIR__.'/../database/migrations/create_comments_table.php',   // the package source file
    database_path('migrations'),
    MigrationPublisher::nextTimestamp(),
);
// the file it was already published to (same name, same contents),
// or …/database/migrations/<Y_m_d_His>_create_comments_table.php
```

resetTimestamps() forgets the cursor, so the next timestamp is the current second again. Call it in a test suite’s setUp(), before the app boots, when tests pin publish timestamps.

Because nothing is auto-loaded, a package’s own test suite must run its migrations explicitly — see Testing.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel.md)
