skip to content

In Laravel, what does `php artisan stub:publish` do, and how do the published stubs change what `make:*` commands generate?

level: middleimportance: should knowfreq 25%

answer

  1. templates behind every generator
  2. copied into a root stubs/ folder
  3. project copy checked before the framework's
  4. --force versus --existing
  5. keep only the stubs you changed

basics

~10 s

stub: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 s

Generators 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
<?php

declare(strict_types=1);

namespace {{ namespace }};

{{ factoryImport }}
use Illuminate\Database\Eloquent\Model;

class {{ class }} extends Model
{
    {{ factory }}
}

go deeper

for a junior

Recall that generators use stub templates and that stub:publish copies them into a stubs/ folder you can edit.

for a middle

Explain the lookup order — project stubs/ first, framework copy second — and the difference between --force and --existing.

for a senior

Show upgrade hygiene: keep only customised stubs under version control, refresh them against the framework after upgrades, and review the diff.

for a principal

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.