public function hasSeeders( array|string|null $seederFiles = null, string $directory = '../database/seeders',): static
Added in 2.4.0. Seeders are a publish-only resource: the toolkit copies them into the application, and nothing else. There is no boot step, because a seeder is only ever run explicitly.
$packager ->name('Blog') ->hasSeeders();
php artisan vendor:publish --tag=blog::seeders
Where they land, and why it matters
Published flat into database/seeders/ — not into a vendor/blog/ subdirectory:
database/seeders/BlogPostSeeder.phpdatabase/seeders/BlogCategorySeeder.php
That is the directory the application's own Database\Seeders namespace maps to, so a published
seeder is immediately runnable:
php artisan db:seed --class="Database\Seeders\BlogPostSeeder"
A vendor/blog/ subdirectory would have needed the consumer to rewrite the namespace before the
class could be autoloaded, which is exactly the kind of chore that makes a "just run this" step stop
being one.
The flat layout has a cost, and it is yours to manage: prefix your seeder class names. Shipping
a PostSeeder into a directory the application already owns is asking for a collision.
Writing the seeder
namespace Database\Seeders; use Acme\Blog\Models\Post;use Illuminate\Database\Seeder; class BlogPostSeeder extends Seeder{ public function run(): void { Post::factory() ->count(25) ->hasComments(3) ->create(); }}
The namespace must be Database\Seeders — that is where it will live once published.
Namespaces and the unpublished file
Because the file declares Database\Seeders, it is not autoloadable from inside your package (your
PSR-4 prefix maps Acme\Blog\ to src/). That is fine and expected: a seeder only exists to be
published. If you want it usable before publishing, ship it under your own namespace and accept
that consumers must run it with the full class name.
Naming specific files
$packager->hasSeeders('BlogPostSeeder.php');$packager->hasSeeders(['BlogPostSeeder.php', 'BlogCategorySeeder.php']);$packager->hasSeeders(directory: '../database/seed');
Discovery is not recursive, so a database/seeders/demo/ subdirectory needs its own call.
Shipping seeders as stubs
A .stub source publishes as .php:
database/seeders/BlogDemoSeeder.stub → database/seeders/BlogDemoSeeder.php
The destination is built from getBaseFileName() — the name without its extension — plus .php,
which is the same convention publishable providers follow.
This is useful when the seeder contains placeholder content you do not want a static analyser or a test runner in your own repository to pick up, or when it references classes that only exist after installation.
Registering with DatabaseSeeder
Publishing puts the file in place; it does not add it to the consumer's DatabaseSeeder. Tell them
what to add:
public function run(): void{ $this->call([ BlogCategorySeeder::class, BlogPostSeeder::class, ]);}
Or do it from an install hook, if your package is opinionated enough to edit a consumer's file:
use NyonCode\LaravelPackageToolkit\Commands\InstallCommand; $packager->hasInstallCommand(function (InstallCommand $command) { $command ->publishMigrations() ->publishSeeders() ->afterInstallation(function (InstallCommand $command) { if ($command->confirm('Seed demo blog content now?', false)) { $command->call('db:seed', ['--class' => 'Database\Seeders\BlogPostSeeder']); } });});
Prompting beats rewriting: DatabaseSeeder is a file consumers edit constantly, and a package that
patches it will eventually patch it wrongly.
In the install command
$packager->hasInstallCommand(function (InstallCommand $command) { $command->publishConfig() ->publishMigrations() ->publishSeeders();});
publishSeeders() is its own step in the installer's progress output, and it adds
Run: php artisan db:seed --class=<published seeder> to the closing "next steps". It is also
included in publishEverything().
Introspection
$packager->isSeedable(); // bool$packager->seederFiles(); // Support\SplFileInfo[]
Testing
test('the package publishes its seeders', function () { $this->artisan('vendor:publish --tag=blog::seeders')->assertExitCode(0); expect(database_path('seeders/BlogPostSeeder.php'))->toBeFile() ->and(file_get_contents(database_path('seeders/BlogPostSeeder.php'))) ->toContain('namespace Database\Seeders;') ->and(database_path('seeders/vendor/blog'))->not->toBeDirectory();});