About bagisto-datagrid-development
A development skill for building Bagisto admin listing pages with the DataGrid engine. It explains query and column setup, controller and view wiring, and guidance for actions, filters, and export.
Key features
- 01DataGrid subclass structure
- 02Query builder and column setup
- 03AJAX controller and view wiring
- 04ACL-gated row and bulk actions
- 05Filtering, sorting, and export guidance
Use cases
- 01Building an admin listing page
- 02Adding columns and filters to a grid
- 03Configuring row or bulk actions
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ bagisto/ agent-skills --skill bagisto-datagrid-developmentAfter installing, type/bagisto-datagrid-developmentin Claude Code to confirm it loaded.
Bundled filesactions.mdcolumns.md
Details
From the project READMEDataGrid Development
An admin listing page is a DataGrid subclass plus three lines of wiring. The
engine (packages/Webkul/DataGrid) owns paging, search, filtering, sorting,
saved filters and export; the subclass supplies a query and describes its
columns. There are 51 of them in the codebase — copy the closest one rather than
inventing a shape.
The four methods
Webkul\DataGrid\DataGrid declares two abstract methods and two optional hooks:
| Method | Required | Purpose |
|---|---|---|
prepareQueryBuilder() | yes | Return a query builder, not a collection |
prepareColumns() | yes | addColumn([...]) per column |
prepareActions() | no | Per-row actions, each ACL-gated |
prepareMassActions() | no | Checkbox actions, each ACL-gated |
Tunable properties, overridden only when the default is wrong: $primaryColumn
(default 'id'), $sortColumn, $sortOrder ('desc'), $itemsPerPage (10),
$perPageOptions.
The shape
class CurrencyDataGrid extends DataGrid
{
/**
* Prepare query builder.
*
* @return Builder
*/
public function prepareQueryBuilder()
{
return DB::table('currencies')
->select('id', 'name', 'code');
}
/**
* Add Columns.
*
* @return void
*/
public function prepareColumns()
{
$this->addColumn([
'index' => 'name',
'label' => trans('admin::app.settings.currencies.index.datagrid.name'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
}
/**
* Prepare actions.
*
* @return void
*/
public function prepareActions()
{
if (bouncer()->hasPermission('settings.currencies.edit')) {
$this->addAction([
'index' => 'edit',
'icon' => 'icon-edit',
'title' => trans('admin::app.settings.currencies.index.datagrid.edit'),
'method' => 'GET',
'url' => fn ($row) => route('admin.settings.currencies.edit', $row->id),
]);
}
}
}
Align the => inside an addColumn/addAction array only if the file you are
editing already does; Pint does not enforce alignment either way, and the
codebase has both.
Wiring
The controller serves JSON on an AJAX hit and the view otherwise — one route, two responses:
public function index()
{
if (request()->ajax()) {
return datagrid(CurrencyDataGrid::class)->process();
}
return view('admin::settings.currencies.index');
}
The Blade side is one tag pointing at that same route:
<x-admin::datagrid :src="route('admin.settings.currencies.index')" />
datagrid() throws InvalidDataGridException unless the class extends
DataGrid, so the class name is the only contract.
Reference files
| File | Load when |
|---|---|
| columns.md | Column types, search/filter/sort flags, dropdown options, closures, joins and addFilter |
| actions.md | Row actions, mass actions, ACL gating, export |
Non-negotiables
prepareQueryBuilder()returns a builder. Calling->get(),->paginate()or mapping to a collection breaks paging, filtering and export, because the engine appends to the query you return.- The query builder is the one place
DB::is expected. Everywhere else in Bagisto goes through a repository; a DataGrid is built on the query builder by design. - Every action and mass action is wrapped in
bouncer()->hasPermission(...). An ungated action renders for admins who cannot perform it, and the grid is the most common place this is forgotten. - Every
labelgoes throughtrans(), with the key added to all 23 locales. - A joined query needs
addFilter()for every aliased column — see columns.md. Without it, filtering and sorting on that column produce an ambiguous-column SQL error. - Escape whatever a closure interpolates. Cells render through
v-html. The engine strips tags from raw values first, but not quotes — so a value placed inside an attribute can break out. See columns.md.
REQUIRED SUB-SKILL: Use bagisto-change-verification before calling any change done.