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

Laravel Datatables Oracle Laravel Package

yajra/laravel-datatables-oracle

Laravel package for DataTables server-side processing. Turn Eloquent queries, Fluent Query Builder queries, or Collections into AJAX-ready JSON with a single line via the DataTables facade. Filtering, searching, sorting, and pagination are handled for you.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm the version matrix. The package majors track Laravel majors, so pick the constraint that matches the app:

composer require yajra/laravel-datatables-oracle:"^13"   # Laravel 13, PHP >= 8.3

Use yajra/laravel-datatables:"^13" if you also want the extras (export buttons, editor, html builder). The provider and DataTables facade are auto-discovered on modern Laravel; only register Yajra\DataTables\DataTablesServiceProvider and the facade alias manually on legacy installs.

2. Publish the config once per project.

php artisan vendor:publish --provider="Yajra\DataTables\DataTablesServiceProvider"

config/datatables.php is where search.smart, search.case_insensitive, min_search_length, escaping, and the column allowlist live. Skim it before your first table — most "why doesn't search behave like I expect" questions are answered there.

3. Look here first, in this order: the generated DataTable class, the docs site quick starter, then vendor/yajra/laravel-datatables-oracle/src/QueryDataTable.php and CollectionDataTable.php when behavior is unclear. The published config and Services\DataTable base class are the two files a working developer actually reads.

4. First use case — an admin users list. Scaffold a dedicated DataTable class:

php artisan datatables:make Users

This gives you app/DataTables/UsersDataTable.php with html(), query(), and dataTable(). Fill in the three parts:

public function query(User $model)
{
    return $model->newQuery()->with('roles')->select('users.*');
}

protected function getColumns(): array
{
    return [
        ['data' => 'id',    'name' => 'id',    'title' => 'ID'],
        ['data' => 'name',  'name' => 'name',  'title' => 'Name'],
        ['data' => 'email', 'name' => 'email', 'title' => 'Email'],
        ['data' => 'roles', 'name' => 'roles.name', 'title' => 'Roles', 'orderable' => false, 'searchable' => false],
        ['data' => 'action','name' => 'action','title' => '', 'orderable' => false, 'searchable' => false],
    ];
}

public function dataTable(DataTable $dataTable)
{
    return $dataTable
        ->eloquent($dataTable)
        ->addColumn('roles', fn (User $u) => $u->roles->pluck('name')->implode(', '))
        ->addColumn('action', fn (User $u) => view('users.actions', compact('u'))->render())
        ->rawColumns(['action'])
        ->addIndexColumn();
}

Then expose a route and point DataTables at it:

Route::get('users/data', [UsersController::class, 'data'])->name('users.data');
$('#users-table').DataTable({
    processing: true,
    serverSide: true,
    ajax: '{{ route('users.data') }}',
    columns: [
        { data: 'DT_RowIndex', name: 'DT_RowIndex', orderable: false, searchable: false },
        { data: 'name' }, { data: 'email' }, { data: 'roles' },
        { data: 'action', orderable: false, searchable: false },
    ],
});

The name values in PHP must line up with the SQL columns you intend to sort/search; data values must line up with the JSON keys. That one-to-one mapping is the whole mental model of this package.

Implementation Patterns

Choose the driver deliberately. DataTables::eloquent(User::query()) when you need model accessors, casts, and relations; DataTables::query(DB::table(...)) for hot reporting queries where you want to hand-tune the SQL and skip hydration; DataTables::collection(...) only for small, already-computed result sets. DataTables::make() picks the driver by inspecting the argument type — handy in shared helpers, but explicit is better in controllers.

Facade for one-offs, class for anything reused. A one-screen report can live entirely in the controller:

return DataTables::eloquent(Invoice::query()->with('client'))
    ->addColumn('total', fn ($i) => number_format($i->total_cents / 100, 2))
    ->rawColumns(['status'])
    ->editColumn('status', fn ($i) => view('partials.status', ['status' => $i->status])->render())
    ->toJson();

Anything with authorization, custom filters, or exports belongs in a Services\DataTable subclass so ->html() (the markup + JS) and the AJAX endpoint stay in a single, testable place.

Filtering: use filterColumn for anything non-trivial. The built-in LIKE search is fine for plain columns, but date ranges, enums, and relation columns need explicit handling:

->filterColumn('created_at', function ($query, $keyword) {
    $query->whereDate('created_at', $keyword);
})
->filterColumn('roles.name', function ($query, $keyword) {
    $query->whereHas('roles', fn ($q) => $q->where('name', 'like', "%{$keyword}%"));
})

