shuvroroy/nova-dynamic-views
A Laravel Nova package for creating dynamic, configurable views in your admin panel. It lets you build reusable, data-driven Nova pages and tailor what users see without custom Vue components, with straightforward installation and integration into existing Nova resources.
1. Confirm the target before you install. v2.4.0 is a one-line compatibility bump for Nova 5.11.0. The changelog says nothing about earlier Nova versions, so check your Nova constraint first. If you are not on Nova at all, this package has no use to you.
composer require shuvroroy/nova-dynamic-views
Pin it ("shuvroroy/nova-dynamic-views": "2.4.*") — with 10 stars and zero dependents there is no community buffer between you and a surprise minor.
2. Reconcile the scope before writing code. The repo framing and release notes describe a header/toolbar override helper. The marketplace copy promises condition-driven field visibility, form/detail view switching, and context-aware layouts. Those are different products. Read src/ and the service provider in one sitting — it is a small package — and treat the header/toolbar framing as authoritative until the source proves otherwise. Ten minutes here prevents building a feature on a promise the package does not keep.
3. Publish and clear.
php artisan vendor:publish --provider="ShuvroRoy\NovaDynamicViews\NovaDynamicViewsServiceProvider"
php artisan view:clear
Note what actually landed — config file, views, nothing. Auto-discovery handles the provider unless you have it disabled.
4. Read the resolution order. This is the single most important thing to learn: global default → per-resource override → conditional override. Every confusing behaviour you will hit later traces back to a misunderstanding of which of those three won.
5. First use case — one resource, one branch. Take User. Replace its toolbar with a branding bar plus two quick links. Then open a resource you did not touch and look at it. If it changed too, your registration is global rather than scoped — that surprise at minute ten is far cheaper than at sprint end.
6. Document the native fallback on day one. Nova's own header() / toolbar() on the resource, plus Nova::header(fn () => view(...)) in NovaServiceProvider, cover most single-override needs. Keeping that path written down makes removing this dependency a refactor rather than a rewrite.
Global default plus surgical overrides. The entire value proposition is de-duplication. Register one generic default — environment badge, tenant name, support link — and override only where a resource genuinely diverges. If you catch yourself writing the same override on five resources, you have a missing global default, not five overrides.
Wrap every call in one internal adapter. This is the most important pattern in the whole package. Do not scatter package calls across forty resources:
namespace App\Nova;
class NovaChrome
{
public static function headerFor(string $resource): void
{
// delegate to shuvroroy/nova-dynamic-views here, and only here
}
public static function toolbarFor(string $resource): void
{
//
}
}
Give it your own vocabulary. If the package stalls, you rewrite one file. Cheapest insurance available.
Resolve conditions in PHP, not Blade. Instead of @if(auth()->user()->hasRole('admin')) inside a header partial, decide which view in the registration callback and let each Blade file stay dumb:
// conceptually — verify exact signatures against src/
if (auth()->user()->hasRole('tenant-admin')) {
// resolve tenant-scoped header view
} else {
// resolve default admin header view
}
Role, permission, and tenant checks belong in PHP where they are testable. Layout belongs in Blade.
Compose from partials, not copies. Have each dynamic view include reusable pieces (<x-nova.env-badge />, <x-nova.tenant-switcher />, <x-nova.quick-links />). You get per-user and per-tenant variation without maintaining N nearly identical header files.
Multi-tenant scoping through an explicit allowlist. Resolve the tenant before the view is chosen, and map identifiers through config — never through string concatenation:
// config/nova-chrome.php
return [
'headers' => [
'acme' => 'nova.headers.acme',
'globex' => 'nova.headers.globex',
'default' => 'nova.headers.default',
],
];
This is where a "dynamic view" package can genuinely hurt you: never let a tenant slug or route parameter flow directly into a view name.
Smoke-test rendered output, one test per view.
$this->actingAs($admin)
->get('/nova/resources/users')
->assertOk()
->assertSee('Production');
There are no public failure reports for this package, so your suite is the documentation of expected behaviour — and it is what catches a Nova minor bump silently changing resolution.
Feature-flag the rollout. NOVA_DYNAMIC_CHROME=false falling back to stock Nova chrome gives you a one-line rollback without redeploying application code.
Version-bump ritual. Every Nova upgrade gets a manual pass through each dynamic view in staging before production. Nova minors have changed view-resolution behaviour before, and there is no vendor support to catch it for you.
Log::debug() around the resolved view name and the condition that matched, reproduce, delete. On a codebase this small that beats hunting through issues.php artisan view:clear on every deploy that touches views. A stale compiled view is the classic "works locally, wrong on staging" failure. Put it in your deploy script next to config:cache.Nova::script() / Nova::style(). Nothing is compiled for you.Nova::header() / resource header() fallback documented so removal stays a refactor.How can I help you explore Laravel packages today?