Architecture overview
How the ApiTables Laravel package fits together
This page explains how the package is organized and what happens when a request arrives. Read Key concepts first if terms like structure and server-driven are new.
The big picture
ApiTables is one Composer package. Your Laravel app consumes it — you do not fork the package for each table.
Your Laravel app
├── config/api-tables-config.php ← you edit: which tables exist
├── app/ApiTables/*.php ← you edit: table definitions
└── vendor/storageitsolutions/... ← package: routes, controller, base classesThe frontend (React ApiTables) is a separate codebase. It only speaks HTTP to your endpoints.
Layers inside the package
| Layer | Class / file | What it does (plain English) |
|---|---|---|
| Config | api-tables-config.php | Lists tables, guards, email/export settings |
| Table class | Your *Table extends TableAbstract | Defines columns, filters, query, actions |
| Service | APITablesService | Finds the right table class and calls it |
| Controller | APITablesController | HTTP adapter — validates route, returns JSON |
| Middleware | TablesMiddleware | Runs auth + per-table middleware |
| Resources | DAPITBLResource, DynamicAPITableCollection | Formats rows and pagination for JSON |
Junior takeaway
You spend 90% of your time in your table class and config. The controller and service are rarely modified.
Bootstrap (what happens at app boot)
APIsTablesServiceProvider registers:
| Item | Effect |
|---|---|
| Config merge | Default api-tables-config values available |
| Routes | All control-tables endpoints become live |
make:api-table | Scaffold command for new tables |
api-table:email-report | Processes queued email exports |
| Event listener | EmailReportRequired → queue → send report |
No manual route registration in your app's routes/api.php.
Request lifecycle (step by step)
When a client calls GET /api/api-table/control-tables/load-table/users:
- Laravel routing matches
tableName = users TablesMiddlewarerunsauth:sanctum+ any per-table middlewareAPITablesController::loadTablereceives the requestAPITablesService::loadTableStructure('users')looks up confignew UsersTable(...)runs constructor → builds columns, filters, actions$table->toArray()returns structure array- Controller wraps with
{ success: true, ... }and returns JSON
The same table instance pattern is used for query-table and action endpoints.
What the table constructor builds
When new UsersTable() runs, TableAbstract::__construct() calls:
| Method | Result stored in |
|---|---|
setInitialBuilder() | $this->builder (Eloquent query) |
prepareColumnsArray() + columns() | $this->columns |
filters() | $this->filters |
filters(true) | $this->advancedFilters |
setRowActions() | $this->rowActions |
setBulkActions() | $this->bulkActions |
setSortables() | $this->sortables |
This is why a typo in setInitialBuilder() breaks every endpoint for that table.
Built-in defaults (every table)
Unless you override setRowActions() / setBulkActions() completely, you inherit:
| Default | Type | Behavior |
|---|---|---|
show_details | Row action | Opens detail modal with row data |
export_excel | Bulk action | Instant Excel download (max 3,000 rows) |
Sort id desc | Query default | Used when client sends no sorts |
To add actions, return parent defaults merged with yours:
protected function setRowActions(): array
{
return array_merge(parent::setRowActions(), [
'archive' => new TableRowAction(/* ... */),
]);
}Senior topics
| Topic | Where |
|---|---|
| Octane compatibility | Services/OctaneDetector.php |
| Custom export class | general_export_class in config |
| Override reporting | ReportingService.php |
| Action route generation | Enums/ActionsConstants.php |
Related
- Data flow — sequence diagrams per endpoint
- Package layout —
src/file map - Endpoints overview — route list
- Troubleshooting — when things go wrong