For a global, cross-column filter use ->filter(fn ($query) => $query->where(...), true) — the second argument marks it as a global filter. Recent versions pass a relation resolver into filter and order callbacks, which matters when the same column name is reachable through more than one relation.

Ordering computed and relation columns. Anything not physically in the result set needs orderColumn:

->orderColumn('roles', fn ($query, $direction) =>
    $query->orderBy(Role::select('name')->whereColumn('roles.id', 'role_user.role_id'), $direction))

Prefer ->orderColumn('name', 'users.name $1') string form only when the column is unambiguous. Never pass user-supplied direction strings into orderByRaw.

Relations: eager-load, then flatten in PHP. with('roles') + addColumn is the reliable pattern. withCount('orders') gives you a real SQL-backed sortable column (orders_count) without N+1. Joining and selecting relation columns is where ambiguous-column and duplicate-row bugs live — when you must join, always select('users.*') and qualify every name in getColumns().

Per-row mutation and formatting. ->editColumn() and ->addColumn() run in the processor; ->format() (aliased ->setRowAttr-style row shaping) runs over the whole row. Newer releases add processWith() to run a callback on each row before processing — useful for injecting computed flags or masking values consistently across columns rather than repeating logic in every editColumn.

Exports stay in the same class. Add ->parameters(['buttons' => [...]]), implement ->filename() and ->html() with the Buttons extension, and reuse the same query() so exported data never drifts from on-screen data. Keep addColumn/editColumn identical for both paths, or the Excel file will show different values than the table.

API resources and JSON shaping. You can hand ->toJson() a resource collection, or apply format() for light reshaping. Recent versions pass the HTTP request into API resources, so whenLoaded/whenCounted conditional fields behave correctly.

Server-side column controls. If you use ColumnControl, SearchPanes, or SearchBuilder on the frontend, the server-side counterparts are built in — but they only work for columns declared in getColumns(). The searchable flag you set per column is honored.

Gotchas and Tips

  • HTML requires rawColumns. Anything rendered by a Blade view or containing markup must be listed in ->rawColumns([...]) after addColumn, or it renders as escaped text. Conversely, don't add a column to rawColumns unless you trust its contents — that's the XSS boundary.
  • Closures are not searchable. addColumn/editColumn values have no SQL counterpart, so sorting or searching those columns silently falls back to whatever the name maps to — often nothing. Set 'orderable' => false, 'searchable' => false and implement orderColumn if you need it.
  • Ambiguous columns are the #1 bug. Any join plus unqualified name => 'name' produces Column 'name' in order clause is ambiguous. Qualify everything (users.name) and always constrain the select. Recent versions add alias options and improved detection, but explicit qualification is still the fix.
  • Case sensitivity depends on the DB, not the package. MySQL's default collation makes LIKE case-insensitive; Postgres does not. Use whereRaw('LOWER(col) LIKE ?', ...) or ILIKE in filterColumn when you need parity across environments, and check search.case_insensitive in config.
  • php artisan serve is known to misfire. The README explicitly warns about spurious redirects, 401s, and 404s under the built-in server. Use Valet, Herd, Sail, or Homestead before you spend an afternoon debugging a phantom auth issue.
  • Debug mode leaks a lot. With APP_DEBUG=true the JSON payload includes the executed queries and request input. Confirm it's false in production; treat a staging APP_DEBUG=true as a data exposure risk, not just noise.
  • Collection driver is O(n) memory. It hydrates everything, then sorts/paginates in PHP. Sorting columns are resolved once per query now, but it's still the wrong tool above a few thousand rows — switch to eloquent/query and let the DB paginate.
  • skipTotalRecords() for grouped/union queries. When the query uses GROUP BY or UNION, the automatic count query is often wrong or slow; skip it and supply the count yourself.
  • Keep the package patched. The 13.1.x line carried several security fixes — SQL injection on orderByNullsLast, an allowlist replacing a fragile denylist for column-name validation, and passing the request into API resources. Also fixed: double format() calls, unregistered columns crashing SearchPanes, double-wildcarded search keywords, and schema-qualified table searches. Pin to ^13.1 or later.
  • Column names with non-Latin characters and spaces. Support was added in 13.1.4, but the allowlist validation still means you should keep name values to real column paths (users.name, roles.name), not display labels.
  • compileBlade uses Blade::render() — column content with Blade syntax is genuinely compiled. Never let user input reach those strings.
  • Debugging loop that works: reproduce with DataTables::eloquent(User::query())->toJson() in php artisan tinker or a test, wrap in DB::listen() to see the generated SQL, confirm the count query and the order clause, then check that columns[].name in JS matches. Almost every bug is a mismatch in one of those three places.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo