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 List Card Laravel Package

thijssimonis/nova-list-card

A Laravel Nova card that displays records from any model as a clean, configurable list on your dashboard. Show recent items, top entries, or filtered results with customizable columns, sorting, and resource links—ideal for quick at-a-glance overviews without custom code.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm you're in the right place first. This is a Nova-only card — one PHP class extending Laravel\Nova\Card, one Vue component, a service provider, and (hopefully) compiled assets. If the project isn't on Nova, this package does nothing. Check composer.json for the Nova major constraint; the package was scaffolded in the Nova 3 era, so Nova 4/5 (Vite, Vue 3) is a rewrite, not a bump.

Install and verify assets before anything else:

composer require thijssimonis/nova-list-card
php artisan vendor:publish --provider="Thijssimonis\\NovaListCard\\CardServiceProvider"

If the provider publishes nothing, confirm the compiled JS/CSS is committed in the repo (dist/ or public/). A blank card panel with no error is almost always the asset step — not your code.

Read the source as the spec. There is no real documentation; the fluent API is a set of one-line setters in src/. Read in this order:

  1. src/ — the card class. That's your entire API surface.
  2. resources/js/ — the Vue component. This tells you whether data arrives via withMeta() or the cards/{card} endpoint, and whether cells render with v-html.
  3. composer.json — Nova constraint and asset strategy.

Thirty minutes here beats an afternoon of guessing method names.

First use case — "latest N" on the dashboard. Register in app/Providers/NovaServiceProvider.php:

use Thijssimonis\NovaListCard\ListCard;

public function cards()
{
    return [
        ListCard::make()
            ->title('Latest signups')
            ->model(\App\Models\User::class)
            ->orderBy('created_at', 'desc')
            ->limit(10)
            ->columns([
                'name'  => 'Name',
                'email' => 'Email',
            ]),
    ];
}

Reload the dashboard. Rows appear, or you have an asset problem — there is no third outcome.

Decide the boundary early. If the requirement grows search, sorting, filtering, pagination, export, or drill-down, you've outgrown the card. That's a Nova resource index page, not a dashboard card. Making that call before you write code prevents the classic "we'll just extend it" one-day card becoming a two-sprint grid.

Implementation Patterns

Global dashboard card vs. per-resource card. NovaServiceProvider::cards() is the right home for an ops overview. The same class can be registered inside a resource's cards() method for "recent activity on this record" — but the card is instantiated per page load, so scope the query with a closure bound to $this->resource. Never hand a per-resource card a global query.

Lean on existing scopes. The card is a thin wrapper over Eloquent; anything you already have chains cleanly:

ListCard::make()
    ->title('Open tickets')
    ->model(Ticket::class)
    ->scope(fn ($query) => $query->where('status', 'open'))
    ->orderBy('priority', 'desc')
    ->limit(8)
    ->columns([
        'id'       => 'Ticket',
        'subject'  => 'Subject',
        'priority' => 'Priority',
    ]);

Link rows to their resource. A list users can't click is a report, not a dashboard. Wire the card-level "view all" to the resource index, and per-row links if the Vue component supports it. If it doesn't, that's a small, well-scoped addition to a fork.

Eager-load anything that resolves across relationships. The card fires on every dashboard load, so a column reaching into customer.name is an N+1 waiting to be discovered in production:

->scope(fn ($query) => $query->with('customer:id,name')->latest()->limit(10))

Pin columns explicitly — never infer. Dashboards are visual-regression surface. A hand-defined column map keeps screenshot baselines stable and keeps users oriented between deploys. Deriving columns from the first row guarantees churn.

Cache the payload when the backing table is large. The query runs on every admin page load:

->scope(fn ($query) => Cache::remember(
    'nova.latest-signups',
    now()->addMinutes(5),
    fn () => $query->latest()->limit(10)->get()
))

Note the limit() is inside the cached closure so you cache 10 rows, not 50,000.

Compose several small cards. Three focused cards ("Latest signups", "Open tickets", "Failed jobs") read better than one tall table and let you tune limit and refresh per surface. Keep each card to one question.

Authorize at the query, not in the card. There is no policy consultation. For multi-tenant or permission-scoped models, put the constraints in the scope() closure. Handing the card an unscoped query and expecting Nova's policy layer to filter rows is the fastest way to leak data onto an admin dashboard.

Feature-flag the first deploy. Ship behind a Nova::when() / config toggle on one non-critical dashboard. If it breaks, you flip a flag instead of reverting a merge.

Gotchas and Tips

Archived is the headline risk. No Nova 4/5 work, no Vue 3/Vite migration, no PHP 8.3 / Laravel 11+ test matrix, no security-response path. The responsible integration mode is to vendor the source into App\Nova\Cards\ListCard with a documented fork date and owner, rather than leaving an unmaintained Packagist reference in your lockfile. Timebox the evaluation to one day.

Verify cell escaping before it ever hits a real admin dashboard. If the Vue component renders cells with v-html, any user-editable model content is a stored-XSS vector running with admin session privileges. This is the single most important line to check in the source audit. Grep the component for v-html first, everything else second.

Row-level authorization is entirely yours. The card renders whatever the query returns. Multi-tenant scoping, soft-delete filters, permission constraints — all of it belongs in the query. There is no safety net.

Empty state is usually missing. Zero rows typically renders as a blank panel, which reads as broken and burns support time. Add an empty-state message to your fork, or seed with a placeholder row (less pleasant).

Assets are the silent failure mode. When the card doesn't appear: check public/vendor/, the compiled bundle, and whether the provider registered Nova::script/Nova::style. Do that before debugging the PHP — it's almost never the PHP.

Cap server-side, always. limit(5)–limit(10) in the query. Never rely on the frontend to truncate — the full result set still travels over the wire and still occupies memory on the worker.

Nova upgrades are your migration work now. The Vue component is the fragile part, and a broken card can take down the surrounding dashboard bundle, not just itself. Set a retirement trigger: fold it into an in-house class at the next Nova major rather than patching a fork indefinitely.

Check theming after any Nova or Tailwind bump. The card predates current Nova theming conventions. Test dark mode and narrow viewports, and treat both as visual-regression candidates.

Know the fallback. The underlying need — a read-only table in a Nova card — is roughly 50–150 lines against Laravel\Nova\Card. If the Vue side doesn't render cleanly against your Nova version, build it in-house. Debugging an unmaintained fork past a day of effort costs more than writing the class, and you keep full control of escaping, empty state, and row links.

Exit path is cheap — keep it that way. One registration line in NovaServiceProvider::cards(). Preserve that: don't scatter card instantiation across resources, and don't let other code depend on the package namespace directly. Then removing it later is a deletion, not a refactor.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo