skip to content

In Laravel, what does php artisan vendor:publish copy, and how do its --tag and --provider options choose the files?

level: juniorimportance: must knowfreq 55%

answer

  1. paths registered with publishes()
  2. tags group files across providers
  3. --provider takes one provider's files
  4. existing files are skipped
  5. --force overwrites, --existing refreshes

basics

~10 s

vendor:publish copies files that service providers registered with publishes() into the app. --tag picks a named group of files, --provider picks one provider's files, and existing files are skipped unless --force is given.

solid answer

~40 s

A package's provider calls `$this->publishes([source => destination], 'tag')` in `boot()` to register files the app may copy, such as a config file, views or assets. `php artisan vendor:publish` copies them. `--tag=audit-trail-config` publishes every path registered under that tag (tags can be repeated); `--provider="Acme\AuditTrail\AuditTrailServiceProvider"` publishes everything that provider registered; both together publish only the intersection. With no option the command shows a searchable list of providers and tags, and `--all` publishes everything without asking. A destination file that already exists is **skipped** and reported as already existing; `--force` overwrites, and `--existing` re-publishes only files that were published before. Migrations registered with `publishesMigrations()` get fresh timestamps in their file names when copied.

code

bash · 8 lines
bash
# only the config file
php artisan vendor:publish --tag=audit-trail-config

# everything the provider registered
php artisan vendor:publish --provider="Acme\AuditTrail\AuditTrailServiceProvider"

# refresh views that were published before, overwriting them
php artisan vendor:publish --tag=audit-trail-views --existing

go deeper

for a junior

Know that vendor:publish --tag=... copies a package's config, views or assets into the app so you can edit them.

for a middle

Explain publishes() and tags, the difference between --tag and --provider, and the skip, --force and --existing behaviour.

for a senior

Plan what an app publishes: config only when it needs edits, views only when customised, and a process for re-checking published files after package updates.

for a principal

Set conventions for in-house packages: consistent tag names, what is publishable at all, and how teams learn about changes to files they published.

## What gets published "Publishing" means copying a file from a package in `vendor/` into the application, where the team can edit it. A package decides what is publishable by calling methods on its service provider, normally in `boot()`: - `publishes(array $paths, $groups = null)` registers source-to-destination pairs, optionally under one or more **tags**. - `publishesMigrations(array $paths, $groups = null)` does the same for migrations and marks them for renaming. ```php $this->publishes([ __DIR__.'/../config/audit-trail.php' => config_path('audit-trail.php'), ], 'audit-trail-config'); $this->publishes([ __DIR__.'/../resources/views' => resource_path('views/vendor/audit-trail'), ], 'audit-trail-views'); ``` Nothing is copied at this point; the provider only records what **could** be copied. ## Choosing what to copy | Command | Publishes | |---|---| | `php artisan vendor:publish --tag=audit-trail-config` | every path registered under that tag, by any provider | | `php artisan vendor:publish --tag=audit-trail-config --tag=audit-trail-views` | both tags | | `php artisan vendor:publish --provider="Acme\AuditTrail\AuditTrailServiceProvider"` | everything that provider registered | | `--provider=... --tag=...` together | only paths registered by that provider **and** under that tag | | `php artisan vendor:publish --all` | every publishable path of every provider | | `php artisan vendor:publish` | an interactive, searchable list of providers and tags | Tags are the usual interface in package READMEs because they let an app take only the config file without the views or assets. An unknown tag prints "No publishable resources for tag [...]". ## Existing files By default the command never overwrites: 1. If the destination does not exist, the file is copied. 2. If it exists, it is **skipped** and reported as "already exists". 3. `--force` overwrites existing files. Packages that ship compiled front-end assets often tell users to run their asset tag with `--force` after every update. 4. `--existing` copies **only** files that already exist in the app, refreshing them without adding new ones. Treat `--force` on a config or view tag with care: it replaces any edits the team made. ## Migrations get new dates Migrations registered with `publishesMigrations()` are renamed as they are copied: the date-time prefix in the file name is replaced with the current time (adding a second per file to keep the order), when the `database.migrations.update_date_on_publish` option is enabled, as it is in the skeleton. The published migration therefore runs after the app's existing ones. ## The audit-trail package The in-house audit-trail package used by three apps registers three tags: - `audit-trail-config` for `config/audit-trail.php`, - `audit-trail-views` for its Blade templates, - `audit-trail-migrations` for its tables. Each app publishes only what it customises. A README line such as `php artisan vendor:publish --tag=audit-trail-config` is the documented install step. ## After a package update Because published files are copies, a package update can leave them behind: 1. **Config files** usually survive, because well-built packages merge their defaults with `mergeConfigFrom()`; new options appear with their default values even though the published file does not show them. 2. **Views** that were published keep the old markup and miss fixes. Compare them with the package's new versions and re-apply local changes, or delete copies you no longer customise so the package version is used again. 3. **Assets** such as compiled JavaScript must usually be re-published with `--force` on every update; some apps add that command to a Composer script so it never gets forgotten. 4. **Migrations** that were published are never updated; new ones must be published again. The `--existing` flag is useful here: it refreshes exactly the files the app already has, without pulling in new ones it never wanted. ## What interviewers look for - That publishing is a one-time **copy**, not a link: later package updates do not change published files. - The difference between `--tag` and `--provider`, and that tags are the finer tool. - That existing files are skipped by default, and what `--force` and `--existing` change. - That the source of publishable paths is the provider's `publishes()` calls.

  • After a package update, the app's published config file lacks a new option. Will vendor:publish --tag fix it?
    Not by default: the file exists, so it is skipped. `--force` would overwrite it and lose local edits. The usual answer is that the package merges its defaults with `mergeConfigFrom()`, so new options work without re-publishing, and the team copies the new key by hand if it wants to change it.
  • What happens when --provider and --tag are passed together?
    Only paths that the named provider registered under that tag are published: the command takes the intersection of the provider's paths and the tag's paths. If the provider registered nothing under that tag, nothing is copied.

saying these in an interview costs you the question

  • vendor:publish creates links that track package updates
  • vendor:publish overwrites existing files by default
  • --tag can only select files from a single provider
  • Published migrations keep the package's original timestamps
  • publishes() copies files as soon as the provider boots