DataTable: all options
This page lists every DataTable method and setting, topic by topic. For a step-by-step guide, see DataTable; for working examples, see DataTable examples.
The table class
A table is a Livewire component that extends Acun\Ui\DataTable\DataTable. Only query() and columns() are required; everything else is optional.
The examples on this page use this orders table:
namespace App\Livewire;
use Acun\Ui\DataTable\Actions\{BulkAction, ExportAction, RowAction, TableAction, UtilityAction};
use Acun\Ui\DataTable\Column;
use Acun\Ui\DataTable\DataTable;
use Acun\Ui\DataTable\Filter;
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;
final class OrdersTable extends DataTable
{
public string $title = 'Orders';
protected function query(): Builder
{
return Order::query();
}
protected function columns(): array
{
return [/* … */];
}
}
Methods you can write in the table class:
| Method | What it does | Section |
|---|---|---|
query() |
The query of the records to list (required) | Authorization and security |
columns() |
The columns (required) | Columns |
filters() |
The filters | Filters |
defaultSort() |
The starting order | Sorting |
actions(), bulkActions() |
Row and bulk actions | Actions |
headerActions(), utilityActions() |
Toolbar buttons | Toolbar |
exportActions() |
The "Export" menu | Export |
authorizeTable() |
Who can open the table | Authorization and security |
Keeping it fast
- The query runs in the database on every request; only the rows of the open page go to the browser. Do not load every record with
Model::all()and filter it in PHP. - If needed, select only the columns you need in
query()(select()) and eager load relations (with()). - Add database indexes to the columns you sort and filter by; the package does not change your migrations. Search columns come with their index through
$table->searchColumn(). - Export uses the table's own query; you do not write the search, filters and sorting a second time.
Columns
Columns decide which fields the table shows and how. Every column starts with Column::make('Label', 'field'):
Column::make('Order no.', 'number')->searchable()->sortable()->nowrap()->toggleable(false),
Column::make('Customer', 'customer_name')->searchable('contains')->view('admin.tables.cells.order-customer'),
Column::make('City', 'city')->hidden()->priority(4),
Column::make('Amount', 'amount')->renderUsing(fn (Order $order) => $order->formattedAmount())->align('right')->width('140px'),
Column::make('Order date', 'ordered_at')->dateTime()->sortable()->priority(3),
Column methods
| Method | What it does | Example |
|---|---|---|
searchable() |
The search box searches this column (Search) | ->searchable('contains') |
sortable() |
Clicking the header sorts by it | ->sortable() |
hidden() |
Starts hidden; the column menu shows it | ->hidden() |
toggleable(false) |
Always shown; the column menu cannot hide it | ->toggleable(false) |
priority() |
Hidden on narrow screens (below) | ->priority(3) |
width() |
Column width | ->width('140px') |
align() |
Alignment: left, center or right |
->align('right') |
nowrap() |
The cell stays on one line | ->nowrap() |
wrap() |
Long text wraps onto more lines, also in a scrollX() table |
->wrap()->width('20rem') |
money($currency, $locale) |
Money format; without a locale, the page's language | ->money('TRY') |
number($decimals) |
Number format | ->number(2) |
date($format) |
Date; without a format, the language's short date | ->date('d.m.Y') |
dateTime($format) |
Date and time | ->dateTime() |
boolean($yes, $no) |
Shows a true/false value as text; default "Active" / "Inactive" | ->boolean('Open', 'Closed') |
renderUsing() |
Your function produces the cell text | ->renderUsing(fn (Order $order) => …) |
view() |
Draws the cell with your own Blade file | ->view('admin.tables.cells.order-status') |
exportable(false) |
Left out of exports (Export) | ->exportable(false) |
exportUsing() |
Changes the value written to the file | ->exportUsing(fn (Order $order) => $order->amount / 100) |
- The
renderUsing()function receives the row, the field's value and the column:fn ($row, $value, $column). The text it returns is not read as HTML; it is shown as plain text. Useview()for cells that need HTML. date()anddateTime()also format values the model does not cast to dates (strings or Unix timestamps). A value that cannot be read is shown as it is.
Drawing a cell with your own view
The view() file receives the row's model as $row and the column as $column:
{{-- resources/views/admin/tables/cells/order-status.blade.php --}}
<acun:badge :variant="$row->status->variant()">{{ $row->status->label() }}</acun:badge>
Do not give the cell content its own background color; leave it transparent. In fixed columns the package sets the color (Limitations and tips).
Hiding columns on narrow screens
By default, columns have priority(1) and show on every screen. A higher value hides the column on narrow screens:
| Value | When it is hidden |
|---|---|
priority(1), priority(2) |
Never |
priority(3) |
Below 38rem (608 px) |
priority(4) and up |
Below 52rem (832 px) |
The user sees the values of hidden columns through row details.
Row details (+)
Row details put a (+) button at the start of the row. The button shows all of the row's columns as "label: value" in a dialog (modal). Columns hidden by default, turned off in the column menu or hidden on a narrow screen are listed too.
protected function rowDetails(): ?string
{
return 'responsive'; // 'always': on every screen; null: off (default)
}
// Optional: the columns and title of the dialog
protected function rowDetailsColumns(): array
{
return $this->resolvedColumns(); // default: every column
}
protected function rowDetailsTitle(Model $row): string
{
return $row->number.' · '.$row->customer_name; // default: the first column's value
}
- With
'responsive', the (+) only appears while a column is hidden because of the screen width. - Cell formats (
view(),renderUsing(),money()…) are used in the dialog as well. - The dialog only opens a record from the table's own
query(); another account's record cannot be opened.
Column menu
The column menu lets the user show and hide columns. The toolbar button is labeled "Columns".
- The menu has "Show all" and "Reset to default".
toggleable(false)columns are always shown; the server refuses to hide them even if the browser asks.- The user's choice is written to the address bar when it differs from the default. To remember it for the whole session, set
acun-ui.datatable.columns.persisttosession. - When the menu appears: Toolbar.
Search
The search box searches the searchable() columns and the searchFields() fields. The search runs on the server and uses the infrastructure from the Search guide.
Search column
Every field is searched through a separate search column that the database builds from it. The column is named {field}_search. Add it with a migration:
Schema::table('orders', function (Blueprint $table) {
$table->searchColumn('number'); // number_search + index
$table->searchColumn('customer_name'); // customer_name_search + index
});
Turkish letters and letter case are folded in the search column. So in every search mode "ayse" → "Ayşe", "ali" → "ALİ" and "ALI", "ismail isik" → "İsmail Işık", "ÇAĞRI" → "çağrı" are found.
Search modes
The mode only decides where in the value the term may sit:
| Mode | Match | Index |
|---|---|---|
SearchMode::Prefix (default) |
The value starts with the term | Used |
SearchMode::Exact |
The value equals the term | Used |
SearchMode::Contains |
The term is anywhere in the value | Not used; choose it on purpose for small tables |
SearchMode::FullText |
Every word starts a word of the value | Used on MySQL |
FullText uses the FULLTEXT index on MySQL; add the search column with $table->searchColumn('name', fullText: true). On other databases every word is searched like Contains.
Set the mode on the column. It can also be written as text: 'prefix', 'exact', 'contains', 'fulltext'.
use Acun\Ui\Search\SearchMode;
Column::make('Order no.', 'number')->searchable(), // number_search, prefix
Column::make('Customer', 'customer_name')->searchable(SearchMode::Contains),
Column::make('Customer', 'customer_name')->searchable('contains', column: 'customer_search'),
The last line reads a search column built from several fields. Add such a column in a migration with $table->searchColumn(['customer_name', 'customer_email'], 'customer_search').
Searching a field that is not a column
To search a field that is not shown as a column, write the searchFields() method. You do not need a hidden column:
use Acun\Ui\Search\SearchField;
protected function searchFields(): array
{
return [...parent::searchFields(), SearchField::make('customer_email')];
}
How search works
- The box searches 400 ms after the user stops typing (
acun-ui.datatable.search_debounce). - All searched fields are added to the query as one grouped "this field or that field" condition. So the conditions in your
query()stay intact. %and_in the term are not wildcards; they are searched as literal text: "50%" only finds records that contain "50%". The package addsESCAPE '!'to every LIKE condition.- On SQLite, prefix search uses
GLOB, where*,?and[are literal text as well. The behavior is the same on SQLite, MySQL, PostgreSQL and SQL Server. - At most 200 characters and 10 words of the term are used (
acun-ui.search.max_length,max_words). - For another search driver (e.g. your own driver for Meilisearch), write a
searchDriver()method in the table or changeacun-ui.search.driver. The column definitions stay the same. See Search: drivers. - Hiding the search box and changing its placeholder: Toolbar.
Filters
Filters narrow the list by a field. They are listed in the filters() method:
protected function filters(): array
{
return [
Filter::select('Channel', 'channel')->options(OrderChannel::options())->inToolbar(),
Filter::multiSelect('Status', 'status')->options(OrderStatus::options())->inToolbar(),
Filter::dateRange('Order date', 'ordered_at'),
Filter::select('City', 'city')->options(array_combine(Order::CITIES, Order::CITIES)),
];
}
Filter types
| Method | What it does | Example |
|---|---|---|
Filter::text() |
Text box; finds records that contain the value | Filter::text('Product', 'product') |
Filter::select() |
Dropdown; records equal to the chosen value | Filter::select('Channel', 'channel')->options([…]) |
Filter::multiSelect() |
List with checkboxes; records equal to one of the chosen values | Filter::multiSelect('Status', 'status')->options([…]) |
Filter::boolean() |
Yes / No choice | Filter::boolean('Active', 'is_active') |
Filter::date() |
A single day | Filter::date('Day', 'ordered_at') |
Filter::dateRange() |
Start and end day | Filter::dateRange('Date', 'ordered_at') |
Filter::number() |
Records equal to a number | Filter::number('Quantity', 'quantity') |
Filter::numberRange() |
Minimum and maximum value | Filter::numberRange('Amount', 'amount') |
dateanddateRangecompare by day. On datetime columns the whole chosen day is included; in a range the whole end day is included too.- Without
options(), abooleanfilter offers "Yes" (1) and "No" (0). - A value such as
'0'(e.g. "No") counts as an active filter. An empty range does not.
Filters in the toolbar
By default, filters are in the drawer that the "Filters" button opens. Move a few frequently used ones to the row under the search box with inToolbar(); they are always visible there.
- Select filters show the filter name in every option: "Channel: Web".
- A multi-select is a dropdown with checkboxes: "Status: 2 selected". It stays open while you tick boxes.
- Range filters are drawn as two fields.
- The drawer lists only the filters that are not in the toolbar. If none are left, the "Filters" button is not shown.
- The count badge on the button and "Clear" in the drawer cover only the drawer filters.
inDrawer()keeps a filter in the drawer explicitly (the default).
Your own condition
If the built-in comparison is not enough, write the condition yourself with applyUsing(). The function receives the query and the filter's value:
// The amount is entered in lira and stored in kuruş.
Filter::numberRange('Amount (₺)', 'amount')->applyUsing(function (Builder $query, mixed $value): void {
if (is_numeric($value['from'] ?? null)) {
$query->where('amount', '>=', (int) round((float) $value['from'] * 100));
}
if (is_numeric($value['to'] ?? null)) {
$query->where('amount', '<=', (int) round((float) $value['to'] * 100));
}
}),
Counting and clearing active filters
| Method | What it does | Example |
|---|---|---|
activeFilterCount() |
Number of active filters | $this->activeFilterCount() |
activeFilterCount(toolbar: …) |
true: only toolbar filters; false: only drawer filters |
$this->activeFilterCount(toolbar: false) |
clearFilters() |
Clears filters; takes the same toolbar: parameter |
$this->clearFilters(toolbar: true) |
The filter badge and the empty-state message use activeFilterCount().
Sorting
Sorting decides which column orders the rows. Only columns marked sortable() can be sorted.
- The first click on a header sorts ascending, the second descending. Clicking another column starts ascending.
- A column name from the browser is used only if it is one of the
sortable()columns (Authorization and security).
Default order
defaultSort() gives the order used until the user picks a column:
protected function defaultSort(): ?array
{
return ['field' => 'ordered_at', 'direction' => 'desc'];
}
- The field does not have to be a sortable column.
- "Reset sorting" returns to this order.
- The default order is not written to the address bar;
?direction=only appears when it differs from the default.
Equal values
After the sorted column, the model's key (usually id) is added in the same direction. So rows with equal values keep one order across pages, cursor pagination and exports read in chunks. Turn it off for a grouped (groupBy) query:
protected function sortTieBreaker(Builder $query): ?string
{
return null;
}
Actions
Actions add buttons to the table: to the toolbar, to the end of every row and to the selection bar. Each group is a method in the table class. All of them are optional and return an empty list by default.
| Method | Class | Where |
|---|---|---|
headerActions() |
Actions\TableAction |
Right end of the toolbar ("New order") |
utilityActions() |
Actions\UtilityAction |
Refresh, the column menu and the "⋮" menu |
exportActions() |
Actions\ExportAction |
The "Export" menu |
actions() |
Actions\RowAction |
End of every row |
bulkActions() |
Actions\BulkAction |
The selection bar |
The browser side (full screen, shortcuts, copying, the print window, confirmation dialogs) lives in the acunDataTable component in the PRO JavaScript file. Your app's JS entry must contain import '@acunsoft/acun-ui-pro'; (JavaScript layers). If the PRO package was updated, run npm run build.
Full example
final class OrdersTable extends DataTable
{
protected function headerActions(): array
{
return [
TableAction::make('create')->label('New order')->icon('plus')
->url(fn () => route('orders.create'))
->can('create', Order::class)
->shortcut('mod+shift+n'),
TableAction::make('sync')->label('Sync')->icon('arrow-path')->color('gray')
->action('syncOrders') // a method of the table
->confirm('Sync the orders?', 'Sync')
->loadingLabel('Syncing…')
->successMessage('Orders updated.'),
];
}
protected function exportActions(): array
{
return [
ExportAction::csv()->shortcut('mod+e'),
ExportAction::pdf(),
ExportAction::json()->can('export-json'),
ExportAction::print(),
ExportAction::copy(),
];
}
protected function utilityActions(): array
{
return [
UtilityAction::refresh(),
UtilityAction::columns(),
UtilityAction::fullscreen(),
UtilityAction::resetFilters(),
UtilityAction::resetSorting(),
];
}
protected function bulkActions(): array
{
return [
BulkAction::make('ship')->label('Ship')->icon('truck')
->action(fn (Builder $query) => $query->where('status', 'paid')->update(['status' => 'shipped']))
->successMessage(':count orders shipped.'),
BulkAction::exportSelected('csv'),
BulkAction::printSelected(),
BulkAction::delete()->can('delete-any', Order::class)
->authorizeRow(fn (Order $order) => auth()->user()->can('delete', $order)),
];
}
protected function actions(): array
{
return [
RowAction::view(fn (Order $order) => route('orders.show', $order)),
RowAction::edit(fn (Order $order) => route('orders.edit', $order))->can('update'),
RowAction::make('ship')->label('Ship')->icon('truck')
->visible(fn (Order $order) => $order->status === OrderStatus::Paid)
->action(fn (Order $order) => $order->update(['status' => OrderStatus::Shipped])),
RowAction::delete()->can('delete'),
];
}
protected function syncOrders(): void
{
// …
}
}
Shared methods
Every action class is built on TableAction and shares these methods. The name given with make('name') must be unique within a group. It may contain letters, digits, -, _, . and :.
| Method | What it does | Example |
|---|---|---|
label() |
Button text; without it, made from the name | ->label('New order') |
icon() |
Icon before the text (a heroicons name) | ->icon('plus') |
tooltip() |
Tip shown on hover | ->tooltip('Sync with the store') |
iconOnly() |
Icon only; the text becomes the tooltip and screen reader name | ->iconOnly() |
color() |
Semantic color (Button appearance) | ->color('gray') |
variant() |
An acun:button variant directly; wins over color() |
->variant('outline') |
url() |
A link; newTab: true opens a new tab |
->url(fn () => route('orders.create')) |
navigate() |
Opens the link without a full page reload (wire:navigate) |
->navigate() |
action() |
The function to run, or the name of a table method | ->action('syncOrders') |
confirm($message, $title, $button) |
Opens a confirmation dialog before running | ->confirm('Are you sure?') |
withoutConfirmation() |
Turns confirmation off | ->withoutConfirmation() |
visible(), hidden() |
Shows or hides by a condition | ->visible(fn (Order $order) => …) |
can($ability, $arguments) |
Laravel authorization check (Gate::allows) |
->can('update') |
authorize() |
Any other check; when it returns false there is no action |
->authorize(fn () => …) |
disabled() |
Drawn greyed out and does not run | ->disabled(fn (Order $order) => …) |
shortcut() |
Keyboard shortcut (below) | ->shortcut('mod+e') |
loadingLabel() |
Text on the button while it runs | ->loadingLabel('Preparing…') |
successMessage() |
Success notification when it finishes | ->successMessage('Updated.') |
attributes() |
Extra HTML attributes (for tests or analytics) | ->attributes(['data-test' => 'new']) |
order() |
Order; a smaller number is drawn first | ->order(10) |
url()renders a link;action()runs on the server. Do not set both.- Without a title and button text,
confirm()uses the action's label. On a row action the message can be a function:->confirm(fn (Order $order) => "Cancel {$order->number}?"). - A hidden or unauthorized action is not rendered; if the browser calls it, the server returns 403. A
disabled()action is drawn greyed out and also returns 403 if called. - On a row action,
can()without arguments checks against the row itself:->can('update')→Gate::allows('update', $order). :countinsuccessMessage()becomes the number of records in a bulk action.
Values an action receives
Action functions ask for the values they need by name or by type:
| Parameter | What it gives | Where |
|---|---|---|
$table |
The table component | All actions |
$row, $record or the model type (Order $order) |
The row's model | Row actions |
$query or $builder |
The query narrowed to the selection | Bulk actions |
$ids or $keys |
The keys of the selected records | Bulk actions |
$records |
The selected models, read in chunks | Bulk actions |
$count |
The number of selected records | Bulk actions |
Untyped parameters are filled in order: the row and the table for row actions, the query and the table for bulk actions. Any other class type comes from Laravel's service container. In a bulk action only the values you ask for are computed.
Button appearance
Every action is drawn as an acun:button. Set its color by meaning with color(); the package picks the right variant for the button's place:
color() |
Header and toolbar | Row and bulk action |
|---|---|---|
| not set | primary (header), white (toolbar) |
soft |
'primary' |
primary |
soft |
'secondary' |
secondary |
soft-secondary |
'gray' |
white (white, framed) |
soft-secondary |
'danger' |
danger |
soft-danger |
'success' |
success |
soft-success |
'warning' |
warning |
soft-warning |
'info' |
info |
soft-info |
'dark' |
dark |
soft |
If you want a specific variant, variant() wins over everything:
TableAction::make('create')->label('New order')->icon('plus'); // primary color, solid
TableAction::make('import')->label('Import')->icon('arrow-up-tray')->color('gray'); // white, framed
TableAction::make('archive')->label('Archive')->variant('outline');
TableAction::make('sync')->label('Sync')->icon('arrow-path')->iconOnly()->tooltip('Sync with the store');
Icon names are the heroicons names of the acun:icon component: plus, arrow-path, arrow-down-tray, printer, truck, eye, pencil-square, trash… The action classes only carry the name.
Header actions
Header actions sit at the right end of the toolbar. On phones the first header action stays visible and the others move into the "Actions" panel, so put the most important button first.
protected function headerActions(): array
{
return [
// Go to a page
TableAction::make('create')->label('New order')->icon('plus')
->url(route('orders.create'))
->shortcut('mod+shift+n'),
// Open in a new tab
TableAction::make('report')->label('Monthly report')->icon('document-text')->color('gray')
->url(fn () => route('reports.monthly'), newTab: true),
// Open a dialog on the page (an acun:modal or a form modal)
TableAction::make('quick-add')->label('Quick add')->icon('bolt')->color('gray')
->action(fn (self $table) => $table->dispatch('acun:modal:open', name: 'order-form')),
// Run a table method, after asking for confirmation
TableAction::make('recalculate')->label('Recalculate')->icon('arrow-path')->color('gray')
->action('recalculateTotals')
->confirm('Recalculate the order totals?', 'Recalculate totals', 'Recalculate')
->loadingLabel('Recalculating…')
->successMessage('Totals updated.')
->can('recalculate', Order::class),
];
}
protected function recalculateTotals(): void
{
// …
}
action('recalculateTotals') runs a method of the table. The method can be public or protected. The browser only sends the action's name; the table decides which method runs.
Row actions
Row actions are the buttons at the end of every row:
protected function actions(): array
{
return [
RowAction::view(fn (Order $order) => route('orders.show', $order)),
RowAction::edit(fn (Order $order) => route('orders.edit', $order))->can('update'),
// An action shown depending on the status
RowAction::make('ship')->label('Ship')->icon('truck')->color('success')
->visible(fn (Order $order) => $order->status === OrderStatus::Paid)
->action(fn (Order $order) => $order->update(['status' => OrderStatus::Shipped]))
->successMessage('The order was shipped.'),
// An action that opens a panel on the page
RowAction::make('notes')->label('Notes')->icon('chat-bubble-left-ellipsis')
->action(fn (Order $order, self $table) => $table->dispatch('show-order-notes', id: $order->id)),
RowAction::delete()->can('delete'),
];
}
RowAction::view($url)andRowAction::edit($url)are links.RowAction::delete()asks for confirmation, deletes the record (model events run) and shows a notification. To change its texts:->confirm('Delete this order permanently?')->successMessage('Order deleted.').- A row action with an icon is an icon button; its label is the screen reader name and the tooltip. Without an icon it is a small button with its label.
- When more than three actions show in a row, the first two are buttons and the rest go into a "⋮" menu. The menu is not clipped by the table's scroll box.
visible(),can()anddisabled()are checked separately for every row.
Selection
The selection decides which records the bulk actions run on.
- The header checkbox selects every record that matches the search and filters, on every page. While some are selected, the box shows a dash; clicking again clears the selection.
- To select only the visible page, set
acun-ui.datatable.select_alltopage. - Moving between pages and changing the page size keep the selection. Changing the search or a filter clears it.
- While all records are selected, the visible rows appear ticked. Unticking a row takes it out of the selection; the bulk action then runs on "the matches minus the unticked rows". Unticking the header box clears the selection completely.
- While all records are selected, thousands of keys are not written to the browser; the action runs on the same server-side query.
- At most 5,000 records can be ticked one by one (
acun-ui.datatable.selection.max_keys). Selecting all records has no limit. - While there is a selection, the selection bar (
acun:selection-bar) appears above the table.
Bulk actions
Bulk actions are the buttons in the selection bar:
protected function bulkActions(): array
{
return [
BulkAction::make('approve')->label('Approve')->icon('check')->color('success')
->action(fn (Builder $query) => $query->update(['status' => 'approved']))
->confirm('Approve the :count selected orders?')
->successMessage(':count orders approved.'),
BulkAction::exportSelected('csv'),
BulkAction::printSelected(),
BulkAction::delete()->can('delete-any', Order::class),
];
}
:countbecomes the number of selected records in the confirmation and success texts.- Bulk actions ask for confirmation by default. The dialog is the
acun:confirmation-dialogcomponent; the browser's own confirm box is not used.withoutConfirmation()turns it off. - The selection is cleared when the action finishes;
keepSelection()keeps it. - Row authorization with
authorizeRow(): Authorization and security.
Ready-made bulk actions:
| Method | What it does | Example |
|---|---|---|
BulkAction::delete() |
Deletes the records one by one; model events and soft deletes apply | BulkAction::delete() |
BulkAction::exportSelected() |
Downloads the selection as csv, json or your own format; pdf opens the print page. Keeps the selection |
BulkAction::exportSelected('csv') |
BulkAction::printSelected() |
Opens the selection on the print page; keeps the selection | BulkAction::printSelected() |
Skipping records that do not qualify
Sometimes only part of the selection qualifies (e.g. only paid orders can be shipped). Then narrow the query yourself and report the result:
BulkAction::make('ship')->label('Ship')->icon('truck')
->withoutConfirmation()
->action(function (Builder $query, int $count, self $table) {
$shipped = (clone $query)->where('status', 'paid')->update(['status' => 'shipped']);
$table->dispatch('toast', type: 'success', message: "{$shipped} orders shipped, ".($count - $shipped).' skipped.');
}),
authorizeRow(), on the other hand, is an authorization rule: if even one record in the selection fails it, no record is touched (403). Use the pattern above for business rules and authorizeRow() for authorization.
Keyboard shortcuts
shortcut('mod+e') gives an action a keyboard shortcut. mod is Ctrl on Windows and Linux and ⌘ on macOS.
- The shortcut is shown in the menu and the tooltip. It does not fire while typing in a field.
- Modifier keys:
mod,ctrl,meta,alt,shift. The last key can be a letter, digit or symbol,f1–f12,enter,delete,backspace,insert,homeorend.
The older Action and BulkAction classes
Acun\Ui\DataTable\Action and Acun\Ui\DataTable\BulkAction are the classes from before the action system, and they still work:
use Acun\Ui\DataTable\Action;
use Acun\Ui\DataTable\BulkAction;
Action::make('Details', 'detail')->icon('eye')->action(fn (Order $order) => $this->dispatch('show-order', id: $order->id));
BulkAction::make('Ship', 'ship')->icon('truck')->action(fn (Builder $query) => $query->update(['status' => 'shipped']));
make($label, $name): the second parameter is a fixed name. Without it, the name is made from the label and changes when the label is translated.icon(),style('danger'),visible(),authorize()andaction()are available. The olderBulkActionalso takesauthorizeRow().- An unauthorized older action is not hidden; it is drawn greyed out. Older bulk actions always ask for confirmation.
- In new tables, use
Actions\RowActionandActions\BulkAction.
Export
Export lets users download, print or copy the records to the clipboard. List the formats in the exportActions() method:
protected function exportActions(): array
{
return [
ExportAction::csv()->label('CSV (.csv)')->shortcut('mod+e'),
ExportAction::pdf(),
ExportAction::print()->label('Print'),
ExportAction::copy()->can('orders.export'),
];
}
The menu shows only the formats you list, in the order you list them. If the list is empty, there is no "Export" button.
Formats
| Method | What it does | Example |
|---|---|---|
ExportAction::csv() |
CSV file | ExportAction::csv() |
ExportAction::json() |
JSON file | ExportAction::json() |
ExportAction::pdf() |
Opens the print page; the user picks "Save as PDF" in the browser | ExportAction::pdf() |
ExportAction::print() |
A clean print page in a new window | ExportAction::print() |
ExportAction::copy() |
Copies the rows to the clipboard; pastes into Excel as cells | ExportAction::copy() |
Scope
The "Export" menu first asks which rows to take:
| Scope | Rows | Value |
|---|---|---|
| Current page | The page on screen | current_page |
| Filtered records | Every record matching the search and filters | filtered |
| Selected records (n) | The selection; when all records are selected, minus the unticked ones | selected |
| All records | The whole query(), without search and filters; asks for confirmation first |
all |
The default scope: the selected records if there is a selection, the filtered records if a search or filter is active, otherwise the current page. To change it in the table:
use Acun\Ui\DataTable\Export\ExportScope;
protected function defaultExportScope(): ExportScope
{
return ExportScope::Filtered;
}
- A scope that does not apply right now is greyed out (e.g. "Selected records" without a selection).
copy()works only with the current page and the selection; it copies at most 1,000 rows (exports.copy_max_rows).- Every scope keeps the
query()conditions and the current sort order.
Format options
| Method | What it does | Example |
|---|---|---|
scopes() |
The scopes the format accepts | ->scopes(['filtered', 'selected']) |
defaultScope() |
This format's default scope | ->defaultScope('filtered') |
filename() |
File name without the extension | ->filename('orders') |
maxRows() |
Maximum number of rows | ->maxRows(10000) |
queue(false) |
Builds the file right away, never in the queue | ->queue(false) |
driver() |
Your own writer class | ->driver(XmlExportDriver::class) |
Without a file name, the table title and the date are used, e.g. orders-2026-10-05-143000.csv.
Exported columns and values
The visible columns are written to the file. Values come as plain text from the same formats as the cells (date, number, money, boolean, renderUsing). view() cells write the field's raw value; give exportUsing() for a different value:
// On screen "₺1,249.90", in the file 1249.9 (a number)
Column::make('Amount', 'amount')
->renderUsing(fn (Order $order) => $order->formattedAmount())
->exportUsing(fn (Order $order) => $order->amount / 100),
Column::make('Status', 'status')->view('admin.tables.cells.order-status')
->exportUsing(fn (Order $order) => $order->status->label()),
Column::make('Internal note', 'internal_note')->exportable(false), // not in the file
If the export uses relations, write the exportQuery() method to eager load them:
protected function exportQuery(ExportScope $scope): Builder
{
return parent::exportQuery($scope)->with('customer');
}
File format details
- CSV is written with a UTF-8 BOM and
;as the default separator, so Turkish Excel opens it correctly. Text starting with=,+,-or@gets a leading', so no formula runs when the file is opened. - PDF doesn't build a separate file: it opens the print page and suggests the browser's "Save as PDF" option. No extra package is needed. The row limit and the view are the print page's (
print.max_rows,print.layout). - The print page shows at most 2,000 rows (
print.max_rows). You can change its view withprint.layout.
Your own format
Write a class that implements the Acun\Ui\DataTable\Export\Contracts\ExportDriver interface. The interface has three methods: extension(), mimeType() and write($rows, $columns, $stream, $options). Rows arrive one at a time; do not collect them, write them to the stream as they come.
ExportAction::make('xml')->label('XML')->driver(XmlExportDriver::class),
To use the format in every table, or to replace one of the built-in writers, add the class to the acun-ui.datatable.exports.drivers setting.
Queued export
Below 5,000 rows (exports.queue_threshold) the file downloads right away. Above that, a queued job builds the file:
- A "The export was queued" notification appears.
- While a job is pending, the table checks the status every few seconds (
exports.poll_seconds). - When the job is done, a notification with a download link appears.
- CSV, JSON and your own formats can go to the queue. PDF, print and copy are always built right away; the "Current page" scope is never queued either.
- The job signs in the requesting user on the same
guard. It rebuilds the table with the search, filters, sort order and selection of that moment;authorizeTable()and the action's authorization are checked again. - Rows are read in chunks of 1,000 (
exports.chunk_size), keeping the order. The file is written to theexports.diskdisk, in theexports.directoryfolder. - The download link is signed, expires after 60 minutes (
exports.expire_minutes) and gives the file only to the user who started the job. - Old files are deleted after every queued job and by the
acun-ui:prune-exportscommand (Configuration).
Requirements:
- A queue worker:
php artisan queue:work. If you set a custom queue name (exports.queue), add--queue=exports. - A cache store shared by the web server and the worker:
database,redis,file… (notarray). The job status is kept there (exports.cache_store). - The values the table's
query()needs must be in public properties; the worker does not runmount(). Rebuild non-public values in therestoringFromSnapshot()method.
Toolbar
The toolbar is the box above the table: page size, search, filters, tools and header actions live there. On desktop, from left to right:
[ 25 ] [ search ] … [ ⏷ ] [ ↻ ] [ custom tools ] [ ⊞ ] [ ⤓ ] [ ⋮ ] [ + New order ]
[ toolbar filters (Status: All ▾) (Channel: All ▾) … ]
Filters (⏷), Refresh (↻), the column menu (⊞) and Export (⤓) are icon-only. Their names appear as tooltips on hover. The number of active filters sits as a badge on the corner of the Filters icon.
Showing and hiding parts
You turn every part on or off from the table class; you do not need to publish views.
| Part | When it shows | How to change it |
|---|---|---|
| Title and record count | When $title is set |
public string $title = 'Orders'; |
| Header actions | When headerActions() is not empty |
Header actions |
| Search box | When there is something to search | showsSearch(), searchPlaceholder() |
| Filters button | When the drawer has filters | inToolbar() filters sit in the row below |
| Refresh | When UtilityAction::refresh() is listed |
utilityActions() |
| Column menu | When utilityActions() is empty or contains UtilityAction::columns() |
showsColumnPicker() |
| Export | When exportActions() is not empty |
exportActions() |
| Page size | Always | showsPerPageSelector() |
| "⋮" menu | When full screen, a reset or a custom tool is listed | utilityActions() |
Example: no column menu and no Export.
// Export: do not write exportActions(), or return [].
// Column menu: turn it off.
protected function showsColumnPicker(): ?bool
{
return false;
}
When showsColumnPicker() returns null (the default), the rule in the table applies. false always hides it, true always shows it.
Example: a plain table with only search.
protected function showsPerPageSelector(): bool
{
return false; // no page size box; defaultPerPage() sets the size
}
protected function searchPlaceholder(): string
{
return 'Search order no. or customer';
}
To remove the search box completely, have showsSearch() return false. If no column is searchable() and searchFields() is empty, the box does not show anyway.
Table tools
utilityActions() decides which tools appear; a tool that is not listed is not drawn:
protected function utilityActions(): array
{
return [
UtilityAction::refresh(),
UtilityAction::columns(),
UtilityAction::fullscreen(),
UtilityAction::resetFilters(),
UtilityAction::resetSorting(),
// Your own tool: in the "⋮" menu
UtilityAction::make('clear-cache')->label('Clear cache')->icon('trash')
->action(fn () => Cache::tags('orders')->flush())
->successMessage('Cache cleared.'),
// As an icon button in the toolbar
UtilityAction::make('help')->label('Help')->icon('question-mark-circle')->iconOnly()
->url(route('help.orders'), newTab: true)
->inline(),
];
}
| Tool | What it does | Where |
|---|---|---|
refresh() |
Reloads the rows; if the page no longer exists, goes to the last page | Toolbar |
columns() |
The column menu | Toolbar |
fullscreen() |
Opens the table full screen; Esc or "Exit full screen" closes it | "⋮" menu |
resetFilters() |
Clears the search and every filter | "⋮" menu |
resetSorting() |
Returns to the defaultSort() order |
"⋮" menu |
make('name') |
Your own tool; with inline() it sits in the toolbar |
"⋮" menu |
- Full screen spreads the table over the whole screen with the page background and locks page scrolling.
- If you list only
[UtilityAction::columns()], there is no "⋮" menu. order()changes the order of the tools.
Toolbar on phones
On phones (below 640 px) one row stays under the search box: the page size, the first header action and an "Actions" button. Every other tool moves into the panel this button opens, stacked with its text. There is no horizontal overflow at 375 px. This layout is used when the table has at least one header, export or utility action.
While loading, the clicked button is disabled and shows a spinner. If you set loadingLabel(), that text shows too.
Pagination
The pagination bar sits under the table: "1 - 25 / 5000 results" on the left; previous, page numbers and next on the right.
- It stays in place when there are no results ("No results").
- On phones only previous, current and next show.
- Changing the page scrolls to the top of the list.
| Method | What it does | Example |
|---|---|---|
perPageOptions() |
Page size options (public) |
return [20, 50, 100]; |
defaultPerPage() |
Starting size; must be one of the options | return 50; |
showsPerPageSelector() |
Shows or hides the page size box | return false; |
paginationOnEachSide() |
How many numbers show on each side of the current page (default 1) | return 2; |
paginationMode() |
Pagination type | return PaginationMode::Cursor; |
paginationView() |
Your own pagination view (public) |
return 'tables.pagination-compact'; |
use Acun\Ui\DataTable\PaginationMode;
public function perPageOptions(): array
{
return [20, 50, 100];
}
protected function defaultPerPage(): int
{
return 50;
}
protected function paginationMode(): PaginationMode
{
return PaginationMode::Simple;
}
Configuration for every table: acun-ui.datatable.per_page (options, default [10, 25, 50, 100]), default_per_page (25) and pagination. ?perPage= in the address bar can only be one of the options; any other value falls back to the default.
Pagination type
| Type | What it does | Configuration value |
|---|---|---|
PaginationMode::LengthAware (default) |
Knows the total page count; page numbers show | paginate |
PaginationMode::Simple |
Only previous and next | simple |
PaginationMode::Cursor |
The fastest; no numbers or total, only previous and next | cursor |
Counting the total is slow on very large tables; choose Simple or Cursor then.
Texts
"Showing", "results", "No results", "Previous" and "Next" are translation keys. Change them by writing the same key in your app's lang/en.json file (Languages and translation):
{
"Showing": "Listing",
"results": "records",
"No results": "No records found"
}
Pagination view
Page numbers and the previous/next buttons are drawn with acun:button (white), the current page in the primary color. There are two ways to change the bar's HTML:
-
In the whole app: publish the views with
php artisan vendor:publish --tag=acun-ui-viewsand editresources/views/vendor/acun-ui/components/ui/pagination.blade.php. DataTable andWithListinglists use this view. -
In one table only: give your own view with
paginationView(). Like Laravel's pagination views, it receives$paginatorand$elements:public function paginationView(): string { return 'tables.pagination-compact'; }{{-- resources/views/tables/pagination-compact.blade.php --}} <nav class="flex items-center justify-end gap-2" aria-label="Pagination"> <acun:button variant="ghost" size="sm" wire:click="previousPage" :disabled="$paginator->onFirstPage()">Previous</acun:button> <span class="text-sm text-gray-600 dark:text-zinc-400">{{ $paginator->currentPage() }} / {{ $paginator->lastPage() }}</span> <acun:button variant="ghost" size="sm" wire:click="nextPage" :disabled="! $paginator->hasMorePages()">Next</acun:button> </nav>
In lists written by hand with WithListing, perPageOptions() and paginationView() do the same job (List pages).
Appearance and colors
The table is drawn with the theme's own components: acun:table, acun:input, acun:filter-button, acun:drawer, acun:selection-bar, acun:pagination. So it follows the theme's primary color and dark mode on its own; you do not need to add colors to the table.
- The primary color is
brand; light mode usesgrayshades and dark modezincshades. Details: Customization. - The PRO package's
acun-ui-pro.cssonly adds the rules for hiding columns on narrow screens, scrolling, the fixed header and fixed columns.
Title
$title shows a title and the record count above the table; when it is empty, neither shows:
public string $title = 'Orders';
The title is also used on the print page and in the file name. It cannot be changed from the browser. For a translated title, set it in mount():
public function mount(): void
{
$this->title = __('Orders');
parent::mount();
}
Empty state
emptyState() gives the title and description shown when there are no results:
protected function emptyState(): array
{
return $this->search !== ''
? ['title' => 'No orders match your search.', 'description' => 'Try another order no. or customer.']
: ['title' => 'No orders yet.', 'description' => 'They will appear here when the first order arrives.'];
}
While a search or filter is active, a "Clear filters" button is added automatically.
Row density
Row density sets the cell spacing using the theme's spacing scale. It is a setting of the table, not a tool the user changes:
protected function density(): string
{
return 'compact'; // compact, normal or comfortable
}
Without it, acun-ui.datatable.density.default (normal) is used. An invalid value counts as normal.
Overriding views
To change the HTML completely, publish the PRO views. Try the PHP options on this page first; publishing views is the last resort.
php artisan vendor:publish --tag=acun-ui-pro-views
The files are copied to resources/views/vendor/acun-ui-pro/components/data-table/. Keep only the ones you want to change and delete the rest; deleted files keep coming from the package. Files you published are not updated when the package is updated.
| File | Contents |
|---|---|
toolbar.blade.php, search.blade.php |
Title, search, buttons, page size |
actions/button.blade.php |
Header and toolbar button |
actions/export-menu.blade.php, actions/export-panel.blade.php |
The "Export" menu |
actions/utility-menu.blade.php |
The "⋮" menu |
actions/row-actions.blade.php, actions/row-button.blade.php, actions/row-menu.blade.php |
Row actions |
bulk-actions.blade.php |
Selection bar and bulk actions |
column-picker.blade.php |
Column menu |
header.blade.php, row.blade.php |
Table header and rows |
row-details.blade.php |
Row details dialog |
filters.blade.php, filter-control.blade.php, toolbar-filters.blade.php |
Filter drawer and toolbar filters |
empty-state.blade.php, pagination.blade.php |
Empty state and pagination |
print.blade.php |
Print page |
Fixed header and columns
Four optional methods decide how the table behaves while scrolling. All are off by default; a table that uses none of them renders the same HTML. Working examples of each: DataTable examples.
| Method | What it does | Example |
|---|---|---|
scrollX(): bool |
Cells stay on one line; a wide table scrolls sideways in its own box | return true; |
scrollY(): ?string |
Gives the box a height; the table scrolls down inside it, the header stays on top | return '400px'; |
fixedHeader(): bool |
While the page scrolls, the header row stays under the top bar | return true; |
fixedColumns(): array |
The leading columns and the actions column stay in place while scrolling sideways | return ['start' => 1, 'end' => true]; |
scrollX() and scrollY() work with CSS only. fixedHeader() and fixedColumns() use the acunDataTableFixed component in the PRO JavaScript file (import '@acunsoft/acun-ui-pro' or @acunUiScripts). Without the JS, the table still works; only the header and columns do not stay fixed, and the browser console reports the missing JS (If the PRO JavaScript isn't loaded).
Scrolling
protected function scrollX(): bool
{
return true;
}
protected function scrollY(): ?string
{
return '400px'; // any CSS length: '28rem', 'min(70vh, 42rem)'…
}
scrollX()keeps every cell on one line. To let a long text column wrap, usewrap()on that column and give it a width:Column::make('Note', 'note')->wrap()->width('20rem').scrollY()keeps the box at that height; the box scrolls vertically and the header row sticks to its top. Both together give a box that scrolls in both directions.- The older settings keep working:
public bool $stickyHeader = true;in a table andacun-ui.datatable.sticky_headerfor every table are the same asscrollY('min(70vh, 42rem)'). IfscrollY()returns a value, that value is used.
Fixed header
protected function fixedHeader(): bool
{
return true;
}
- When the header row reaches the top bar, a copy of the header appears there. At the end of the table the copy moves up with the table and disappears; nothing on the page shifts.
- The sort arrows and the "select all" box in the copy work; the click is passed to the button in the real header. Keyboard and screen reader users use the real header; the copy is hidden from screen readers and is not in the tab order.
- The copy is rebuilt after every Livewire update. Column widths and horizontal scrolling match the real table.
- The panel's top bar (
.panel-topbar, in the fixed menu layout) is found automatically. If your own layout has a fixed bar, give it thedata-ac-sticky-topattribute. For a gap between the header and the bar:.ac-data-table { --ac-data-table-fixed-header-gap: 8px; }. - In full screen the header stays at the top edge of the screen. It works on phones too.
- Used together with
scrollY(),scrollY()wins: the header is already fixed inside the box, so the page copy is not drawn.
Fixed columns
protected function fixedColumns(): array
{
return ['start' => 1, 'end' => true];
}
start: the number of data columns on the left (at most 10). The selection checkbox and the row details (+) column come first, so they stay fixed too. Ifstartis larger than the number of visible columns, all columns are fixed. If the first column is hidden through the column menu, the next column is fixed.end: keeps the actions column on the right. It does nothing when the table has no row actions (actions()).- The width of fixed columns is measured in the browser. It is recalculated when the screen size, the density (
density()), the column menu orpriority()columns hidden on narrow screens change. - While content passes underneath, a light shadow shows on the edge of the fixed columns.
- On phones (below 640 px) only the left columns stay fixed; the actions column scrolls with the table so it does not take up space on a narrow screen. Keep
startsmall on phones (usually 1). - Using it together with
scrollX()is recommended.
Combining them
| Combination | Result |
|---|---|
scrollX() + scrollY() |
A box that scrolls both ways, header on top |
scrollX() + scrollY() + fixedColumns() |
The same box; fixed columns on the left and right, corner cells on top |
fixedHeader() + fixedColumns() |
Fixed header on the page; the copy has the fixed columns in the same place |
fixedHeader() + scrollY() |
scrollY() wins; the header is fixed inside the box |
| Row details, selection bar, density, full screen | Work with all of them |
Limitations and tips
-
Solid background: Fixed cells get a solid background to cover the content passing underneath. A normal row uses the box color, a hovered row the row color, and the header the header color. If you color a row with your own CSS (e.g. a selected row), also give the same color to the
--ac-data-table-row-bgvariable:.ac-data-table tbody tr:has(input:checked) { background-color: var(--color-brand-50); --ac-data-table-row-bg: var(--color-brand-50); } -
Custom cell views: Do not give the content of a
view()cell its own background color; leave it transparent. A color in the content breaks the row's hover color and dark mode. -
Overflowing boxes: Do not put the table inside a box with
overflow: hiddenoroverflow: auto; the fixed header would stick to that box. Useoverflow: clipfor rounded corners. -
Several tables on one page: Only the header of the table whose body is on screen stays fixed. So that they do not share address bar values (
?q=,?page=), write aqueryString()method in the second table or turn offacun-ui.datatable.url_state. -
Speed: With the feature off, no JS runs. With it on, scrolling is handled at most once per frame; measuring only happens when the size changes and after a Livewire update. Even on very long pages (100+ rows) the cost is only the number of header cells.
-
Printing: The fixed header copy is hidden when printing.
Configuration
The defaults for every table are in the datatable section of config/acun-ui.php. To copy the file into your app:
php artisan vendor:publish --tag=acun-ui-config
'datatable' => [
'per_page' => [10, 25, 50, 100], // page size options
'default_per_page' => 25,
'search_debounce' => 400, // search box delay (ms)
'url_state' => true, // search, sorting, filters… go to the address bar
'sticky_header' => false, // older name: scrollY('min(70vh, 42rem)') on every table
'pagination' => 'paginate', // paginate | simple | cursor
'select_all' => 'all', // header checkbox: all (every match) | page (visible page)
'selection' => ['max_keys' => 5000], // most records that can be ticked one by one
'actions' => ['enabled' => true], // false: header, export and utility actions are off
'exports' => [
'enabled' => true, // false: export, copy and print are off
'queue_threshold' => 5000, // above this many rows the file is built in the queue
'chunk_size' => 1000, // rows are read in chunks of this size
'disk' => 'local',
'directory' => 'exports',
'queue' => null, // queue name (null: the default)
'connection' => null, // queue connection (null: the default)
'expire_minutes' => 60, // lifetime of the download link
'poll_seconds' => 3, // how often the table checks a queued job
'cache_store' => null, // cache that holds the job status; must be shared with the worker
'csv_delimiter' => ';',
'copy_max_rows' => 1000,
'per_minute' => 30, // prints, downloads and copies per user per minute
'drivers' => [
'csv' => CsvExportDriver::class,
'json' => JsonExportDriver::class,
],
],
'print' => ['layout' => 'acun-ui-pro::components.data-table.print', 'max_rows' => 2000, 'token_minutes' => 10],
'density' => ['default' => 'normal'], // compact | normal | comfortable
'columns' => ['persist' => 'none'], // none | session
'routes' => ['middleware' => ['web', 'auth'], 'prefix' => 'acun-ui/data-table'],
],
- If you published an older
config/acun-ui.php, the package defaults are used for the missing keys. - The download and print routes are signed and give the file only to its owner. If you use a different
guard, set it inroutes.middleware, e.g.['web', 'auth:admin']. - The print link is valid for 10 minutes (
print.token_minutes). - Search settings are in the
acun-ui.searchsection: the Search guide. - A short description of every key: the Configuration page.
Address bar
The search (q), sorting (sort, direction), filters (filters), page size (perPage), column choice (columns) and page number are written to the address bar. Reloading the page or sharing the link opens the same list.
- Each value only appears when it differs from the default.
- To turn it off, set
url_statetofalse, or write your ownqueryString()method in the table.
Pruning old files
To delete expired export files regularly, schedule the command:
// routes/console.php
Schedule::command('acun-ui:prune-exports')->hourly();
Authorization and security
DataTable checks every request from the browser again on the server. The package does not impose its own permission system; you give the rules with authorizeTable(), query() and action authorization.
Access to the table
authorizeTable() decides who can open the table:
protected function authorizeTable(): void
{
$this->authorize('viewAny', Order::class);
}
The method runs on the first load and on every later Livewire request, before property updates and action calls. A user whose access is revoked during the session can no longer run any action.
Which records are visible
query() decides which records the user sees. In apps where every user, or every customer account, sees only its own records, put that condition here:
protected function query(): Builder
{
return Order::query()->where('user_id', auth()->id());
}
- Row actions only run on a record inside
query(); sending another record's key returns 404. - Bulk actions and exports always run inside
query()and with the active filters. Keys from the browser are added on top of this query; records outside it are never touched. - The row details dialog also opens only records inside
query().
How actions are checked
Hiding a button is not security on its own. That is why the package checks every call again on the server:
- Every Livewire call (
runTableAction,runRowAction,runBulkAction,exportTable,copyRows,preparePrint) finds the action again on the server. An unknown action returns 404; a hidden, unauthorized or disabled one returns 403. authorizeTable()runs before them on every request.- The value an action returns is not sent to the browser; only redirects and file downloads go back.
Row authorization in bulk actions
authorize() runs on the server for the whole action, authorizeRow() for every targeted record:
BulkAction::delete()
->authorize(fn () => auth()->user()->can('delete-any', Order::class))
->authorizeRow(fn (Order $order) => auth()->user()->can('delete', $order)),
- If even one selected record fails
authorizeRow(), the action does not run at all and returns 403. Records are not silently skipped. - While all records are selected, the check runs on every matching record, in chunks.
- If a row action has a per-record rule (e.g.
can('delete')), give the same rule to the bulk action withauthorizeRow(). Otherwise the bulk action could be used to get around the row rule.
Values from the browser
- Sorting only happens on
sortable()columns; a column name from the browser is never passed straight into the query. The direction can only beascordesc. - The page size can only be one of the
perPageOptions(). Any other value from the address bar (e.g.-1) falls back to the default. - Filter values are checked by type: a select filter takes only declared options, a range filter only
fromandtovalues. Undeclared filters are ignored. - At most 200 characters and 10 words of the search term are used.
- The table's
$titleand$stickyHeaderproperties cannot be changed from the browser. - The number of records that can be ticked one by one is limited by
selection.max_keys.
Export limits
- One user can start at most 30 prints, downloads or copies per minute (
exports.per_minute). Above the limit a warning notification appears. - Download and print links are signed, expire and give the file only to its owner.
exports.enabled = falseturns off the export menu, bulk printing and exporting the selection together.
Note
The older name
AcunDataTablestill works but will be removed; useDataTablein new tables. A full example in the older style is in the package:examples/Livewire/OrdersTable.php.