Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Datatables Bundle Laravel Package

omines/datatables-bundle

Symfony bundle that integrates the DataTables jQuery library for realtime Ajax tables. Decoupled from data sources, it ships with Doctrine ORM, MongoDB, and Elastica adapters for building custom, efficient tables in Symfony 6.4+.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with a compatibility check, not composer require. This is a Symfony bundle (README targets Symfony 6.4+; the docs page still says 4.1+ — trust the README and the composer.json constraint). It is not a Laravel package: no service provider, no Blade directives, no Eloquent adapter, and the default renderer is TwigRenderer. In a pure Laravel app you cannot "just install it" and expect a working table; your realistic options are (a) a Symfony app/service in the same estate, (b) a deliberate bridge that boots the bundle's services inside Laravel's container, or (c) treating it as the reference architecture for a yajra/laravel-datatables implementation. Decide which of those you're doing before writing code.

If you are in a Symfony codebase (or standing up a small one), the minimal path is:

  1. composer require omines/datatables-bundle — Flex registers Omines\DataTablesBundle\DataTablesBundle automatically; otherwise add it to config/bundles.php.
  2. bin/console assets:install if you want the shipped bundles/datatables/js/datatables.js; or skip it and load DataTables from your own build/CDN.
  3. Include the DataTables client CSS/JS yourself — the bundle deliberately does not manage clientside dependencies.
  4. Build one controller action and one Twig template. That's the whole first slice.
public function index(Request $request, DataTableFactory $dataTableFactory)
{
    $table = $dataTableFactory->create()
        ->add('firstName', TextColumn::class)
        ->add('lastName', TextColumn::class)
        ->createAdapter(ArrayAdapter::class, [
            ['firstName' => 'Donald', 'lastName' => 'Trump'],
            ['firstName' => 'Barack', 'lastName' => 'Obama'],
        ])
        ->handleRequest($request);

    if ($table->isCallback()) {
        return $table->getResponse();
    }

    return $this->render('list.html.twig', ['datatable' => $table]);
}
<div id="presidents">Loading...</div>
<script src="{{ asset('bundles/datatables/js/datatables.js') }}"></script>
<script>
$(function() {
    $('#presidents').initDataTables({{ datatable_settings(datatable) }});
});
</script>

Where to look first: the quickstart, then Adapters (the real decision point), then Columns and DataTable types. Skim the CHANGELOG before pinning a version — the last release is June 2026, so verify the Symfony/PHP constraints against your app rather than the stale 4.1+ line in the prose.

Best first use case: a read-only admin index over an existing Doctrine entity, using ORMAdapter with only 'entity' => Foo::class and letting the bundle infer fields. That exercises the request/callback loop, the JSON settings function, and the default search/sort/pagination criteria in one go — and shows you immediately whether the "magic" defaults match your expectations.

Implementation Patterns

The handleRequest / isCallback / getResponse triad is the core loop. Treat it like Symfony's Form component: handleRequest() inspects the request, isCallback() tells you whether this is an AJAX data request, and getResponse() hands back the JsonResponse. Call handleRequest() before any branching, and never render a template on the callback path.

Move table definitions into DataTable types. The Quickstart aside is the pattern you should adopt on day one: implement DataTableTypeInterface::configure(DataTable $dataTable, array $options) (or extend AbstractDataTableType), then use $dataTableFactory->createFromType(EmployeeTableType::class, [...]) in the controller. This keeps controllers to ~10 lines, makes the table reusable across actions, and gives you an obvious place to inject repositories or services into the type. In Laravel terms, this is the equivalent of a dedicated DataTable class rather than configuring columns inline in a controller.

Column configuration is where most day-to-day work happens. Use the options array for field (including relation paths like 'company.name'), searchable, orderable, visible, className, and template/format options. TextColumn covers most cases; use NumberColumn, BooleanColumn, DateColumn, TwigColumn, or ConstantColumn for typed presentation, and a custom column extending the abstract column when you need a bespoke value. Add repeatable ->addOrderBy($column, DataTable::SORT_ASCENDING) for deterministic default ordering — without it, pagination on large tables is non-deterministic and you will chase phantom duplicate rows.

Pick the adapter deliberately. ORMAdapter is the default for Doctrine: 'entity' => Employee::class plus the AutomaticQueryBuilder infers joins and selects from your column fields, and the default SearchCriteriaProvider applies global search across mapped fields. For anything non-trivial (aggregates, GROUP BY, DISTINCT, subqueries, hasMany joins) supply your own query and criteria providers instead of fighting the automatic builder. ArrayAdapter is excellent for tests and static lookups; MongoDB and Elastica adapters ship too; there is intentionally no DBAL adapter (see the FAQ) — so a raw SQL or Laravel query-builder source means writing a custom adapter.

Custom adapters are the escape hatch, and the cleanest one. Implement AdapterInterface/extend AbstractAdapter and return a DataTableResults with the page of rows plus total and filtered counts. This is also the honest way to use the bundle's concepts from Laravel: the protocol (columns → criteria → results) is self-contained even if the Symfony wiring isn't.

