twentyonetf/ajax-table-card
twentyonetf/ajax-table-card is a Laravel package for rendering AJAX-driven table cards, enabling dynamic tabular content and updates without full page reloads. Suitable for dashboards, listings, and search results; small community adoption.
This is a Nova card, so everything happens inside an existing Laravel Nova installation — there is no standalone usage.
Install into a Nova app
composer require twentyonetf/ajax-table-card
Confirm your nova version constraint is satisfied. The last tag is from March 2023, so it targets Nova 4.x-era APIs; verify against your Nova 4/5 install before committing.
Look at the package source first, not the README. With a 4-star, single-maintainer card, the src/ card class, the Vue components under resources/js, and the routes/ or controller that serves the AJAX payload are the real documentation. Read the card constructor signature to learn which options are exposed.
Publish what's publishable
php artisan vendor:publish --provider="Twentyonetf\\AjaxTableCard\\CardServiceProvider"
Check --tag options in the provider — typically config, assets, and views. If assets need rebuilding for your Nova version, be prepared to run npm install && npm run prod inside the package.
First use case — a dashboard card
Register in app/Nova/Dashboards/Main.php:
use Twentyonetf\AjaxTableCard\AjaxTableCard;
public function cards()
{
return [
(new AjaxTableCard())
->endpoint('/nova-vendor/ajax-table-card/rows')
->columns([
['key' => 'id', 'label' => 'ID'],
['key' => 'name', 'label' => 'Name'],
['key' => 'status', 'label' => 'Status'],
]),
];
}
Exact method names (endpoint, columns, title) depend on the card class — confirm in source. Render the dashboard and watch the Network tab to see the AJAX request and response shape. That round-trip is the fastest way to understand the contract the package expects.
Dedicated AJAX endpoint controller. The card's whole value is that rows are fetched client-side, so you own the endpoint. Put it behind Nova's auth middleware and return a flat array of rows:
Route::middleware(['nova'])
->prefix('nova-vendor/ajax-table-card')
->group(function () {
Route::get('/rows', [AjaxTableCardController::class, 'index']);
});
Server-side pagination/sorting. Read query params from the request and translate into a query builder call. Return Nova's expected pagination metadata ({ data: [...], meta: { current_page, last_page, total } }) if the card's Vue component reads those keys — check the component before assuming.
public function index(Request $request)
{
return Order::query()
->when($request->search, fn ($q, $s) =>
$q->where('reference', 'like', "%{$s}%"))
->orderBy($request->sort ?? 'created_at', $request->direction ?? 'desc')
->paginate($request->per_page ?? 15);
}
Resource pages vs dashboards. Cards work on both. On a Resource page, use $request->resourceId from Nova's context to scope the query — but remember the AJAX request is a separate request, so you must pass the resource id back through the card config or URL:
(new AjaxTableCard())->endpoint(
'/nova-vendor/ajax-table-card/rows?resource_id=' . $this->resource->id
)
Column-driven rendering. Define columns as an array of key/label pairs and let the Vue side render them. For richer cells (badges, links), check whether the package supports a render callback or slot — if not, you can often inject HTML from the server, but do it deliberately and escape untrusted data.
Caching. Since every page load hits your endpoint, wrap expensive queries in Cache::remember() keyed on sort/filter params. For dashboards hit by many admins this is the single biggest performance win.
Auth is easy to get wrong. The AJAX call is made from the Nova SPA with the session cookie. If you register the route outside Nova's nova middleware group, you'll get 401s that look like data problems. Test with php artisan route:list and reproduce the request in the Network tab before debugging the Vue side.
CSRF / 419s. GET requests don't trigger CSRF, but if you add POST (e.g. row actions), include X-CSRF-TOKEN — Nova usually handles this automatically, but custom fetch calls in a forked Vue component won't.
Version drift. Last release: March 2023. Nova's Vue 2 → Vue 3 migration in Nova 4.4+/5 changed component APIs and card asset compilation. If the card compiles but renders blank, the built JS is likely targeting an older Vue runtime. Either rebuild from source or pin Nova to a compatible minor.
Serialization surprises. AJAX responses bypass Nova's normal field resolve() cycle. Eloquent models go straight through toArray(), so dates come out as ISO strings, Carbon diffs don't render, and relationships need explicit with(). Do the shaping in the controller — don't expect Nova field classes to transform the payload.
N+1 on the endpoint. Because the whole table is one query + render, a missing with('customer') in a 15-row table silently becomes 15 extra queries on every card load. Add DB::listen() in a local environment while testing.
Debugging workflow:
storage/logs/laravel.log for endpoint exceptions.NOVA_DEV / npm run watch if you fork the Vue component.php artisan nova:card to generate a fresh card and diff its structure against this one — often faster than reading third-party source.Extension points to inspect. Before forking, check the card class for ->withMeta(), ->width(), and a component() override. Most Nova cards let you swap the Vue component while keeping the server-side config; if this one does, you can rebuild the table UI without touching the AJAX plumbing.
Config quirks. If a published config exists, note that Nova cards are instantiated per-request — config values read in the constructor are cached for the whole request but not across requests. Prefer passing options via the card chain over editing config for per-card behavior.
Maintenance risk. Zero dependents and a low release cadence means you should budget for a fork. Pin the version in composer.json, keep a local copy of any patched Vue build, and add a smoke test (hit the endpoint, assert 200 + row count) so an unrelated Nova upgrade surfaces breakage immediately.
How can I help you explore Laravel packages today?