---
title: "Builder methods — Package Toolkit for Laravel | Roundly"
description: "Every Package builder method — config, migrations, translations, views, routes, commands, aliases, about and stubs — with its publish tag."
url: https://roundly-consulting.com/open-source/docs/package-toolkit-for-laravel/builder-methods
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)

# Builder methods

Every method returns the builder, so declarations chain. Call name() first — it drives config keys, view and translation namespaces, and every publish tag:

| Method | What it does |
| --- | --- |
| `name(string $name)` | The package handle. Drives config keys, view/translation namespaces and publish tags. |
| `hasConfigFile(?string $file = null)` | Merge + publish a config file. Defaults to <name>.php (key <name>, tag <name>-config). |
| `hasMigrations()` | Publish every database/migrations/\*.php file under <name>-migrations, timestamp-injected and kept in the directory’s order. Nothing is auto-loaded. |
| `hasMigration(string $name)` | Publish a single database/migrations/<name>.php.stub under <name>-migrations, timestamp-injected. Only for .php.stub sources. |
| `hasTranslations()` | Load + publish translations (published to lang/vendor/<name>, tag <name>-translations). |
| `hasViews(?string $namespace = null)` | Register + publish Blade views (namespace defaults to <name>, tag <name>-views). |
| `hasRoutes(string $file, ?string $enabledVia = null)` | Load a route file (optionally gated behind a boolean config key, read like Config::boolean()) and publish it under <name>-routes. Once the host has published it, the published copy loads instead. An unrecognised switch value throws at boot. |
| `hasCommands(array $commands)` | Register console commands (console only). Repeated calls accumulate. |
| `hasFacadeAlias(string $class, ?string $configKey = null)` | Register a class alias; the config value decides its name or skips it, and an unrecognised value throws. |
| `contributesToAbout(?Closure $data = null)` | Add a section to php artisan about. |
| `publishesStubs(string $from, string $to, string $tag)` | Publish an arbitrary set of files under a custom tag. |

## A full declaration

A package that uses every builder method:

```php
use Illuminate\Support\Str;
use RoundlyConsulting\PackageToolkit\Package;

public function configurePackage(Package $package): void
{
    $package
        ->name('comments')
        ->hasConfigFile()                                       // config/comments.php
        ->hasMigrations()                                       // database/migrations/*.php
        ->hasMigration('create_comment_reactions_table')        // database/migrations/<name>.php.stub
        ->hasTranslations()                                     // resources/lang → comments::
        ->hasViews()                                            // resources/views → comments::
        ->hasRoutes('comments.php', 'comments.routes.enabled')  // routes/comments.php, gated by config
        ->hasCommands([PruneCommentsCommand::class])            // console only
        ->hasFacadeAlias(Comments::class, 'comments.alias')     // alias decided by config
        ->contributesToAbout(static fn (): array => [
            'Package' => 'comments',
            'Key type' => (string) config('comments.key_type'),
            'API key' => Str::mask((string) config('comments.api_key'), '*', 7),
        ])
        ->publishesStubs(
            $package->basePath.'/stubs',
            base_path('stubs/comments'),
            'comments-stubs',
        );
}
```

## Publish tags

Each declaration registers a publish group under a tag derived from the package name, so a host publishes exactly what it needs:

| Tag | Declared by | Published to |
| --- | --- | --- |
| `<name>-config` | hasConfigFile() | config/<file>.php |
| `<name>-migrations` | hasMigrations(), hasMigration() | database/migrations/<Y\_m\_d\_His>\_<migration>.php |
| `<name>-translations` | hasTranslations() | lang/vendor/<name> |
| `<name>-views` | hasViews() | resources/views/vendor/<namespace> |
| `<name>-routes` | hasRoutes() | routes/<file> |
| `<your tag>` | publishesStubs() | The destination you pass |

```bash
php artisan vendor:publish --tag=comments-config
php artisan vendor:publish --tag=comments-migrations
php artisan vendor:publish --tag=comments-translations
php artisan vendor:publish --tag=comments-views
php artisan vendor:publish --tag=comments-routes
php artisan vendor:publish --tag=comments-stubs
```

## Config files

hasConfigFile() merges config/<name>.php under the <name> key and publishes it under <name>-config. Pass a bare filename to ship a differently named file — the key follows the filename, the tag stays <name>-config. The .php extension is optional:

```php
$package
    ->name('comments')
    ->hasConfigFile()                       // config/comments.php          → config('comments.*')
    ->hasConfigFile('comments-webhooks');   // config/comments-webhooks.php → config('comments-webhooks.*')

// both files publish under the comments-config tag
```

