Version: 1.5.2+
Last Updated: December 30, 2025
Status: Production Ready ✅
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Transaction',
'columns' => [
['key' => 'amount', 'label' => 'Amount'],
['key' => 'date', 'label' => 'Date'],
['key' => 'description', 'label' => 'Description'],
],
'sortBy' => 'date', // ✅ Sort by 'date' column initially
'sortDirection' => 'desc', // ✅ Sort in descending order (newest first)
])
date columnThe sortBy parameter specifies which column to sort by initially:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'id', 'label' => 'ID'],
['key' => 'name', 'label' => 'Name'],
['key' => 'price', 'label' => 'Price'],
],
'sortBy' => 'name', // Start sorting by name column
])
The sortDirection parameter controls the initial sort order:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [...],
'sortBy' => 'price',
'sortDirection' => 'asc', // Lowest to highest
])
Valid Values:
'asc' - Ascending order (A→Z, 1→∞)'desc' - Descending order (Z→A, ∞→1)The package also supports the legacy sort parameter:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [...],
'sort' => 'desc', // Still works! (backward compatible)
])
Sort by columns from related models:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Order',
'columns' => [
['key' => 'order_number', 'label' => 'Order', 'sort' => true],
[
'key' => 'customer_name',
'label' => 'Customer',
'relation' => 'customer:name', // Sort by customer.name
],
[
'key' => 'amount',
'label' => 'Amount',
],
],
'sortBy' => 'customer_name', // Sort by customer.name initially
'sortDirection' => 'asc',
])
How it Works:
relation field in columncustomer.name columnSort by relationship count:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Category',
'columns' => [
['key' => 'name', 'label' => 'Category'],
[
'key' => 'products_count',
'label' => 'Product Count',
],
],
'sortBy' => 'products_count', // Sort by count of products
'sortDirection' => 'desc', // Most products first
])
How it Works:
_count → auto-detectedproducts from products_countwithCount('products') added to querySort by values inside JSON columns:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\User',
'columns' => [
['key' => 'name', 'label' => 'Name'],
[
'key' => 'metadata',
'label' => 'Country',
'json' => 'address.country', // Sort by JSON path
],
],
'sortBy' => 'metadata', // Column key
'sortDirection' => 'asc',
])
How it Works:
json key with pathaddress.countryJSON_EXTRACT() function✅ SORTABLE:
['key' => 'name', ...]['relation' => 'user:name', ...]['key' => 'items_count', ...]['key' => 'data', 'json' => 'field', ...]❌ NOT SORTABLE:
['relation' => 'user.profile:name', ...]['function' => 'getStatus', ...]['raw' => '<button>...</button>', ...][@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Product',
'columns' => [
// ✅ SORTABLE - Database column
['key' => 'name', 'label' => 'Product Name'],
// ✅ SORTABLE - Related column
['key' => 'brand_name', 'label' => 'Brand', 'relation' => 'brand:name'],
// ✅ SORTABLE - Count column
['key' => 'reviews_count', 'label' => 'Reviews'],
// ❌ NOT SORTABLE - Function column
['key' => 'status', 'label' => 'Status', 'function' => 'getStatus'],
// ❌ NOT SORTABLE - Raw HTML
['key' => 'actions', 'label' => 'Actions', 'raw' => '<a>Edit</a>'],
],
])
The table automatically shows indicators:
Name ↓ (Sorted ascending by Name)
Brand ↑ (Sorted descending by Brand)
Reviews (Not currently sorted)
Status (⚠️) (Not Sortable - function column)
Actions (Not Sortable - raw HTML)
The system automatically optimizes sorting:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Order',
'columns' => [
['key' => 'order_number', 'label' => '#'],
['key' => 'customer_name', 'label' => 'Customer', 'relation' => 'customer:name'],
['key' => 'items_count', 'label' => 'Items'],
['key' => 'total', 'label' => 'Total'],
],
'sortBy' => 'customer_name',
'sortDirection' => 'asc',
])
Generated SQL (Optimized):
SELECT DISTINCT orders.*
FROM orders
LEFT JOIN customers ON orders.customer_id = customers.id
WITH COUNT(order_items) as items_count
WHERE orders.status = 'completed'
ORDER BY customers.name ASC
LIMIT 50;
Query Count: 1 query for 50 rows ✅ No N+1 Queries: ✅
Use Related Columns Properly
✅ Good: 'relation' => 'category:name'
❌ Bad: Manually fetching relationship
Leverage Count Columns
✅ Good: 'key' => 'items_count'
❌ Bad: Loading all items and counting in PHP
Avoid Nested Relations for Sorting
✅ Good: Sort by primary relation only
❌ Bad: 'relation' => 'user.profile.country:name'
Pagination + Sorting
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [...],
'sortBy' => 'created_at',
'sortDirection' => 'desc',
'records' => 25, // 25 items per page
])
Symptom: Clicking column header doesn't change order
Solutions:
'key' field// ✅ WORKS - Database column
['key' => 'email', 'label' => 'Email']
// ❌ DOESN'T WORK - Computed property
['key' => 'full_name', 'label' => 'Full Name'] // If only exists in code
Symptom: Related data appears but sort is incorrect
Cause: Incorrect relation format
Fix:
// ❌ WRONG - Missing column name
['key' => 'category_id', 'label' => 'Category', 'relation' => 'category']
// ✅ CORRECT - Full relation path
['key' => 'category_id', 'label' => 'Category', 'relation' => 'category:name']
Symptom: sortBy and sortDirection are ignored
Cause: Column doesn't exist or isn't sortable
Solution:
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'created_at', 'label' => 'Created'], // ✅ This exists
],
'sortBy' => 'created_at', // ✅ Must match column key
'sortDirection' => 'desc',
])
Symptom: Error: Undefined variable $sortColumn
Cause: Using old Blade template syntax
Fix: Update to use sortColumn property
[@if](https://github.com/if) ($sortColumn == 'name')
{{-- Sorting by name --}}
[@endif](https://github.com/endif)
Or use the new sortBy alias:
public $sortBy = null; // Now available
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Product',
'columns' => [
['key' => 'sku', 'label' => 'SKU'],
['key' => 'name', 'label' => 'Product Name'],
['key' => 'price', 'label' => 'Price'],
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name'],
['key' => 'stock', 'label' => 'Stock'],
['key' => 'reviews_count', 'label' => 'Reviews'],
],
'sortBy' => 'price', // Most expensive first
'sortDirection' => 'desc',
'records' => 50,
])
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\AccountFlow\Transaction',
'columns' => [
['key' => 'date', 'label' => 'Date'],
['key' => 'amount', 'label' => 'Amount'],
['key' => 'type', 'label' => 'Type'],
['key' => 'category_id', 'relation' => 'category:name', 'label' => 'Category'],
['key' => 'account_id', 'relation' => 'account:name', 'label' => 'Account'],
['key' => 'description', 'label' => 'Description'],
],
'sortBy' => 'date', // Newest first
'sortDirection' => 'desc',
'records' => 25,
])
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\User',
'columns' => [
['key' => 'name', 'label' => 'Full Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'role', 'label' => 'Role'],
['key' => 'organization_name', 'label' => 'Organization', 'relation' => 'organization:name'],
['key' => 'last_login_at', 'label' => 'Last Login'],
['key' => 'created_at', 'label' => 'Created'],
],
'sortBy' => 'name', // A to Z
'sortDirection' => 'asc',
'records' => 50,
])
[@livewire](https://github.com/livewire)('aftable', [
'model' => 'App\Models\Order',
'columns' => [
['key' => 'id', 'label' => 'Order ID', 'hidden' => true],
['key' => 'order_number', 'label' => 'Order #'],
['key' => 'customer_name', 'label' => 'Customer', 'relation' => 'customer:name'],
['key' => 'total_amount', 'label' => 'Total Amount'],
['key' => 'items_count', 'label' => 'Items'],
['key' => 'status', 'label' => 'Status'],
['key' => 'created_at', 'label' => 'Order Date'],
],
'sortBy' => 'created_at', // Sort by order date
'sortDirection' => 'desc', // Newest orders first
'records' => 25,
])
[@livewire](https://github.com/livewire)('aftable', [
// ... other parameters ...
'sortBy' => 'column_key', // Initial sort column
'sortDirection' => 'asc|desc', // Initial sort direction
'sort' => 'asc|desc', // Backward compatibility
])
public $sortBy = null; // Current sort column (alias for sortColumn)
public $sortColumn = null; // Current sort column (internal)
public $sortDirection = 'asc'; // Current sort direction (asc/desc)
// Programmatically set sort
public function sortBy($column)
// Get sort state
public function getSortIcon($column): string
public function isColumnSorted($column): bool
public function getSortDirection($column): ?string
// Get sortable columns
public function getSortableColumns(): array
// Reset sorting
public function resetSortToDefault()
'columns' => [
[
'key' => 'name', // Database column (sortable)
'label' => 'Product Name',
'sort' => true, // ✅ Explicitly enable sorting
],
[
'key' => 'category_id',
'relation' => 'category:name', // ✅ Relation column (sortable)
'label' => 'Category',
],
[
'key' => 'items_count',
'label' => 'Items', // ✅ Count column (auto-sortable)
],
[
'key' => 'data',
'json' => 'address.city', // ✅ JSON column (sortable)
'label' => 'City',
],
[
'key' => 'status',
'function' => 'getStatus', // ❌ Function column (NOT sortable)
'label' => 'Status',
],
[
'key' => 'actions',
'raw' => '<a>Edit</a>', // ❌ Raw HTML (NOT sortable)
'label' => 'Actions',
],
]
sortBy for initial sort columnsortDirection for initial orderrelation format (causes N+1 queries)| Scenario | Queries | Speed | Memory |
|---|---|---|---|
| 50 items, sort by DB column | 1 | ~50ms | ~2MB |
| 50 items, sort by relation | 1 | ~75ms | ~3MB |
| 50 items, sort by count | 1 | ~100ms | ~3MB |
| 1000 items, sort by relation | 1 | ~200ms | ~8MB |
| Scenario | Queries | Speed | Memory |
|---|---|---|---|
| 50 items, manual relation | 51+ | ~2s | ~15MB |
| 50 items, manual count | 51+ | ~3s | ~20MB |
| Complex nesting | 100+ | ~5s+ | ~30MB+ |
artflow-studio/tableLast Updated: December 30, 2025
Status: Production Ready ✅
How can I help you explore Laravel packages today?