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 Buttons Laravel Package

yajra/laravel-datatables-buttons

Laravel DataTables Buttons is a plugin for Laravel DataTables that handles server-side exporting of tables to CSV, Excel, and PDF, plus printing. Compatible with Laravel 12.x and PHP 8.3+, it installs via Composer and includes an optional service provider and publishable config/assets.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install with a version aligned to your framework

composer require yajra/laravel-datatables-buttons:^13

Version alignment matters more than usual here: 13.x targets Laravel 12/13 and PHP >= 8.3, 11.x targets Laravel 11, 10.x Laravel 10, 9.x Laravel 9. Match the buttons major to your yajra/laravel-datatables major — a mismatch produces confusing "class not found" / method-signature errors from the core DataTable class rather than a clean composer conflict.

2. Publish config and assets (optional but do it early)

php artisan vendor:publish --tag=datatables-buttons --force

This gives you config/datatables-buttons.php (default button sets, export defaults, model/namespace for the generator) plus the generator stubs. Publishing up front makes it obvious which knobs exist instead of discovering them by reading vendor/.

3. Generate a DataTable class

php artisan datatables:make Users --model=User

You get app/DataTables/UsersDataTable.php extending Yajra\DataTables\Services\DataTable. This class is the heart of the package: it holds query(), html() (the builder), getColumns(), and the export hooks.

4. Wire up a route and a view

// routes/web.php
Route::get('users', fn (UsersDataTable $dataTable) => $dataTable->render('users.index'));
{{-- resources/views/users/index.blade.php --}}
{!! $dataTable->table() !!}

@push('scripts')
    {!! $dataTable->scripts() !!}
@endpush

Don't forget the front-end dependencies: DataTables core, the Buttons extension JS/CSS, plus JSZip (Excel), pdfmake + vfs_fonts (PDF), and the print button assets. The scripts() call renders the init code; it does not pull in those assets for you.

5. Enable buttons — the first real use case

Server-side export is the whole point of this package. Turn on the buttons toolbar by including B in the DOM string:

public function html(): HtmlBuilder
{
    return $this->builder()
        ->setTableId('users-table')
        ->columns($this->getColumns())
        ->minifiedAjax()
        ->dom('Bfrtip')
        ->orderBy(1)
        ->buttons([
            Button::make('excel'),
            Button::make('csv'),
            Button::make('pdf'),
            Button::make('print'),
            Button::make('colvis'),
            Button::make('reset'),
            Button::make('reload'),
        ]);
}

That is the entire "day one" loop: table renders, buttons appear, clicking Excel hits the same endpoint with action=excel and streams a download generated server-side from the same query. Because the export request carries the current search/order/filter parameters, exports automatically respect whatever the user has filtered — that is the package's core value proposition, and the reason you should not hand-roll CSV endpoints.

Where to look first in the source: src/Services/DataTable.php (export dispatch, ajax(), filename(), exportClass()), src/Html/Builder.php (dom, buttons, minifiedAjax, postAjax, scripts), src/Buttons/, and src/Export/.

Implementation Patterns

Treat DataTable as a controller, not a view model. The idiomatic pattern is one class per listing, returned from the route (either via ->render($view) or, since v12.3.0, as a route action directly: Route::get('users', UsersDataTable::class)). Keep query() returning a Builder — every export, search, and sort reuses it, so anything in query() is automatically honored by exports.

Column-level export control is the workhorse pattern. In getColumns():

return [
    Column::computed('action')
        ->exportable(false)
        ->printable(false)
        ->width(120)
        ->addClass('text-center'),
    Column::make('id'),
    Column::make('name'),
    Column::make('email'),
    Column::make('created_at'),
];

Rules of thumb:

  • Action/buttons columns: exportable(false)->printable(false).
  • Internal IDs you show but don't want in a customer-facing export: exportable(false) keeps them on screen without polluting the file. visible(false) is not the same thing — hidden columns still export unless you also mark them non-exportable.
  • addIndexColumn() should almost always be paired with exportable(false)->printable(false).

Use rawColumns() + exportRender() together. The moment a column renders HTML (<a href=...>, badges, status pills), you need rawColumns(['action', 'status']) for the table to display it, and an exportRender() callback so the CSV/XLSX doesn't contain literal <span class="badge">. This is the single most common bug in these tables:

Column::make('status')
    ->exportRender(fn ($row) => $row->status->label()),   // clean text in the export

Since v11.1.0 you also have exportFormat() for cell styling (XLSX), and since v13.2.0 enums export natively, so Column::make('status') on a cast enum emits the scalar value instead of blowing up. If you're on an older release and a status enum exports as blank, that's the missing feature.

Customize the export without touching query(). Override the hooks on the DataTable:

protected function filename(): string
{
    return 'users_' . date('Ymd_His');
}

protected function exportClass(): string
{
    return \App\Exports\UsersExport::class;   // for headers/styling/column mapping
}

