Unopim datagrid
Agent skills for UnoPim — domain-specific context for AI agents (Claude Code, Cursor, Windsurf) working in an UnoPim PIM codebase. Install: npx skills add unopim/agent-skills
npx -y skills add unopim/agent-skills --skill unopim-datagridAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Implement Unopim DataGrid classes for listing records in admin panels with search, filter, sort, actions (edit/delete), and mass actions. Covers DataGrid subdirectory structure (DataGrids/Section/), prepareQueryBuilder with DB::table (prefix added by Laravel), addColumn with correct closure patterns, addAction with function callbacks for url, addMassAction, bouncer() permission checks, and Blade integration. Use this skill when creating a DataGrid for credentials, mappings, export history, or any Unopim module listing page.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.7 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
Unopim DataGrid
Overview
All DataGrid patterns are derived from the WooCommerce connector reference:
DataGrids/Credential/CredentialDataGrid.php.
Critical rules:
- DataGrid lives in a subdirectory:
DataGrids/{Section}/{Name}DataGrid.php - Methods use PHPDoc
@returnonly — no PHP return type hints on methods closurecallbacks in columns use PHP arrow functionsfn ($row) =>urlcallbacks in actions use regular function:function ($row) { return ...; }- Table names without prefix in DB::table() — Laravel adds DB_PREFIX
- Use
bouncer()->hasPermission()for action permission checks - Status badges:
class="label-active"(true) andclass="label-info text-gray-600 dark:text-gray-300"(false) - Comments: match Laravel core — no inline comments that narrate what the code does. The closure/url/permission patterns above are documented in this skill, not repeated as comments in generated code. Comment only a non-obvious why, one line.
Admin UI rule (critical):
- If the listing page includes form/filter controls in Blade, use UnoPim form components.
- Do not generate raw
<select>,<input>,<textarea>, or<label>controls whenx-admin::form.*components exist. - Keep labels/placeholders/messages in translations.
1. Class Structure
<?php
// src/DataGrids/Credential/CredentialDataGrid.php
// ^^^^^^^^^ — always a subdirectory named after the entity
namespace Webkul\{ModuleName}\DataGrids\Credential;
use Illuminate\Support\Facades\DB;
use Webkul\DataGrid\DataGrid;
class CredentialDataGrid extends DataGrid
{
/**
* Prepare query builder.
*
* @return \Illuminate\Database\Query\Builder
*/
public function prepareQueryBuilder()
{
// Use DB::table() without prefix — never Eloquent model here
$queryBuilder = DB::table('{module}_credentials')
->select(
'id',
'label',
'apiUrl',
'consumerKey',
'status',
'created_at',
);
return $queryBuilder;
}
/**
* Add columns.
*
* @return void
*/
public function prepareColumns()
{
$this->addColumn([
'index' => 'label',
'label' => trans('{module-name}::app.credentials.datagrid.label'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'apiUrl',
'label' => trans('{module-name}::app.credentials.datagrid.api-url'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'consumerKey',
'label' => trans('{module-name}::app.credentials.datagrid.consumer-key'),
'type' => 'string',
'searchable' => false,
'filterable' => false,
'sortable' => false,
]);
$this->addColumn([
'index' => 'status',
'label' => trans('{module-name}::app.credentials.datagrid.status'),
'type' => 'boolean',
'searchable' => true,
'filterable' => true,
'sortable' => false,
'closure' => fn ($row) => $row->status
? '<span class="label-active">' . trans('admin::app.common.yes') . '</span>'
: '<span class="label-info text-gray-600 dark:text-gray-300">' . trans('admin::app.common.no') . '</span>',
]);
}
/**
* Prepare actions.
*
* @return void
*/
public function prepareActions()
{
if (bouncer()->hasPermission('{module-slug}.credentials.edit')) {
$this->addAction([
'icon' => 'icon-edit',
'title' => trans('admin::app.catalog.attributes.index.datagrid.edit'),
'method' => 'GET',
'url' => function ($row) {
return route('{module-slug}.credentials.edit', $row->id);
},
]);
}
if (bouncer()->hasPermission('{module-slug}.credentials.delete')) {
$this->addAction([
'icon' => 'icon-delete',
'title' => trans('admin::app.catalog.attributes.index.datagrid.delete'),
'method' => 'DELETE',
'url' => function ($row) {
return route('{module-slug}.credentials.destroy', $row->id);
},
]);
}
}
/**
* Prepare mass actions.
*
* @return void
*/
public function prepareMassActions()
{
if (bouncer()->hasPermission('{module-slug}.credentials.mass-delete')) {
$this->addMassAction([
'title' => trans('admin::app.catalog.attributes.index.datagrid.delete'),
'url' => route('{module-slug}.credentials.mass_delete'),
'method' => 'POST',
'options' => ['actionType' => 'delete'],
]);
}
if (bouncer()->hasPermission('{module-slug}.credentials.mass-edit')) {
$this->addMassAction([
'title' => trans('admin::app.catalog.products.index.datagrid.update-status'),
'url' => route('{module-slug}.credentials.mass_update'),
'method' => 'POST',
'options' => [
[
'label' => trans('admin::app.catalog.products.index.datagrid.active'),
'value' => true,
],
[
'label' => trans('admin::app.catalog.products.index.datagrid.disable'),
'value' => false,
],
],
]);
}
}
}
2. Callback Patterns: closure vs url
| Location | Function style | Why |
|---|---|---|
addColumn closure key | Arrow function fn ($row) => | Short rendering expression |
addAction url key | function ($row) { return ...; } | Must return the URL string |
addMassAction url key | Plain string route(...) | Static URL, no row context |
Column closure (arrow function OK):
'closure' => fn ($row) => $row->status ? '<span class="label-active">Yes</span>' : '<span class="label-info ...">No</span>',
Action URL (regular function required):
'url' => function ($row) {
return route('{module-slug}.credentials.edit', $row->id);
},
Mass action URL (static — no function):
'url' => route('{module-slug}.credentials.mass_delete'),
3. Controller Integration
public function index()
{
if (request()->ajax()) {
return app(CredentialDataGrid::class)->toJson();
}
return view('{module-name}::credentials.index');
}
4. Blade View
{{-- resources/views/credentials/index.blade.php --}}
@extends('admin::layouts.master')
@section('page_title')
{{ __( '{module-name}::app.credentials.index.title' ) }}
@stop
@section('content-wrapper')
<div class="flex flex-col gap-4 p-4">
<div class="flex items-center justify-between gap-4">
<p class="text-xl font-bold text-gray-800 dark:text-white">
{{ __(' {module-name}::app.credentials.index.title') }}
</p>
@if (bouncer()->hasPermission('{module-slug}.credentials.create'))
<a
href="{{ route('{module-slug}.credentials.create') }}"
class="primary-button"
>
{{ __(' {module-name}::app.credentials.index.create-btn') }}
</a>
@endif
</div>
{{-- DataGrid component renders the table --}}
<x-admin::datagrid :src="route('{module-slug}.credentials.index')" />
</div>
@stop
5. MappingDataGrid Example
DataGrids for other entities follow the same pattern in their own subdirectory:
<?php
// src/DataGrids/Mapping/MappingDataGrid.php
namespace Webkul\{ModuleName}\DataGrids\Mapping;
use Illuminate\Support\Facades\DB;
use Webkul\DataGrid\DataGrid;
class MappingDataGrid extends DataGrid
{
/**
* @return \Illuminate\Database\Query\Builder
*/
public function prepareQueryBuilder()
{
return DB::table('{module}_mappings as m')
->leftJoin('{module}_credentials as c', 'm.credential_id', '=', 'c.id')
->select(
'm.id',
'm.sku',
'm.external_id',
'c.label as credential_label',
'm.created_at',
);
}
/**
* @return void
*/
public function prepareColumns()
{
$this->addColumn([
'index' => 'sku',
'label' => trans('{module-name}::app.mappings.datagrid.sku'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'external_id',
'label' => trans('{module-name}::app.mappings.datagrid.external-id'),
'type' => 'string',
'searchable' => true,
'filterable' => true,
'sortable' => true,
]);
$this->addColumn([
'index' => 'credential_label',
'label' => trans('{module-name}::app.mappings.datagrid.credential'),
'type' => 'string',
'searchable' => false,
'filterable' => false,
'sortable' => false,
]);
}
/**
* @return void
*/
public function prepareActions()
{
if (bouncer()->hasPermission('{module-slug}.mappings.delete')) {
$this->addAction([
'icon' => 'icon-delete',
'title' => trans('admin::app.catalog.attributes.index.datagrid.delete'),
'method' => 'DELETE',
'url' => function ($row) {
return route('{module-slug}.mappings.destroy', $row->id);
},
]);
}
}
}
6. Checklist
- DataGrid file location:
src/DataGrids/{Section}/{Name}DataGrid.php(subdirectory) - Extends
Webkul\DataGrid\DataGrid - Methods have PHPDoc
@returnonly — no PHP return type hints on methods -
DB::table('{module}_...')— table names without hardcoded prefix - Column
closureuses arrow functionfn ($row) => - Action
urluses regularfunction ($row) { return route(...); } - Mass action
urlis plainroute(...)string (no function) - Boolean columns use
label-active(true) andlabel-info text-gray-600 dark:text-gray-300(false) - Permission checks use
bouncer()->hasPermission() - Controller calls
app(DataGridClass::class)->toJson()for AJAX - Blade uses
<x-admin::datagrid :src="route(...)" />