0

Publishing config, views, migrations from a service provider

Intermediate5 min read·lv-03-005

Concept

php artisan vendor:publish copies files from package directories into the application. This allows users to customize package config, views, migrations, and other assets without modifying files inside vendor/ (which get overwritten on composer update).

How publishing works:

  1. Package's service provider registers publishable paths with $this->publishes([source => destination], 'tag').
  2. User runs php artisan vendor:publish.
  3. Laravel copies files from vendor/package/... to the app (e.g., config/package.php, resources/views/vendor/package/).
  4. User can now edit the published files freely.

Tag system: Packages organize publishable assets into tags. Users can publish selectively:

  • php artisan vendor:publish --tag=config — only config files.
  • php artisan vendor:publish --tag=views — only views.
  • php artisan vendor:publish --provider="Vendor\\Package\\ServiceProvider" — all from a provider.
  • php artisan vendor:publish --all — all publishable from all providers.

--force flag: Overwrite already-published files. Without --force, artisan skips existing files.

Config discovery pattern: Packages use mergeConfigFrom() in register() so the package has working defaults even without publishing. Users publish to customize.

View discovery: When a view exists at resources/views/vendor/package/name.blade.php, Laravel uses the published version instead of the package's original. Enables template customization.

Migration publishing: Some packages (Sanctum, Passport) publish migrations. Others auto-register migrations with loadMigrationsFrom() — no publishing needed.

Code Example

php
<?php
// Service provider — register publishable files
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        // Config
        $this->publishes([
            __DIR__ . '/../config/notification.php' => config_path('notification.php'),
        ], ['notification', 'notification-config']);

        // Views
        $this->publishes([
            __DIR__ . '/../resources/views' => resource_path('views/vendor/notification'),
        ], ['notification', 'notification-views']);

        // Translations
        $this->publishes([
            __DIR__ . '/../lang' => $this->app->langPath('vendor/notification'),
        ], ['notification', 'notification-lang']);

        // Stubs (for code generation)
        $this->publishes([
            __DIR__ . '/../stubs' => base_path('stubs/vendor/notification'),
        ], ['notification-stubs']);
    }

    // Views are loaded from package UNLESS user has published them
    $this->loadViewsFrom(__DIR__ . '/../resources/views', 'notification');

    // Translations fallback
    $this->loadTranslationsFrom(__DIR__ . '/../lang', 'notification');
}
bash
# List all publishable assets and their tags
php artisan vendor:publish --list

# Publish specific tag
php artisan vendor:publish --tag=notification-config

# Publish all from a provider, overwriting existing files
php artisan vendor:publish --provider="Acme\Notification\NotificationServiceProvider" --force

# After publishing config — customize it
# config/notification.php is now yours to edit
php
// Checking if app is in console context (don't slow web requests with publish logic)
if ($this->app->runningInConsole()) {
    $this->publishes([...]); // only register publish when running artisan
}