Vue admin tables¶
Craft.VueAdminTable is the listing UI behind almost every settings screen — Sections,
Fields, Entry Types, Filesystems, GraphQL schemas. If your plugin has a "list of things I
manage" page, this is what Craft uses, and it gives you sorting, pagination, search, reordering,
bulk actions, and delete confirmation for a page of config.
It is not an element index. Element indexes are for elements; admin tables are for everything else. Reach for the element index if your rows are elements, and this if they aren't.
It's a Vue component with no Twig macro — you register the asset bundle, drop an empty container
in your template, and construct it in a {% js %} block.
Setup¶
{% extends "_layouts/cp" %}
{% set title = "Widgets"|t('my-plugin') %}
{% do view.registerAssetBundle('craft\\web\\assets\\admintable\\AdminTableAsset') %}
{% block content %}
<div id="widgets-vue-admin-table"></div>
{% endblock %}
{% js %}
(() => {
const columns = [
{ name: '__slot:title', title: Craft.t('my-plugin', 'Name'), sortField: true },
{ name: '__slot:handle', title: Craft.t('my-plugin', 'Handle'), sortField: true },
{ name: 'type', title: Craft.t('my-plugin', 'Type'), sortField: true },
];
new Craft.VueAdminTable({
columns,
container: '#widgets-vue-admin-table',
emptyMessage: Craft.t('my-plugin', 'No widgets exist yet.'),
tableDataEndpoint: 'my-plugin/widgets/table-data',
deleteAction: 'my-plugin/widgets/delete',
deleteConfirmationMessage: Craft.t('my-plugin', 'Are you sure you want to delete “{name}”?'),
});
})();
{% endjs %}
That's the whole pattern, and it's exactly what settings/sections/_index.twig does. Wrapping
it in an IIFE keeps columns out of the global scope — Craft's own templates do the same.
Strings must be registered for JS translation up front, or Craft.t() returns the source
string:
{% do view.registerTranslations('my-plugin', ["Name", "Handle", "Type", "No widgets exist yet."]) %}
Data mode vs API mode¶
Two ways to feed the table, and the choice costs you features.
Data mode — pass rows straight to tableData. Simple, but no column sorting and no
pagination. Fine for a short, bounded list.
new Craft.VueAdminTable({
columns,
container: '#widgets-vue-admin-table',
tableData: [{ id: 1, title: 'News', handle: 'news' }],
});
API mode — point tableDataEndpoint at a controller action. You get sorting and
pagination, but your controller must return this exact shape:
return $this->asJson([
'pagination' => [
'total' => (int)$total,
'per_page' => (int)$limit,
'current_page' => (int)$page,
'last_page' => (int)$lastPage,
'next_page_url' => $nextPageUrl,
'prev_page_url' => $prevPageUrl,
'from' => (int)$from,
'to' => (int)$to,
],
'data' => $rows,
]);
Each row's array keys map to column names. With sortField: true, the sort arrives as
{field}|{direction} — e.g. email|asc.
search behaves differently per mode too: in data mode it filters client-side across the
attributes you list in searchParams; in API mode it's handed to your endpoint and you
implement it.
Columns¶
| Key | Description |
|---|---|
name |
Handle of the column, matched against the row data's key |
title |
Display title in the header |
titleClass |
Class added to the header cell (optional) |
dataClass |
Class added to the data cell (optional) |
callback |
Function to transform the cell output — receives the attribute's value |
sortField |
Field name to sort on. API mode only (optional) |
Special columns¶
Prefix name with __slot: for the four built-in column types.
| Slot | Renders |
|---|---|
__slot:title |
Title cell like an element index — supports icon, iconColor, status, title, and url in the row data |
__slot:handle |
The handle, wrapped in <code> |
__slot:menu |
A dropdown link menu inside the cell |
__slot:detail |
A clickable toggle revealing an expandable detail row beneath its parent |
__slot:detail takes its own row-data object:
| Attribute | Description |
|---|---|
handle |
HTML for the clickable toggle. Omit for an "info" icon (optional) |
title |
title attribute on the toggle (optional) |
content |
HTML for the detail row — or an array, with showAsList |
showAsList |
Default false. With an array content, renders a key → value list (optional) |
Column callback¶
const columns = [
{
name: 'myColumn',
title: Craft.t('site', 'My Column'),
callback: function(value) {
if (value) {
return '<span data-icon="check" title="' + Craft.t('app', 'Yes') + '"></span>';
}
return '';
},
},
];
All options¶
Every option Craft.VueAdminTable accepts.
Data & display¶
| Option | Type | Default | Description |
|---|---|---|---|
columns |
Array | [] |
Defines the table columns |
container |
String | null |
CSS selector for the element to mount on |
tableData |
Array | null |
Rows for data mode |
tableDataEndpoint |
String | null |
Endpoint for api mode — returns data, pagination, and metadata |
perPage |
Int | null |
Rows per page. Used with tableDataEndpoint |
emptyMessage |
String | No data available. |
Shown when there are no rows |
itemLabels |
Object | {singular: Item, plural: Items} |
Singular/plural names for pagination counts |
minItems |
Int | null |
Minimum number of items allowed in the table |
fullPage |
Bool | false |
Set true when the table is the only element on the template |
fullPane |
Bool | true |
Set false when the table isn't the only UI component in its pane |
padded |
Bool | false |
Adds padding around the table |
Selection¶
| Option | Type | Default | Description |
|---|---|---|---|
checkboxes |
Bool | false |
Show the checkbox column |
checkboxStatus |
Function | true |
Callback per row — return false to disable that row's checkbox |
allowMultipleSelections |
Bool | true |
When false, hides the select-all checkbox |
allowMultipleDeletions |
Bool | true |
Whether multiple rows can be deleted at once |
Search¶
| Option | Type | Default | Description |
|---|---|---|---|
search |
Bool | false |
Show the search field |
searchParams |
Array | [] |
Attributes to search across — data mode only |
searchPlaceholder |
String | Search |
Placeholder text |
searchClear |
String | Clear |
Title of the clear button |
noSearchResults |
String | No results |
Empty-search text |
Deleting¶
| Option | Type | Default | Description |
|---|---|---|---|
deleteAction |
String | null |
Action URL to post to. Enables the delete buttons when set |
deleteCallback |
Function | null |
Runs after deletion — receives the deleted row's ID |
deleteConfirmationMessage |
String | Are you sure you want to delete “{name}”? |
Confirmation prompt |
deleteSuccessMessage |
String | “{name}” deleted. |
Success notice |
deleteFailMessage |
String | Couldn’t delete “{name}”. |
Failure notice |
beforeDelete |
Function | resolves true |
Returns a Promise<boolean> deciding whether the delete proceeds |
Reordering¶
| Option | Type | Default | Description |
|---|---|---|---|
reorderAction |
String | null |
Action URL. Drag handles appear when set |
paginatedReorderAction |
String | null |
Reorder action for paginated data |
moveToPageAction |
String | null |
Action URL for moving an item to another page |
reorderSuccessMessage |
String | Items reordered |
Success notice |
reorderFailMessage |
String | Couldn’t reorder items |
Failure notice |
Actions & buttons¶
| Option | Type | Default | Description |
|---|---|---|---|
actions |
Array | [] |
Action buttons in the table toolbar |
footerActions |
Array | [] |
Action buttons in the table footer |
buttons |
Array | [] |
Link buttons in the toolbar |
Several options are switches disguised as strings: deleteAction, reorderAction, and
moveToPageAction each turn their feature on by being non-null. There's no deletable: true.
Action buttons¶
Used with checkboxes for singular or bulk operations. An entry is either a menu button with
sub-actions, or a single action button.
Menu button: label, icon (optional), and actions — an array of sub-actions.
Sub-action / single action button:
| Name | Type | Description |
|---|---|---|
label |
String | Title to show |
action |
String | Action URI to post to |
param |
String | Name of the post parameter |
value |
String | Value posted with param |
ajax |
Bool | Post via Ajax |
status |
String | Status icon for the button |
allowMultiple |
Bool | Whether it can run with multiple items selected |
separator |
Bool | Adds a separating line above. Menu sub-actions only |
Single action buttons take all of these except separator.
Buttons¶
Simple links in the top right of the toolbar — despite the name, they're anchors.
| Name | Description |
|---|---|
label |
Link label |
icon |
Link icon (optional) |
href |
The href attribute |
enabled |
Boolean, or a callback returning one |
new Craft.VueAdminTable({
buttons: [
{
label: 'Create New Thing',
icon: 'plus',
href: '{{ cpUrl("my-plugin/thing/new") }}',
enabled: () => true,
},
],
});
Events¶
Pass these as options.
| Event | Data | Fires |
|---|---|---|
onLoaded |
— | Table has loaded, regardless of data loading |
onLoading |
— | Table enters a loading state |
onData |
Array of objects | Successful load or page change |
onSelect |
Array of IDs | A checkbox or select-all changes |
onPagination |
Object | Pagination loaded, including first load |
onQueryParams |
Object | Query params are being built — return the object |
onCellClicked |
data, field, event | Cell click |
onCellDoubleClicked |
data, field, event | Cell double-click |
onRowClicked |
data, event | Row click |
onRowDoubleClicked |
data, event | Row double-click |
onQueryParams is the one with a return value — it's your hook for adding filters to the
endpoint request:
new Craft.VueAdminTable({
onQueryParams: function(params) {
params.foo = 'bar';
return params;
},
});
Properties & methods¶
| Name | |
|---|---|
instance |
The Vue instance — the table is wrapped in a skeleton Vue app |
$table |
The VueAdminTable component instance, for direct access to its properties and methods |
reload() |
Reload the table data |
const adminTable = new Craft.VueAdminTable({ /* … */ });
setInterval(() => adminTable.reload(), 15000);
Before delete¶
beforeDelete receives the row id and returns a Promise<boolean> — resolve false to
cancel. Useful for a secondary confirmation or a server-side check:
new Craft.VueAdminTable({
beforeDelete: id => {
return new Promise(function (resolve) {
setTimeout(() => {
resolve(id > 99);
}, 1500);
});
},
});
Note
The preview is a static stand-in. Everything here is rendered by Vue at runtime —
there's no server-side markup to copy. The demo above is hand-written HTML using the classes
the component produces (.vue-admin-table, .tableview, table.data, .cell-bold) so you
can see the shape, but you never write this markup yourself. Your template contributes one
empty <div>.
Note
Craft documents this one itself. Unusually, the component ships with its own README at
src/web/assets/admintable/README.md in craftcms/cms. This page is drawn from it plus the
5.10 source; if something here looks stale, that file is the place to check.