Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Custom Views Laravel Package

digitalcloud/nova-custom-views

Add fully custom views to Laravel Nova resources. Define your own index, detail, and form screens with Blade templates, keeping Nova's navigation and authorization while giving you complete control over layout and data.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the source before you write an integration. The package is a few hundred lines of glue, and the two sources disagree on what it actually does: the description sells navigable custom pages wired into Nova's menu and routes, while the field-level review describes a Nova field that renders a Blade partial on a resource screen. Those are different products with different integration points (route + menu registration vs. an entry in fields()). Open src/ end-to-end first — provider, main class, Vue component — and confirm which shape you're getting. It's a ten-minute read that saves you a wasted sprint.

Install

composer require digitalcloud/nova-custom-views

Service provider discovery handles registration on Laravel 5.5+. There's no meaningful config surface. If the package ships publishable views or JS assets, publish them and commit the output so a fresh clone doesn't depend on a publish step.

Where to look, in order

  1. composer.json — the laravel/nova constraint. Last release was 2018-11-12, which puts it in the Nova 1.x era. If you're on Nova 3+, this is your first and possibly last check.
  2. src/ — the service provider (what it registers: routes, views, menu entries, fields), then the main class. Get the exact method names for setting the view and passing data; don't copy method names from a blog post, the docs are too thin.
  3. resources/js/ — the Vue component that renders the HTML. Note whether it uses v-html (an escaping risk) and whether it's registered globally or per-component. This is what breaks first on a Nova upgrade.

First use case: a read-only detail panel. Pick something you'd never edit — a health/status block, a Stripe customer snapshot, a rendered changelog, an external system's sync state. Read-only means no validation, no fillable behaviour, no form lifecycle.

use DigitalCloud\NovaCustomViews\CustomView; // verify FQCN against src/

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),

        CustomView::make('Health')
            ->view('nova.panels.health')
            ->with(fn ($model) => [
                'status'    => $model->health_status,
                'checkedAt' => optional($model->health_checked_at)->toDateTimeString(),
            ])
            ->onlyOnDetail(),
    ];
}

Then resources/views/nova/panels/health.blade.php renders with those variables. A server-rendered partial inside a Nova detail screen in five minutes, no JS build, no Vue. That's the whole value proposition — measure everything else against it.

If you actually need a full page, the integration changes: register the view, add the menu entry, and confirm which middleware and auth the route inherits. Verify policy enforcement explicitly — don't assume Nova's resource authorization carries over.

Implementation Patterns

Keep the field a viewport, not a form control. Everything below is about preserving that boundary.

Compute in the callback, render dumb in Blade. Don't run queries inside the template. Build a plain array in the data callback and let the Blade iterate:

->with(function ($model) {
    return [
        'orders'  => $model->orders()->latest()->limit(5)->get(['id','total','created_at'])->toArray(),
        'revenue' => (float) $model->orders()->sum('total'),
    ];
})

This keeps the template reusable, keeps the payload small (see the serialization gotcha), and lets you preview the Blade with a hand-built array.

Name views after the concern, not the resource. Because data is injected explicitly, one partial can back several resources — a health panel on Server, Database, and Queue. Keep them in resources/views/nova/panels/ and push the differences through the callback.

Group with Nova's own layout primitives. A custom view is still a field; nest it in a Panel or Tab alongside real fields so the card chrome, spacing, and collapse behaviour match the rest of the screen:

Panels\Panel::make('Billing', [
    CustomView::make('Stripe')->view('nova.panels.stripe')->onlyOnDetail(),
]),

Default to ->onlyOnDetail(). Index cells are narrow table columns; a rich partial overflows, inflates row height, or both. If you need something on the index, restrict the view to a single badge and check it at 1024px and 1440px.

Hide it on forms. The field carries no validation and isn't fillable. Without onlyOnDetail() or exceptOnForms(), users hit a blank, non-interactive block on the create screen and file a bug.

Push interactivity outward. A button should link to a Nova Action, a Tool page, or an external URL. Wiring Alpine or axios into a 2018-era field's injected HTML means fighting the component lifecycle and the Nova asset pipeline, and you'll lose. The rule: if the view needs to know something after page load, it has outgrown this package.

Enforce authorization in the view, not just the route. Nova's resource policies govern resource screens; a custom page or an arbitrarily embedded partial may not inherit them. If the screen exposes cross-tenant data or anything beyond what the surrounding resource already shows, add an explicit Gate::allows() / $this->authorize() check and cover it with a test.

Stay inside the app's styling conventions. Reuse existing Blade components and layout partials. The win is that a Blade-fluent, Vue-light team ships admin UI; the failure mode is a second, divergent styling system bolted into Nova.

Gotchas and Tips

Version drift is the headline risk. Last release 2018-11-12, 79 stars, zero dependents, no security response path. Nova 2 reworked field/meta serialization; Nova 3 and 4 changed the Vue and asset pipelines. Expect the JS component to fail to register on modern Nova — a blank panel or an "unknown component" console error — even when the PHP side installs and boots cleanly. Prove it on one throwaway resource in a branch before committing to a build.

On Nova 4+, skip the package. A native custom field is roughly thirty lines and is supported:

class HealthPanel extends Field
{
    public $component = 'health-panel';

    public function __construct($status, $checkedAt)
    {
        parent::__construct('Health');
        $this->withMeta(compact('status', 'checkedAt'));
    }
}

Register health-panel in resources/js/fields.js with a small Vue component that renders the meta. That's the entire package's functionality, written against the API your Nova version actually ships, with no abandoned dependency in your lockfile. For a full page, a native Nova Tool is the equivalent replacement.

Blank panel triage, in order: (1) browser console for a Vue "unknown component" error — that's a JS registration failure, not a PHP one; (2) composer show digitalcloud/nova-custom-views to confirm what installed; (3) whether the view namespace resolves (view:clear after moving files); (4) whether published assets exist and are current. PHP-side errors surface in the Nova log; a silently empty panel is almost always JS or view resolution.

Serialization is the hidden cost. Whatever you pass into the view is typically serialized into the page payload. Passing whole Eloquent models — especially with relations loaded — bloats the response and can leak attributes you didn't mean to expose. Pass arrays, pass scalars, select explicit columns.

Escaping. If the Vue component injects HTML with v-html, any user-controlled string reaching the Blade without escaping is an XSS vector. Stick to {{ }} in the partial and audit anywhere you're tempted by {!! !!}.

Cache and assets. Nova caches compiled assets aggressively. After changing the package JS or published views, php artisan nova:publish plus a hard refresh is the difference between "it's broken" and "it's stale".

Fork it if you keep it. With zero dependents and no releases since 2018, treat this as a vendored fork, not a dependency. Pin the version, move it into your namespace, add a compatibility test against your Nova/Laravel/PHP matrix, and name an owner. An unmaintained package without a named owner becomes an unplanned migration at your next Nova upgrade.

Set the exit condition up front. If a Nova major upgrade is anywhere on the roadmap, the honest framing is that the upgrade is the retirement date for this package — migrate to native fields/tools or rebuild the handful of screens as plain Laravel routes with policies. Decide that now, while the screens are few and the cost is a day, rather than during a platform upgrade as a surprise line item.

Debug the data in the callback. dd() inside with() rather than inside the Blade — the callback is where the query and transformation live, and stalling there gives a cleaner signal than a half-rendered page.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource