zepfietje/laravel-nova-stubs
A collection of customizable Laravel Nova stubs for scaffolding resources, fields, filters, actions, and more. Publish and tweak the templates to match your project's conventions, so generated files start exactly how you want them.
This package is a collection of stub files, not a runtime library. Its only job is to put better-looking templates in front of Nova's Artisan generators (nova:resource, nova:field, nova:lens, nova:filter, nova:action, nova:card, nova:tool, the metric generators, etc.). Treat installation as a one-time scaffolding step.
composer require --dev zepfietje/laravel-nova-stubs
php artisan vendor:publish # pick the zepfietje/laravel-nova-stubs provider from the list
Nova's console commands resolve stubs by checking for a project-local override directory first (in Nova 3/4 that is stubs/nova/ at the project root) and falling back to Nova's own packaged stubs. So "publishing" here means files land in your repo and start winning over Nova's built-ins. Confirm the exact target path the publish command used the first time — it is not resources/stubs/, and getting the directory wrong means your edits silently do nothing.
Where to look first, in this order:
vendor/zepfietje/laravel-nova-stubs/stubs/ — read the raw templates. The value proposition is "clean versions," so the information you need is what they changed.vendor/laravel/nova/stubs/ — diff the two directories. That diff is your entire spec for this package.vendor:publish wrote to — commit it, then delete or ignore the vendor copy.First real use case: you are about to generate five Nova resources in one sprint and you want them to come out with declare(strict_types=1), a base App\Nova\Resource, final class, and no docblock noise. Edit the published resource stub once, then run:
php artisan nova:resource Invoice
php artisan nova:resource Payment
Every generated class now matches your house style with zero post-generation editing.
Vendor the stubs, drop the dependency. This package is archived (last release 2020-11-30, one star). The durable pattern is: publish once, commit stubs/nova/ to git, then composer remove --dev zepfietje/laravel-nova-stubs. You keep 100% of the value — a folder of templates, MIT licensed, no fork required — and you stop carrying an unmaintained dependency whose stubs are frozen at the Nova 3 era. If you want provenance, note the source commit in a comment at the top of each stub.
Publish selectively. Nova falls back to its own stub whenever a project-local file is absent, so you only need overrides for generators you actually use. Deleting stubs/nova/lens.stub restores Nova's stock lens output with no other changes. Keep the folder small; every file is a future merge conflict against a Nova upgrade.
Bake conventions into the stub, not into code review. Typical edits that pay for themselves immediately:
<?php
declare(strict_types=1);
namespace {{ namespace }};
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Resource;
final class {{ class }} extends Resource
{
public static $group = '{{ group }}';
public static $model = \App\Models\{{ model }}::class;
public static $title = 'name';
public static $search = ['id', 'name'];
public function fields(NovaRequest $request): array
{
return [
ID::make()->sortable(),
];
}
}
The exact signature (fields(), fields(NovaRequest $request), Fields container vs. plain array) is version-dependent — copy the signature verbatim from whatever Nova currently ships in vendor/laravel/nova/stubs/ and only change the surrounding style. Never retype it from memory.
Use stubs to enforce architecture, not just formatting. If every resource should extend an app base class that adds authorizedToView, defaultSort, or a cards() default, put that in the stub:
final class {{ class }} extends \App\Nova\Resource
Now no resource can accidentally bypass your base class, because the generator is the only sanctioned entry point.
Pre-wire the boilerplate-heavy generators. Custom field, metric, and card stubs are where the win is largest, because those classes have real structure:
nova:field — scaffold resolve()/fill() with typed, nullable-safe signatures already in place.nova:trend-metrics / nova:value-metrics — pre-fill $model, a calculate() that uses ->whereBetween(..., [Carbon::now()->subDays($range), Carbon::now()]), and $timezone handling, so nobody hand-rolls range bounds again.nova:action — stub in handleFields, rules(), and $name ordering.nova:filter — stub in the apply(NovaRequest $request, $query, $value) signature with the correct query-builder type.Add a CI guard for stub rot. Stubs are untyped strings — a broken placeholder or a stale Nova API produces a class that only fails at request time. A cheap test catches it:
public function test_generated_resources_compile(): void
{
$this->artisan('nova:resource', ['name' => 'StubSmokeTest'])->assertSuccessful();
exec('php -l '.base_path('app/Nova/StubSmokeTest.php'), $out, $code);
$this->assertSame(0, $code);
unlink(base_path('app/Nova/StubSmokeTest.php'));
}
Run it in the same CI job as your Nova upgrade branch. It is the only automated signal you will get that your stubs still match the framework.
Pipeline the output. Point Pint and PHPStan/Larastan at app/Nova in CI. Stubs are templates, not source — the generator output is what must pass review, and a linter catches leftover placeholder artifacts.
Published stubs always win, silently. There is no merge, no version check, no warning. Once stubs/nova/resource.stub exists, every future nova:resource uses it — including after you upgrade Nova. This is the single biggest failure mode: your templates quietly pin your generated code to an old Nova API while the rest of the app moves on.
This package predates Nova 4. Last release was 2020-11-30 (PHP 8 support only — no functional change). Nova 4 rewrote the generator output substantially: NovaRequest is passed into fields()/fieldsForIndex()/etc., field containers (Fields, ActionFields, Filters) replaced bare arrays, nova:resource changed which defaults it emits, and several commands were added or renamed. Applying these 2020-era stubs to a Nova 4+ app will generate classes that look fine and then throw TypeError/ArgumentCountError on first request. Diff against your installed Nova's stubs before adopting, every time. If the diff is large, use this package as a style reference and hand-write your stubs from Nova's current ones instead.
Placeholders are plain string replacement. Nova's commands do naive str_replace of tokens ({{ namespace }}, {{ class }}, DummyClass, {{ resource }}, and in some generators DummyModel/DummyLabel). A typo, or an extra space inside the braces, produces a class with a literal {{ class }} in it and no error anywhere. When you customize a stub, grep -o '{{[^}]*}}' it and confirm every token appears in Nova's corresponding command source; delete any token the command does not replace.
Verify your override is actually live. Rather than guessing which file was used, put a marker in the stub and generate:
printf '<?php // STUB-MARKER\n' | cat - stubs/nova/resource.stub > /tmp/r && mv /tmp/r stubs/nova/resource.stub
php artisan nova:resource Probe && head -1 app/Nova/Probe.php
If the marker is absent, your path is wrong or Nova is reading from a different directory in your version.
Re-publishing can clobber your edits. vendor:publish prompts before overwriting, but --force does not. Since you should be removing the package anyway, this becomes a non-issue — but never leave --force in a script that touches stubs/.
This is require-dev only. It has zero runtime footprint; if it ever appears in require, something went wrong in the install command.
Extension point worth knowing: the override is just a directory lookup. You can achieve the entire benefit without this package by copying a handful of files from vendor/laravel/nova/stubs/ into stubs/nova/ and editing them. Given the archived status, that is the recommended route for new work — the package's real contribution is showing you which stubs are worth cleaning up, and that is worth ten minutes of reading, not a permanent dependency.
MIT license means forking or vendoring is fully permitted; keep the license header if you copy files wholesale.
How can I help you explore Laravel packages today?