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 classes

The frontend (React ApiTables) is a separate codebase. It only speaks HTTP to your endpoints.

Layers inside the package

LayerClass / fileWhat it does (plain English)
Configapi-tables-config.phpLists tables, guards, email/export settings
Table classYour *Table extends TableAbstractDefines columns, filters, query, actions
ServiceAPITablesServiceFinds the right table class and calls it
ControllerAPITablesControllerHTTP adapter — validates route, returns JSON
MiddlewareTablesMiddlewareRuns auth + per-table middleware
ResourcesDAPITBLResource, DynamicAPITableCollectionFormats 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:

ItemEffect
Config mergeDefault api-tables-config values available
RoutesAll control-tables endpoints become live
make:api-tableScaffold command for new tables
api-table:email-reportProcesses queued email exports
Event listenerEmailReportRequired → 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:

  1. Laravel routing matches tableName = users
  2. TablesMiddleware runs auth:sanctum + any per-table middleware
  3. APITablesController::loadTable receives the request
  4. APITablesService::loadTableStructure('users') looks up config
  5. new UsersTable(...) runs constructor → builds columns, filters, actions
  6. $table->toArray() returns structure array
  7. 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:

MethodResult 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:

DefaultTypeBehavior
show_detailsRow actionOpens detail modal with row data
export_excelBulk actionInstant Excel download (max 3,000 rows)
Sort id descQuery defaultUsed 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

TopicWhere
Octane compatibilityServices/OctaneDetector.php
Custom export classgeneral_export_class in config
Override reportingReportingService.php
Action route generationEnums/ActionsConstants.php

On this page