A production-ready, trait-based Laravel Livewire 4 datatable component with automatic optimization, N+1 prevention, and zero-config setup.
Version: 1.5.6 | PHP: 8.2+ | Laravel: 12+ | Livewire: 4.x | Tailwind/Bootstrap: Compatible
ArtFlow Table is a Livewire 4 component that builds powerful, performant datatables with:
raw column templates — Full Blade & PHP expression support ({{ }}, {!! !!}, closures)#[Computed] caching, #[On] events, wire:model.livewithCount() without loading relationsdateColumn support/aftable/generator for interactive docscomposer require artflow-studio/table
That's it! Package auto-registers with Laravel. livewire/blaze is bundled as a dependency and activates automatically — no configuration needed.
Optional: Set
BLAZE_ENABLED=falsein your.envto disable Blaze's Blade compilation step.
@livewire('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'title', 'label' => 'Item Name'],
['key' => 'code', 'label' => 'Code'],
['key' => 'amount', 'label' => 'Amount'],
],
])
That's all you need! The table automatically:
@livewire('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'title', 'label' => 'Name'],
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name'],
['key' => 'department_name', 'label' => 'Department', 'relation' => 'department:name'],
['key' => 'subitems_count', 'label' => 'Sub-items'],
],
])
System automatically:
category and department relationswithCount()| Metric | Result | Improvement |
|---|---|---|
| Database Queries | 1 query | 98% reduction (51 → 1) |
| Page Load Time | 150-200ms | 75-80% faster (800ms → 200ms) |
| Configuration Code | Minimal | 80% less than competitors |
| Memory Usage | Optimized | Handles 1M+ records |
Real Example: Displaying 50 products with categories, brands, and variant counts
['key' => 'title', 'label' => 'Item Name']
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name']
['key' => 'subitems_count', 'label' => 'Sub-items']
_count suffix ✅withCount() for efficiency ✅[
'key' => 'actions',
'label' => '',
'actions' => [
['type' => 'button', 'label' => 'Edit', 'href' => '/items/{id}/edit'],
['type' => 'button', 'label' => 'Delete', 'href' => '/items/{id}', 'method' => 'DELETE'],
]
]
@livewire('aftable', [
'model' => App\Models\User::class,
'columns' => [...],
'perPage' => 25,
'sortBy' => 'created_at',
'sortDirection' => 'desc',
'searchable' => true,
'exportable' => false,
'printable' => false,
'checkbox' => false,
'index' => false,
'colvisBtn' => true,
'refreshBtn' => false,
'extraVars' => ['currency' => 'PKR'],
'dateColumn' => 'created_at',
])
raw columns support full Blade syntax and PHP expressions. The current row is available as $row.
{{ }} and {!! !!})// Date formatting
['key' => 'date', 'label' => 'Date',
'raw' => '{{ \Carbon\Carbon::parse($row->date)->format("d M Y") }}']
// Status badge with unescaped HTML
['key' => 'active', 'label' => 'Status',
'raw' => '{!! $row->active
? "<span class=\"badge badge-light-success\">Active</span>"
: "<span class=\"badge badge-light-warning\">Inactive</span>" !!}']
// Calculated value
['key' => 'amount', 'label' => 'Total',
'raw' => 'PKR {{ number_format($row->qty * $row->price, 2) }}']
Livewire 4 note:
rawtemplates usecompileString() + eval()internally, bypassing Livewire 4'sExtendedCompilerEnginewhich caused compiled PHP to appear as raw text. Both{{ }}(escaped) and{!! !!}(unescaped) work correctly.
['key' => 'status', 'label' => 'Status',
'raw' => fn($row) => '<span class="badge bg-'.($row->status === 'active' ? 'success' : 'danger').'">'.e($row->status).'</span>']
@livewire('aftable', [
'model' => App\Models\Order::class,
'extraVars' => ['currency' => 'USD', 'locale' => 'en'],
'columns' => [
['key' => 'total', 'raw' => '{{ $currency }} {{ number_format($row->total, 2) }}'],
],
])
| Feature | Old (LW3) | New (LW4) |
|---|---|---|
| Event listeners | protected $listeners = [...] |
#[On('event')] attribute |
| Reactive updates | wire:model |
wire:model.live |
| Computed data | Manual caching | #[Computed] auto-caching |
raw rendering |
Blade::render() |
compileString() + eval() |
| Alpine.js | External import | Bundled with Livewire |
| Event | How to dispatch |
|---|---|
refreshTable |
Livewire.dispatch('refreshTable') |
dateRangeSelected |
Livewire.dispatch('dateRangeSelected', {startDate, endDate}) |
Visit /aftable/generator in your application to:
@livewire() column code with a visual builder📖 AI_USAGE_GUIDE.md - Non-technical guide
📖 AI_TECHNICAL_REFERENCE.md - Technical deep dive
You define columns (simple list)
['key' => 'title', 'label' => 'Title']
['key' => 'category_name', 'relation' => 'category:name']
['key' => 'subitems_count', 'label' => 'Sub-items']
System auto-detects
withCount()Single optimized query executes
SELECT * FROM items
WITH category
WITH COUNT subitems
WHERE title LIKE ?
ORDER BY created_at ASC
LIMIT 50
Results display instantly ⚡
User clicks "Sort by Price"
↓
Livewire triggers update
↓
Component rebuilds query with ORDER BY
↓
Query executes (1 query only!)
↓
Blade template updates
↓
User sees sorted table
@livewire('aftable', [
'model' => 'App\Models\Item',
'columns' => [
['key' => 'title', 'label' => 'Item Name'],
['key' => 'code', 'label' => 'Code'],
['key' => 'category_name', 'label' => 'Category', 'relation' => 'category:name'],
['key' => 'amount', 'label' => 'Amount', 'value_type' => 'price'],
['key' => 'items_count', 'label' => 'Related Items'],
['key' => 'quantity', 'label' => 'Quantity'],
],
])
@livewire('aftable', [
'model' => 'App\Models\User',
'perPage' => 25,
'columns' => [
['key' => 'name', 'label' => 'Name'],
['key' => 'email', 'label' => 'Email'],
['key' => 'department_name', 'label' => 'Department', 'relation' => 'department:name'],
['key' => 'is_active', 'label' => 'Status', 'value_type' => 'boolean'],
['key' => 'created_at', 'label' => 'Joined', 'value_type' => 'date'],
],
])
@livewire('aftable', [
'model' => 'App\Models\Order',
'columns' => [
['key' => 'id', 'label' => 'Order #'],
['key' => 'customer_name', 'label' => 'Customer', 'relation' => 'customer:name'],
['key' => 'total_amount', 'label' => 'Total', 'value_type' => 'price'],
['key' => 'items_count', 'label' => 'Items'],
['key' => 'status', 'label' => 'Status'],
['key' => 'created_at', 'label' => 'Date', 'value_type' => 'date'],
],
])
@livewire('aftable', [
'model' => App\Models\Item::class,
'query' => Item::query()->where('tenant_id', auth()->user()->tenant_id)->active(),
])
@livewire('aftable', [
'model' => App\Models\User::class,
'columns' => [...],
'exportable' => true, // CSV / Excel export button
'printable' => true, // Print button (isolated pop-up window)
])
@livewire('aftable', [
'model' => App\Models\Order::class,
'vars' => ['currency' => 'USD'], // available as $currency in raw templates
'columns' => [
['key' => 'total', 'raw' => '{{ $currency }} {{ number_format($row->total, 2) }}'],
],
])
Enable printable: true to show a Print button that opens an isolated pop-up window containing only the table — no surrounding layout.
@livewire('aftable', [
'model' => App\Models\User::class,
'columns' => [...],
'printable' => true,
'printConfig' => [
'title' => 'User Report',
'header' => '<p>Confidential — Internal Use Only</p>',
'footer' => 'HR Department',
'fontSize' => 11, // pt (default 11)
'orientation' => 'landscape', // portrait | landscape
'paperSize' => 'A4',
'showDate' => true,
'showPageNum' => true,
'tableClass' => 'table-striped',
],
])
Note: Requires the browser to allow pop-ups for your domain.
All three are equivalent — pick the one that fits your workflow:
{{-- 1. Blade helper (most common) --}}
@livewire('aftable', ['model' => App\Models\User::class, 'columns' => [...]])
{{-- 2. Livewire tag syntax --}}
<livewire:aftable :model="App\Models\User::class" :columns="$columns" />
{{-- 3. @aftable directive with optional custom table markup --}}
@aftable(['model' => App\Models\User::class, 'columns' => [...]])
<table class="table table-hover my-custom-theme">
<thead class="bg-dark text-white"><tr></tr></thead>
<tbody></tbody>
</table>
@endaftable
The live code generator at /aftable/generator supports all three via the Output Syntax radio group.
<!-- In resources/views/products/index.blade.php -->
@livewire('aftable', [
'model' => 'App\Models\Product',
'columns' => [...],
])
// DON'T do this:
class MyComponent extends Component {
public function render() {
// Don't use @livewire here
}
}
Rule: Component must be used directly in Blade views, NOT instantiated in PHP!
Solution: Run composer require artflow-studio/table
Solution: Use 'relation' => 'relationName:columnName' format for related data
Solution: Only database fields are sortable. Use actual column names.
Solution: Search only works on text columns. Numbers, dates won't search.
Solution: Verify relation exists on model and column name is correct
| File | Purpose | Audience |
|---|---|---|
| README.md | This file — Overview & API reference | Everyone |
| USAGE_STUB.md | Complete method reference | Developers, AI agents |
| AI_USAGE_GUIDE.md | Non-technical usage guide | Users, AI agents |
| AI_TECHNICAL_REFERENCE.md | Architecture deep-dive | Developers, AI agents |
| CHANGELOG_V2.0.0.md | v2.0.0 Livewire 4 upgrade notes | Developers |
| CHANGELOG_V1.8.0.md | v1.8.0 release notes | Developers |
Interactive docs: visit
/aftable/generatorin your running app for a live docs browser and column code generator.
This package is open-source and available under the MIT license.
ArtFlow Table 2.0.0 - Where Performance Meets Simplicity ⚡
Make datatables simple and fast with automatic optimization!
Last Updated: July 2025
Status: ✅ Production Ready (Livewire 4 / Laravel 12)
How can I help you explore Laravel packages today?