exportFile() lets you swap the export engine entirely; printPreview() controls whether the print button renders a preview window or goes straight to the browser dialog. Use these rather than post-processing the downloaded file.

Gate the buttons, not just the routes. The buttons config is PHP, so authorization belongs there:

->buttons(array_filter([
    Button::make('excel')->enabled(auth()->user()->can('export users')),
    Button::make('print'),
]))

Because the export request goes to the data endpoint, also authorize inside ajax()/query() — a hidden button is not access control.

Custom buttons via ButtonAction classes. The generator produces action-button stubs; the pattern that scales is a small class per custom button (reload, bulk action, modal trigger) rather than inlining JS strings into ->buttons(). This keeps the builder readable and lets you share buttons across tables.

Extending globally: macros and container binding. Since v12.1.0 the DataTable base class is macroable, so cross-cutting defaults (your standard button set, your standard dom, your locale) can live in one service provider:

DataTable::macro('standardButtons', fn () => $this->buttons([...]));

Similarly, DataTableHtml::make() now resolves the HTML builder through the container even when you pass constructor arguments, so you can bind a custom builder subclass (or use resolving/afterResolving) and have every table in the app pick up your defaults. Explicit positional arguments still override the resolved instance's constructor, so existing make(...) calls keep working after v13.2.1.

Testing pattern. Feature-test the data endpoint (assert JSON structure, draw, filtered totals) and test export endpoints separately by asserting Content-Disposition and content type rather than parsing a binary body. Hitting ?action=csv with the same column/search params the browser would send is enough to cover the export branch deterministically — no Selenium needed.

Gotchas and Tips

Pin the patch version, especially around the ajax implementation. v13.0.1 switched postAjax to minifiedAjax, and v13.0.2 reverted it. If your table relies on POST (long filter payloads, complex data callbacks, or a custom ajax action — fixed in 12.1.3), a silent minor bump can break exports because the export request no longer carries the same payload shape. Keep composer.lock committed and re-test exports after every bump.

POST tables need CSRF, and exports are POSTs too. If you use postAjax() (or rely on the buttons extension posting for large queries), make sure the export request includes the token; otherwise you get an empty file or a 419 that the browser happily downloads as export.csv containing an HTML error page. Symptom to watch for: CSV downloads that open fine in a text editor but fail in Excel — usually an error page, not a CSV.

dom('Bfrtip') is mandatory. Buttons render nothing without B in the DOM string. Equally common: buttons appear but do nothing because the Buttons extension JS/CSS wasn't loaded, or because JSZip/pdfmake is missing — the Excel/PDF buttons will silently no-op in the console.

PDF and Excel have real external requirements. PDF needs a PDF driver (dompdf/snappy) installed and configured; Excel export in v13.x goes through OpenSpout (fastexcel was dropped in 13.1.0). If you're mid-upgrade from 11.x/12.x and your export class extended a fastexcel-based class, that's a breaking change. The "export package integration" error message was improved in 10.0.6 — if you see a vague message about a missing dependency, read composer.json before googling.

Big exports will time out or OOM. The export re-executes the query without pagination. For anything over ~50k rows: constrain with ->limit()-style filtering, chunk the query, or move to a queued export that emails a link. Also check whether rawColumns callbacks are doing N+1 loads — they run per row, and an export amplifies every lazy relation by the row count. Eager-load in query().

Column order/index assumptions break with computed columns. orderBy(1) and hardcoded columns[i] indices shift when you prepend an index column or an action column. Use orderBy on the real column name where possible, and be aware that the front-end index and the export index are not necessarily the same when exportable(false) columns exist.

HTML leaks into exports. Covered above because it's the #1 support question: rawColumns() without exportRender() gives you <span> tags in the spreadsheet, and dates render as raw Y-m-d H:i:s unless you format them (exportFormat() or a callback). Conversely, don't put rawColumns around columns that contain user input without escaping the non-exported parts.

Octane caveat. Versions before 10.0.5 could redeclare the export query-generator function across requests under Laravel Octane. On modern 13.x this is fixed, but if you're porting an old table to Octane, verify the export endpoint under load (ab/wrk against ?action=csv) rather than assuming it's stateless.

Debugging ladder when an export misbehaves: (1) hit the same URL manually with ?action=csv plus a minimal columns[0][data]=id payload and see the raw response; (2) temporarily ->toJson()/->toHtml() the table in a test to confirm the query is right; (3) check Content-Disposition matches filename(); (4) confirm the request actually reached the DataTable (log in ajax()), because a 404 from a mismatched route is downloaded as a file and looks like a corrupt export.

Extension points worth knowing: custom Button classes for bespoke toolbar actions, a custom export class via exportClass()/exportFile() for header rows, column mapping and styling, DataTable macros for app-wide defaults, and container binding / afterResolving on the HTML builder for shared markup conventions. Prefer these over editing vendor files — this package ships frequently (13.0.x → 13.2.x within months) and vendor patches will be lost on the next composer update.

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core