caddydz/nova-preview-resource
Adds a preview button to Laravel Nova resource index rows, letting you open a read-only view of a record without entering the edit form or leaving the list. Lightweight and easy to register on any Nova resource; check compatibility with your Nova version.
Minimal path from composer require to a working preview popup:
Install the package (it targets Nova 4-era APIs; last tagged release Feb 2023):
composer require caddydz/nova-preview-resource
If it ships frontend assets, publish them and rebuild Nova:
php artisan vendor:publish --provider="CaddyDz\NovaPreviewResource\ToolServiceProvider"
php artisan nova:publish
Look here first, in this order:
README.md (sparse for a 3-star package — treat the source as the docs)src/ for the trait/concern you apply to a Resource (e.g. NovaPreviewResource, Previewable, or similar)routes/ or src/Http/ for the endpoint the popup calls — this tells you exactly what data is serialized and what authorization, if any, is enforcedresources/js/ for the Vue modal component and how the trigger is registered on the indexApply the trait to the Nova resource you want previewable:
use CaddyDz\NovaPreviewResource\NovaPreviewResource; // verify exact FQCN in src/
class Post extends Resource
{
use NovaPreviewResource;
public static $model = \App\Models\Post::class;
public function fields(NovaRequest $request)
{
return [/* full detail fields */];
}
}
Define what the popup shows. Most preview packages expose a method (e.g. previewFields()) that returns a subset of fields(). Start with a small read-only set (ID, Text, DateTime, BelongsTo) — those render reliably in a modal. Register the resource in NovaServiceProvider::resources() and hit the index view; a preview trigger (eye icon / button) should appear per row.
First real use case: a content team working entirely from the index — click the preview on a Post/Order row to confirm body copy, status, and relations without navigating to the detail page or loading the edit form.
Reuse fields() instead of duplicating. The highest-value pattern is to extract your preview subset into one method and reference it from both places, so the modal never drifts from the detail page:
public function fields(NovaRequest $request)
{
return array_merge([
ID::make()->sortable(),
Text::make('Title'),
Badge::make('Status'),
], $this->previewFields($request));
}
public function previewFields(NovaRequest $request)
{
return [
Textarea::make('Body')->onlyOnDetail(),
BelongsTo::make('Author', 'author', User::class),
];
}
Keep the preview read-only by construction. Use ->readonly() on fields inside the preview set and avoid Text::make(...)->rules(...) or anything that implies a form submit. A preview modal that accidentally posts back to a detail/update endpoint is the classic failure mode.
Pair it with onlyOnIndex triggers, not a new page. The package's value is that the popup overlays the index. If you find yourself building a custom detail route for the preview, you've reimplemented Nova's detail view — stop and use ResourceDetail instead.
Authorize before rendering. In the preview fields method (or the package's controller, if it exposes one), gate on the same ability Nova uses on detail:
public function previewFields(NovaRequest $request)
{
abort_unless($request->user()->can('view', $this->resource), 403);
return [ /* ... */ ];
}
The popup must not become a policy bypass that leaks fields the index already redacts.
Extend it for related previews. A common workflow is to preview a parent record when only a relation is shown on the index — e.g. on Comment rows, preview the parent Post. Point the preview at the related model's resource fields rather than the comment's.
Keep the field list short. A modal is roughly 60–70% viewport; more than ~8–10 fields scrolls awkwardly on the index. Put the "everything" view behind the actual detail page and reserve the popup for the 3–5 fields people check constantly (status, price, title, timestamps).
Nova version compatibility is the main risk. Last release 2023-02-19 with 0 dependents and single-digit stars — it was written against a specific Nova 4 minor and may not work on Nova 5. Before adopting, check composer.json for the laravel/nova constraint and pin Nova accordingly, or fork and patch. Budget a half-day to verify on your Nova version rather than assuming.
"Sparse docs" means source is the spec. Read the trait and any registered route/controller before writing anything. Specifically confirm: (a) the exact trait namespace, (b) the method name that supplies preview fields, (c) whether the popup hits a backend endpoint or serializes fields client-side, (d) whether a service provider is auto-discovered.
Relationship fields are the usual rendering failure. BelongsTo, HasMany, and MorphTo often render as empty or as a broken link inside the modal because they depend on resources loading via the detail route. Test each relationship field in the preview explicitly; if it fails, fall back to Text::make('Author', fn () => $this->author?->name).
File and image fields. Image and File fields typically depend on Nova's file-serving route and the Nova request context. They frequently show a broken thumbnail in a custom modal. Swap to a plain Text with the URL, or accept it as a known gap.
Asset caching bites immediately after install. If the trigger icon doesn't appear, the sequence is: php artisan nova:publish, npm run prod in the Nova assets path (or php artisan vendor:publish for the package's compiled asset), hard-refresh the browser, and clear storage/framework/views. A published-but-cached JS bundle is the most common "it installed but nothing happens".
Computed and closure fields can leak N+1 queries. Preview fields are evaluated per row click, but if you build them with closures that touch relations, you'll feel it on wide indexes. Mirror the with() eager loads your index controller already uses.
Check what the popup serializes before trusting it in production. If the package exposes an endpoint, confirm it applies Nova's Authorizable/policy checks and doesn't return the full model (including hidden-sensitive attributes) to any authenticated Nova user. With 3 stars and no dependents, treat the security surface as unvetted and wrap it with your own can('view') guard.
Extension point: if the built-in modal is too rigid, the cleanest exit is to keep the package's trigger and swap the modal body for your own Vue component, or replace it entirely with a Nova Lens/Modal action from laravel/nova-actions — the latter is maintained and worth the migration if this package becomes a maintenance burden.
How can I help you explore Laravel packages today?