ralphjsmit/stubs
Take control of Laravel's generator stubs. This package lets you publish and override the template files behind artisan make commands, so generated classes, migrations, and tests follow your project's own conventions and coding style, all configured simply.
First, resolve the identity — this is not optional. The record points at two different packages: the install name ralphjsmit/stubs (Laravel stub publishing) and the repository/description ralphjsmit/filesystem (a fluent Storage wrapper). The name, description, release notes and dependents all corroborate the stubs reading, so the rest of this assessment assumes it — but confirm before you write a line of code:
composer show ralphjsmit/stubs # name, version, requires, autoload
cat vendor/ralphjsmit/stubs/composer.json
ls vendor/ralphjsmit/stubs/config vendor/ralphjsmit/stubs/stubs
If composer show resolves to ralphjsmit/filesystem instead, stop and re-read the filesystem notes in Gotchas below before proceeding.
Install as a dev dependency. Stubs are consumed only when artisan make:* runs — there is no runtime footprint — so it belongs in require-dev:
composer require ralphjsmit/stubs:1.4.0 --dev # pin exactly; no upstream cadence since Feb 2023
Publish, in two layers. First the framework baseline, using Laravel's own command, then whatever the package registers:
php artisan stub:publish # Laravel's built-in; writes to ./stubs
php artisan vendor:publish --tag=stubs # package tags — confirm exact tag via --list
php artisan vendor:publish --list # inspect available tags before publishing
Where to look first, in order:
config/stubs.php (if published) — the source map of which stubs come from which package.stubs/ at your project root — this is the directory Laravel's generators prefer over framework copies.vendor/ralphjsmit/stubs/stubs/ — the package's shipped copies, for the packages (Jetstream, Fortify, Breeze, Livewire) you'd otherwise have to hunt down manually.First use case. Customize stubs/model.stub — add declare(strict_types=1);, final class, a base class or trait you always add — then:
php artisan make:model Invoice
git diff app/Models/Invoice.php
If the generated file already matches house style, the wiring works. If it looks like stock Laravel, your stub isn't being resolved (see Gotchas).
Baseline-then-customize. Publish everything once, diff against the framework defaults, then delete every stub you don't intend to own. Committing 30 stock stubs creates 30 files to re-diff on every laravel/framework upgrade for zero benefit. Keep the 4–8 you actually customize.
Encode house style once, not in code review. The highest-leverage stubs, ranked by review-churn eliminated:
| Stub | What you encode |
|---|---|
model.stub |
strict types, final, base model, $guarded = [], trait set |
controller.stub / controller.api.stub |
base controller, return types, form-request injection |
request.stub |
authorize(): bool, rules(): array signatures |
factory.stub |
custom Factory base, faker conventions |
test.stub (Pest/PHPUnit variants) |
namespace, base TestCase, dataset imports |
migration.stub |
up/down return types, anonymous class convention |
Add domain stubs (Action, DTO, Policy) by pointing a generator at them, or by documenting php artisan make:class + a copied stub if the package doesn't cover your custom types.
Selective publishing for first-party packages. Publish Jetstream/Fortify/Breeze/Livewire stubs only while customizing those scaffolds — typically during initial project setup — then commit the results and drop the stub. Their published stubs are the ones most likely to drift between package majors.
Team workflow. Treat stubs/ as owned source:
CODEOWNERS entry for stubs/.stub:publish + re-apply local diffs) in the repo README.CI guard against silent drift. After a framework upgrade, regenerate into a scratch namespace and assert the output still compiles:
php artisan make:model __StubProbe --force
php -l app/Models/__StubProbe.php # catches syntax drift in the stub
git checkout -- app/Models/__StubProbe.php 2>/dev/null; rm -f app/Models/__StubProbe.php
Better: a Pest/PHPUnit test that shells out to artisan make:* for each customized stub and snapshot-asserts the output. This is the only real safety net — generated code is reviewed once and never again.
Filesystem reading (if that turns out to be the real package). Confine it to internal, non-critical tooling — seeders, fixtures, admin exports, one-off migration scripts — behind a single wrapper so a revert is a one-file change:
final class InternalFiles
{
public function __construct(private Filesystem $fs) {} // ralphjsmit wrapper
public function read(string $path): string { return $this->fs->get($path); }
}
Native Storage:: is the fallback. Never route uploads, PII, billing artifacts or anything with an SLA through it.
If you're a package author, register your own stub sources with a dedicated publish tag so consumers can opt in without pulling the whole set.
The identity mismatch is a blocker, not a footnote. ralphjsmit/stubs and ralphjsmit/filesystem support different decisions with different risk profiles — one is build-time with near-zero blast radius, the other intercepts runtime file I/O. A 2023 release against a 2025/2026 runtime is the most likely failure point. Sandbox-install against your exact Laravel/PHP matrix before committing anything to a shared branch.
The version ceiling is Laravel 10. 1.4.0 is the last release and it added Laravel 10 support in #11. Laravel 11 reworked the stub and generator internals substantially. Expect either a Composer constraint conflict or — worse — a clean install with stubs that silently produce wrong-shape code. Verify with a real make:model/make:controller in a throwaway app on your production framework version.
Filename must match exactly. Generators resolve base_path('stubs/{name}.stub') and fall back to the framework copy if the file is missing. controller.api.stub vs controller.stub, model.stub vs models.stub — a typo doesn't error, it silently gives you stock output. Debug recipe: temporarily put invalid PHP in the stub. If generation still succeeds, your stub is not being picked up; check the filename and that stubs/ is at the project root, not under app/.
Placeholders differ between framework versions and packages. {{ namespace }}, {{ rootNamespace }}, {{ class }}, DummyNamespace — never hand-write these. Copy the published file verbatim and edit around the placeholders.
Never run --force casually. stub:publish --force and vendor:publish --force overwrite your edits with no diff prompt. Publish once, commit, and treat regeneration as a deliberate migration step with a review.
Vendor stubs are version-pinned to the framework. After every composer update laravel/framework, re-publish and diff against your committed stubs — otherwise you keep generating code that no longer matches the framework's base classes. Budget this as an upgrade-checklist item; it's the recurring tax on the whole approach.
Publish-tag collisions. If you already publish stubs from another package or your own scaffolding, tags and destination paths can overwrite each other. Run vendor:publish --list on a clean checkout and confirm the ownership of every stubs/ file before merging.
No service-provider discovery guarantee. If the package's composer.json doesn't declare extra.laravel.providers, nothing registers automatically — the publish tags won't exist and vendor:publish will report "no publishable resources". Register manually in AppServiceProvider::register() or a local provider.
Zero dependents, single maintainer, last release Feb 2023. Assume no upstream response and no CVE triage. Pin the exact version, add a CODEOWNERS entry, and keep the exit cheap: everything lives in stubs/ and possibly one config file, so rollback is git rm -r stubs config/stubs.php plus composer remove.
The honest build-vs-adopt check. Laravel's built-in stub:publish already publishes framework stubs. This package's marginal value is the first-party package stubs (Jetstream/Fortify/Breeze/Livewire) bundled with one command. If you need three or fewer of those, spend the afternoon copying them from vendor/ into stubs/ and skip the dependency entirely — you get ~90% of the benefit with 100% of the ownership.
How can I help you explore Laravel packages today?