In Laravel, what does `php artisan stub:publish` do, and how do the published stubs change what `make:*` commands generate?
answer
- templates behind every generator
- copied into a root stubs/ folder
- project copy checked before the framework's
- --force versus --existing
- keep only the stubs you changed
basics
~10 sstub:publish copies the framework's common generator templates into a stubs/ folder at the project root. Each make:* command checks that folder before its built-in stub, so edits there shape every class generated afterwards.
solid answer
~40 sGenerators build classes from **stub** files, templates with placeholders such as `{{ namespace }}` and `{{ class }}`. `php artisan stub:publish` copies the commonly customised ones — `model.stub`, the `controller.*` family, `migration.create.stub`, `job.queued.stub`, `test.stub`, `console.stub` and more — into `stubs/` at the project root. Each generator resolves its stub by checking `stubs/<name>.stub` in the project first and falling back to the framework's copy, so an edited stub changes every class generated afterwards; files generated earlier stay as they are. `--force` overwrites stubs you already published; `--existing` re-copies only the ones already present, which also discards your edits. Keep only the stubs you changed, commit them, and review them after framework upgrades, because a published stub no longer receives the framework's improvements.
code
php · 13 lines<?php
declare(strict_types=1);
namespace {{ namespace }};
{{ factoryImport }}
use Illuminate\Database\Eloquent\Model;
class {{ class }} extends Model
{
{{ factory }}
}go deeper
Recall that generators use stub templates and that stub:publish copies them into a stubs/ folder you can edit.
Explain the lookup order — project stubs/ first, framework copy second — and the difference between --force and --existing.
Show upgrade hygiene: keep only customised stubs under version control, refresh them against the framework after upgrades, and review the diff.
Decide which conventions deserve enforcement through stubs versus linting or review, weighing consistency against the cost of maintaining forked templates.
## What a stub is Every `make:*` generator in Laravel writes its class from a **stub**: a plain template file containing placeholders. When you run `php artisan make:model Shipment`, the generator reads `model.stub`, replaces `{{ namespace }}` with `App\Models`, `{{ class }}` with `Shipment` and a few type-specific placeholders (such as the factory import), and writes `app/Models/Shipment.php`. The framework keeps its stubs inside `vendor/laravel/framework`, which you must never edit because Composer would overwrite your change on the next install. ## What `stub:publish` copies `php artisan stub:publish` copies the stubs teams most often want to change into a `stubs/` directory at the project root, creating the folder if needed. The set includes, among others: - `model.stub` and `model.pivot.stub`; - the controller family: `controller.stub`, `controller.plain.stub`, `controller.api.stub`, `controller.model.stub`, `controller.invokable.stub` and the nested and singleton variants; - `migration.stub`, `migration.create.stub` and `migration.update.stub`; - `job.stub` and `job.queued.stub`, the listener, event, mail and notification stubs; - `request.stub`, `resource.stub`, `policy.stub`, `observer.stub`, `factory.stub`, `seeder.stub`; - `test.stub`, `test.unit.stub`, `pest.stub` and `console.stub`. It publishes the *common* set, not every stub the framework has; a generator still looks in `stubs/` for any stub name it uses, so you can add a less common one by hand. ## How a generator chooses its stub 1. The generator decides which stub name it needs from its options — for example `controller.model.stub` for `make:controller --model=Shipment`. 2. It checks whether that file exists under the project's `stubs/` directory. 3. If it does, the project copy is used; otherwise the framework's built-in copy is used. 4. The placeholders are replaced and the file is written, subject to the usual already-exists check. Because the lookup happens on every run, editing a published stub takes effect immediately for new classes and never touches existing ones. ## The options | Command | Effect on stubs already in `stubs/` | Effect on stubs not yet published | |---|---|---| | `stub:publish` | left alone | copied | | `stub:publish --force` | overwritten with the framework's version | copied | | `stub:publish --existing` | overwritten with the framework's version | not copied | `--existing` is a refresh: useful after an upgrade to see what the framework changed, but it replaces your customisations, so run it on a clean working tree and review the diff. ## Using it well - Typical customisations: `declare(strict_types=1);` at the top of every class, a house docblock, a base class or trait on every model or test, removing comment boilerplate. - **Delete the stubs you did not change.** A published stub freezes that template; untouched copies only stop you from receiving the framework's own improvements. - Commit `stubs/` so every developer and every CI job generates the same code. - Packages can add their own stubs to the published set by listening for the `PublishingStubs` event and calling its `add()` method. ## A worked example Suppose a team wants every new class to begin with `declare(strict_types=1);` and every new feature test to extend a shared base class that seeds a tenant. 1. Run `php artisan stub:publish` once. 2. Edit `stubs/model.stub`, `stubs/controller.stub` and `stubs/job.queued.stub` to add the strict-types line, and edit `stubs/test.stub` to extend the team's base test class. 3. Delete every other file in `stubs/`, so the rest keep tracking the framework. 4. Commit the remaining files together with a short note in the README explaining why they exist. 5. After the next framework upgrade, run `php artisan stub:publish --existing` on a clean branch, review the diff against the team's edits, and re-apply them where the framework changed its template. From then on `php artisan make:model Parcel` produces a model with strict types, while models generated before the change are untouched and can be updated in a normal refactoring change. ## Common mistakes - Editing stubs inside `vendor/` — lost on the next `composer install`. - Expecting a changed stub to rewrite classes generated earlier. - Publishing everything, customising one file, and never noticing that the other stubs have drifted behind the framework.
- Your team published all stubs two years ago and only customised `model.stub`. What would you do after upgrading to Laravel 13?Delete every published stub except `model.stub`, so the generators fall back to the framework's current templates. Then compare `model.stub` with the framework's new version — for example by running `stub:publish --existing` on a clean working tree and reviewing the diff — and re-apply the team's change on top of it before committing.
- Can you customise a stub that `stub:publish` does not copy, such as the morph-pivot model stub?Yes. The generator checks the project's `stubs/` directory for the stub name it uses whatever `stub:publish` copied, so you can place `model.morph-pivot.stub` there by hand, starting from the framework's copy in `vendor/`. `stub:publish` just seeds the common set.
saying these in an interview costs you the question
- Customising generator output means editing the stub files inside vendor/laravel/framework.
- Changing a published stub regenerates classes that were already created from it.
- stub:publish --existing is safe to run because it never overwrites your customised stubs.
- Publishing every stub has no downside even if you customise only one.
- Generators ignore the project's stubs folder unless you register it in config.