digital-creative/expandable-table-row
A Filament table plugin that lets users expand rows to reveal extra details, custom content, or nested information without leaving the list. Add collapsible, interactive rows to resource tables for richer data views.
This is a small, single-purpose package: it lets a Nova resource index row expand in place to reveal extra data, instead of forcing users into the detail page or a modal. Treat it as one or two public methods plus a Vue component, not a framework.
composer require digital-creative/expandable-table-row
php artisan vendor:publish --tag=expandable-table-row # check the repo for the exact tag
php artisan nova:publish # if you ship custom Nova assets
Read the source before the README. With ~19 stars and no dependents, src/ is short enough to skim in ten minutes and is the authoritative answer for method names, namespace, and what the component expects. Confirm it targets your Nova major version (last release Oct 2023 — Nova 3/4 are the realistic targets).
First use case: an Order index where the row shows #, customer, total, status — and expanding reveals line items, shipping address, and last payment attempt. That is exactly the "peek without navigating" job this package exists for.
// app/Nova/Order.php (method/class names: verify against src/)
public function expandableRow(): array
{
return [
'items' => $this->items()->with('product:id,name,sku')->get(),
'address' => $this->shipping_address,
'paid_at' => optional($this->paid_at)->toDateTimeString(),
];
}
Payload on the resource, not in the component. Keep the extra data in one resource method so it inherits the resource's authorization context and stays testable. Return arrays of primitives or already-serialized models, not collections the Vue side has to understand.
Lazy vs eager loading. The whole value proposition is that the index stays fast. Two workable shapes:
indexQuery() and hand the loaded relation to the expanded row.public static function indexQuery(NovaRequest $request, $query)
{
return $query->withCount('items')->with('latestPayment');
}
Row-level summaries are the sweet spot. items_count, last_activity_at, open_tickets, refund_total — cheap aggregates that answer "do I need to look closer?" before the user commits a click. Reserve the expanded panel for the detail that follows.
Authorize the payload separately. Index fields run through field-level authorization; a raw array returned from a resource method does not. Gate sensitive extras explicitly:
public function expandableRow(): array
{
$data = ['items' => $this->items];
if (Gate::allows('viewFinancials', $this->resource)) {
$data['margin'] = $this->margin;
}
return $data;
}
Compose with existing index customization. If you already use a custom indexQuery, fieldsForIndex, or metric cards, add expansion last and re-check pagination and sorting afterwards — the expanded <tr> is a sibling row, so anything that re-renders the table body (search, sort, filter, page change) will collapse it.
packages/ rather than pinning a version you may not be able to get fixes for. Check the repo's issue/PR activity before betting a core admin screen on it.<tr>. Keep overrides scoped to your own component rather than editing Nova's index styles.nova:publish ran after install, then the browser console for a failed chunk, then config/nova.php for asset registration. Payload empty but row expands → the resource method is being called but authorization or serialization is dropping it; dd() in the method first, network tab second.resources/js/ and adapt — a good move if you need lazy loading, custom empty states, or nested expansion that the package doesn't provide.How can I help you explore Laravel packages today?