final class EloquentAdapter extends AbstractAdapter
{
    public function getData(DataTableInterface $dataTable, array $options = []): DataTableResults
    {
        $query = $options['query'] ?? throw new \InvalidArgumentException('query required');

        $total = (clone $query)->count();
        // apply search criteria from $dataTable, then order and paginate
        $filtered = (clone $query)->count();
        $rows = $query->forPage($page, $length)->get()->all();

        return new DataTableResults($rows, $total, $filtered);
    }
}

Frontend integration is two lines. datatable_settings(table) emits the JSON config; $('#id').initDataTables(settings) boots it. Server-side options in datatables.yaml are passed nearly verbatim to the DataTables constructor, but callbacks and events must be attached on the JS side after init — they cannot be serialized from PHP.

Use persist_state consciously. query and fragment survive page reloads and are shareable but pollute URLs; local/session are cleaner for dashboards but make E2E tests flaky if state leaks between runs. Set it globally for uniform UX and override per table only where it matters.

If bridging to Laravel: register the bundle's services in a service provider, bind DataTableFactory into the container, and either pull in symfony/twig-bridge for datatable_settings or implement DataTableRendererInterface to emit Blade-friendly markup. Budget real time for this — it is not a supported path, and you own the upgrade risk.

Gotchas and Tips

  • It is Symfony-only in practice. No Eloquent adapter, no Blade, Twig expected by the default TwigRenderer, no Laravel service provider, no datatable_settings() outside Twig. If the goal is "DataTables in Laravel this sprint", yajra/laravel-datatables is the lower-risk answer; use this bundle when the host app is Symfony.
  • The AutomaticQueryBuilder is convenient, not clever. It scans metadata and infers joins from field values; joins to to-many relations can inflate row counts and break pagination totals. Prefer an explicit query builder with a deliberate count strategy for anything beyond simple ManyToOne paths.
  • Nested fields require joins. 'field' => 'company.name' works only if the adapter joins company. Ordering or searching on a path that isn't joined yields SQL errors, not graceful degradation — check the generated query in the profiler first.
  • Global versus column search behave differently. The default provider applies LIKE-style matching across mapped fields; per-column DataTables search uses its own wildcarding. For exact matches, case-sensitive needs, or searching computed/derived columns, register a custom SearchCriteriaProvider rather than adding searchable => true and hoping.
  • Whitelist what you expose. Columns and their field values map directly to queryable properties, and global search hits every mapped field. Mark sensitive or expensive columns searchable => false / orderable => false; do not rely on the client to be polite.
  • Call order matters. handleRequest() must run before isCallback(); getResponse() on a non-callback request is a bug. And return $table->getResponse() rather than hand-rolling a JsonResponse — the shape includes draw/records counters that DataTables expects.
  • Method and CSRF. method defaults to POST; if you set GET, be aware of caching layers and query-string length on wide tables. POST callbacks bypass Symfony's CSRF form protection only because they aren't form submissions — don't add blanket CSRF checks to the callback route without allowing the DataTables payload.
  • language_from_cdn: true makes an external request on every page load (and has GDPR/offline implications). For air-gapped or privacy-sensitive installs set it to false and supply translation_domain entries; the bundle ships the translation keys.
  • Config precedence is global-then-table. Everything in datatables.yaml (template, template parameters, className, columnFilter at thead/tfoot/both, renderer, translation domain) has a per-table setter. Set globally for consistency, override per table only for genuine exceptions — mixed conventions are the usual source of "why does this one table look different" tickets.
  • Template overrides are the styling lever. Point template at your own file and pass template_parameters (including className) rather than patching vendor assets; assets:install output gets overwritten on upgrade.
  • Debugging checklist: browser network tab for the callback payload (draw, start, length, search, order), Symfony profiler for the generated SQL, then dump() inside your criteria provider. If the table renders empty, check whether the callback returned 200 with data: [] (criteria/adapter issue) versus a 500 (join/SQL issue) — the two look identical in the UI.
  • Extension points to keep in your back pocket: custom AdapterInterface implementations, DataTableTypeInterface for reuse, DataTableRendererInterface for non-Twig output, custom criteria providers for search/order semantics, and custom column classes for presentation. Almost every "the bundle can't do X" complaint resolves at one of these five seams.
  • Pin and watch versions. With a June 2026 release and an active changelog, lock the minor version, read the changelog on upgrade, and run your table types through a smoke test — the API is stable but adapter and criteria internals have moved between majors.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mydnic/volet-feature-board-filament-plugin
wmde/email-address
refinery29/test-util
dees040/laravel-api-responses
teamzac/geocoder
ideatica/config-writer
keaton-php/pipe-dream
jacksonwebservices/laravel-user-suite
maksimru/conditional-laravel-debugbar
gismatul/nova-support-chat
gismatul/laravel-support-chat
networkrailbusinesssystems/oracle-api
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
refinery29/php-cs-fixer-config
parsnick/steak