## Views and translations

hasTranslations() loads resources/lang under the <name> namespace and publishes it to lang/vendor/<name>. hasViews() loads resources/views under the <name> namespace — or the one you pass, as in hasViews('talk') — and publishes it to resources/views/vendor/<namespace>.

## Routes behind a config switch

hasRoutes() loads routes/<file> and publishes it under <name>-routes. The optional second argument names a boolean config key, read strictly like Config::boolean() — so a host can switch routes off from .env: false, 0, '0', 'false', 'off' and 'no' skip the file (case-insensitive); true, 1, '1', 'true', 'on', 'yes', or a switch that is not set — an absent key, null, or a blank '' (a host’s KEY=) — load it. Anything else — 'disabled', 'ture', 2 — throws InvalidConfigurationException at boot, so a typo never loads the routes:

```php
// configurePackage()
$package
    ->name('comments')
    ->hasConfigFile()
    ->hasRoutes('comments.php', 'comments.routes.enabled');

// config/comments.php
return [
    'routes' => [
        'enabled' => true,   // false — or 'off', '0', 'no' from env — drops the package routes entirely;
                             // a blank value (KEY=) counts as not set and loads them
    ],
];
```

## Published route files

When routes/<file> exists in the host app, the provider loads that copy instead of the package’s file. A host publishes the routes to change them — URIs, middleware, names — and edits the copy; nothing else is needed. Name route files after the package (comments.php, not web.php), so a publish never targets the host’s own route file:

- Don’t also require the copy from routes/web.php or another route file. The provider already loads it, so a second load registers every route twice and php artisan route:cache fails on the duplicate names.
- The config switch gates the copy too: switched off, neither file loads. To wire the routes yourself — inside your own group, say — switch them off and require the copy from your route file.
- Delete the copy to go back to the package’s routes. After a package release changes its route file, republish with --force (this overwrites your edits) or merge the change by hand.

## Class aliases

hasFacadeAlias() registers a class alias through Laravel’s AliasLoader. Without a config key the alias is always the class’s base name; with one, the config value decides when the provider registers — env-style booleans are parsed, so 'off' from .env opts out instead of becoming an alias named “off”, a blank value counts as not set and keeps the base name, and a value that is neither a boolean spelling nor a non-empty string throws:

| Config value | Result |
| --- | --- |
| No config key passed | Alias = the class’s base name. |
| `true / 1 / '1' / 'true' / 'on' / 'yes'` | Alias = the class’s base name. |
| Key absent, or blank ('' / whitespace — not set) | Alias = the class’s base name. |
| `null (explicit) / false / 0 / '0' / 'false' / 'off' / 'no'` | No alias is registered. |
| Any other non-empty string | That string is the alias name. |
| Anything else (2, 1.5, an array) | Throws InvalidConfigurationException at register time. |

```php
// configurePackage()
$package
    ->name('comments')
    ->hasConfigFile()
    ->hasFacadeAlias(Comments::class, 'comments.alias');

// config/comments.php
return [
    'alias' => true,          // registers the alias "Comments"
    // 'alias' => 'Talk',     // registers the alias "Talk" instead
    // 'alias' => false,      // registers no alias — as do null, 'off', '0' and 'no'
    // 'alias' => '',         // blank counts as not set — registers "Comments"
    // 'alias' => 2,          // throws InvalidConfigurationException at register time
];
```

## The about command

contributesToAbout() adds a section named after the package (name() with its first letter upper-cased) to php artisan about. Without a callback it reports the package name; pass a closure returning label/value pairs for more. The closure runs when the command renders — redact anything secret:

```php
use Illuminate\Support\Str;

$package
    ->name('comments')
    ->contributesToAbout();   // section "Comments" → Package: comments

$package
    ->name('comments')
    ->contributesToAbout(static fn (): array => [
        'Package' => 'comments',
        'Key type' => (string) config('comments.key_type'),
        'API key' => Str::mask((string) config('comments.api_key'), '*', 7),   // never render a raw secret
    ]);
```

```bash
php artisan about
```

## Arbitrary stubs

publishesStubs() is the escape hatch for files that fit no other helper — it publishes a source path to a destination under a tag you choose:

```php
$package
    ->name('comments')
    ->publishesStubs(
        $package->basePath.'/stubs',   // source (the base path is already resolved here)
        base_path('stubs/comments'),   // destination in the host
        'comments-stubs',              // your tag
    );
```

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