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

jubeki/nova-card-linkable

jubeki/nova-card-linkable makes Laravel Nova dashboard and resource cards clickable, letting you attach an internal route or external URL so the whole card acts as a link. Lightweight, simple to configure, and handy for navigation tiles.

View on GitHub
Deep Wiki
Context7

Getting Started

jubeki/nova-card-linkable gives you a Nova dashboard card whose entire surface is a link target. It is a ~1 file PHP + 1 file Vue package (19 stars, zero dependents, last release April 2022), so read the source before you trust any method name in a blog post — including this one. Verify the fluent API against README.md and composer.json constraints in the same sitting.

  1. Check compatibility first. Open the repo's composer.json and confirm the laravel/nova constraint includes your installed major. Nova 3.x/early-4.x is the era this was built in; Nova 5 is a coin flip. Pin the version:
    composer require jubeki/nova-card-linkable:2.0.1
    
  2. Publish Nova's card assets (once, and again after any Nova or package upgrade):
    php artisan nova:publish
    
  3. Register a card in app/Nova/Dashboards/Main.php:
    use Jubeki\NovaCardLinkable\Linkable;
    
    public function cards(): array
    {
        return [
            Linkable::make('Pending Orders')
                ->url('/'.Nova::path().'/resources/orders?pending=1')
                ->width('1/3'),
        ];
    }
    
  4. Where to look first: vendor/jubeki/nova-card-linkable/src/ — there is so little code that you should read all of it before writing your first card. It tells you the exact constructor signature, whether ->url() accepts a closure, and whether the card wraps another card/metric or is a standalone card you must style yourself. That last distinction drives everything else on this page.

First real use case: a dead-end KPI tile that becomes a drill-down. "Pending Orders: 42" → click → the filtered OrderResource index showing exactly those 42 rows. That single conversion is usually enough to prove whether the pattern is worth keeping.

Implementation Patterns

Build URLs from Nova's config, never from /nova. The path is configurable (config/nova.php → path), and hard-coding /nova/resources/orders silently breaks the moment someone changes it or mounts Nova on a subdomain path:

$base = '/'.Nova::path().'/resources/'.\App\Nova\Order::uriKey();
Linkable::make('Orders')->url($base);

Match filter keys, not class names. Nova's index filters are applied via a query string keyed by the filter's key() method, so the link only works if it matches:

Linkable::make('Pending Orders')
    ->url($base.'?'.http_build_query([
        'pending_orders_filter' => 'pending',   // Filter::key()
    ]));

Use http_build_query() rather than string concatenation — order IDs, search terms, and dates with spaces will break a hand-glued URL, and it gets you multiple filters for free:

->url($base.'?'.http_build_query([
    'orders_filter' => 'overdue',
    'per_page'      => 50,
]))

Wrap, don't restyle. If your version accepts a card or metric instance, wrap your existing Value/Trend metric so you keep number formatting, ranges, and progress bars and only add the link:

Linkable::make(
    Value::make('Revenue', fn () => Order::sum('total'))
)->url($base);

If the constructor takes only a title string, treat the card as a styled tile and put the number in the title — and accept that you've re-implemented the metric yourself. This is the main reason most teams eventually write their own card.

One card per dashboard entry, not per resource. Cards are cheap, so a common pattern is a "dashboards index" where each card points at another dashboard with a different query string (?team_id=3, ?range=ytd). You get a navigable landing page without a single line of Vue.

Centralise card construction. Dashboards accumulate five or six near-identical link cards; put a private helper on a base dashboard class:

protected function linkCard(string $title, string $uri, array $query = []): Linkable
{
    return Linkable::make($title)
        ->url('/'.Nova::path().'/resources/'.$uri.($query ? '?'.http_build_query($query) : ''))
        ->width('1/3');
}

Guard visibility with the card's own authorization. The link itself performs no authorization check, so pair every card with a canSee() that mirrors the target resource's policy:

Linkable::make('Open Orders')
    ->url(...)
    ->canSee(fn (Request $request) => Gate::check('viewAny', Order::class));

Without it, a support-tier admin sees a clickable tile that lands on a 403 — the worst of both worlds.

External links and tools. Pointing at Grafana, a BI dashboard, or a custom Nova tool works the same way, but verify the rendered markup is a real <a href> before relying on middle-click / "open in new tab". If the Vue component uses a JS click handler instead, users lose browser-native link behaviour.

If you need more than a static link, fork now. Conditional targets based on request state, click analytics, per-row drill-downs — none of that is in a 2022 micro-package. Copying src/ plus the Vue component into app/Nova/Cards/ takes an hour and removes the dependency question permanently.

Gotchas and Tips

  • It is unmaintained. No release cadence since April 2022, and 2.0.1 was a packaging fix (removing a stray cards.png), not a runtime fix. Treat a Nova major upgrade as a scheduled re-verification of every card, not a composer update you do on a Friday.
  • Assets are the #1 "it renders as a blank box" cause. Run php artisan nova:publish and confirm something landed under public/vendor/. Check the package tarball for a prebuilt dist/ — if it requires a Node build and you have no build step in CI, that's a blocker, not a bug.
  • Stale assets after config changes. If you change config/nova.php or re-register cards, clear php artisan config:clear and hard-refresh; the compiled card bundle is cached aggressively.
  • SPA vs full reload. Nova's frontend is a Vue SPA. A card anchor that isn't intercepted by the router triggers a full page load — functionally fine, but users lose dashboard scroll state. Note it as expected behaviour before someone files a bug.
  • Filters silently don't apply. If the URL loads the index but the filter isn't active, the query key is wrong (class name vs key()), or the filter is hidden for that user's canSee. Cross-check with php artisan route:list | grep nova and the filter's own key() return value.
  • viaResource / viaResourceId params. Links into a relation's index need those extra query parameters to render; a bare resource URL will 404 or show the wrong context.
  • No open-redirect protection is your job. If any part of the URL is user- or config-driven, validate the scheme/host before passing it to ->url(). Static hard-coded links are low risk; a settings-driven "report URL" card is not.
  • Debugging a dead link: inspect the rendered HTML for the actual href, not the PHP you wrote. Nine times out of ten the problem is the URL string, not the package.
  • width() / height() are the only layout levers. Titles truncate at 1/3 on narrow screens — prefer short titles and let the link carry the meaning.
  • Pin the Composer version ("jubeki/nova-card-linkable": "2.0.1") and note in your upgrade checklist that Nova upgrades invalidate the pin. With one dependent-less package in the tree, composer why output is your audit trail.
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