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

Primevue Datatables Laravel Package

savannabits/primevue-datatables

Laravel package for building PrimeVue DataTables with server-side processing. It pairs Eloquent queries with sortable, searchable, paginated tables, so you can ship responsive admin interfaces with minimal configuration and reusable backend logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. The package is a small, single-maintainer adapter last released in July 2023. Install it with an explicit constraint rather than a floating one, and confirm it resolves against your current lockfile before you write any code:

composer require savannabits/primevue-datatables:~1.5.0

Treat ~1.5.0 as a hard pin, not a courtesy. With a short changelog and no dependents, you are the only regression signal this package has.

Publish and read the scaffolding first. The package ships publishable config and frontend scaffolding, and that scaffolding is the fastest way to learn the request/response contract it expects:

php artisan vendor:publish --provider="Savannabits\PrimevueDatatables\PrimevueDatatablesServiceProvider"

Read the generated config and any stub controller/component before touching your own code. The contract points you need to internalize are: which query-string keys the backend parses (page, per_page/rows, sortField, sortOrder, filters), how filter objects are shaped, and what the JSON envelope looks like. Everything downstream is a mapping problem against those names.

Confirm your stack gate. v1.5.0 is where Laravel 10 support landed, and that is the newest verified target. Before committing, run the install against your actual PHP/Laravel/database matrix in CI. If your roadmap reaches Laravel 11 or 12 inside this tool's expected life, decide now whether you are forking or replacing — do not discover it mid-upgrade.

First use case: one read-only admin table. Pick a single low-risk back-office grid — a user list, an audit log, an orders index — where the query is a plain Eloquent builder with no joins or aggregates. Wire one endpoint:

// routes/web.php or routes/api.php
Route::get('/admin/users/table', [UserTableController::class, 'index'])
    ->middleware(['auth', 'can:viewAny,App\Models\User']);
class UserTableController extends Controller
{
    public function index()
    {
        $query = User::query()
            ->where('tenant_id', auth()->user()->tenant_id); // scoping is YOUR job

        return PrimevueDatatables::of($query)
            ->columns([
                'id', 'name', 'email', 'created_at',
            ])
            ->make();
    }
}

(The builder's exact class name and method chain should be taken from the published scaffolding — the shape is model/query in, column map applied, JSON out.)

On the frontend, mount a DataTable in lazy mode with :value, :lazy="true", :totalRecords, v-model:filters, and @page / @sort / @filter handlers that update the query params. Get one column sorting and one text filter round-tripping end to end before you expand columns. That single vertical slice proves the contract, and almost every problem you will hit later is a contract mismatch — not a query bug.

Implementation Patterns

Keep the controller a thin adapter. The controller's only jobs are: build a scoped query, declare the column map, return the payload. Anything that looks like business logic belongs in the model, a scope, or a query object. This is also your swap seam — when the package stalls, you replace one file per table, not a hundred.

Drive everything from an explicit column definition. The single most valuable pattern with this package is a per-model table definition class that both the index endpoint and your tests consume:

class UserTableDefinition
{
    public static function columns(): array
    {
        return [
            'id'         => ['sortable' => true,  'searchable' => false],
            'name'       => ['sortable' => true,  'searchable' => true],
            'email'      => ['sortable' => true,  'searchable' => true],
            'status'     => ['sortable' => true,  'searchable' => true],
            'created_at' => ['sortable' => true,  'searchable' => false],
        ];
    }
}

Declare sortable and filterable columns positively. Never accept a sortField and pass it straight through — the request is user-controlled and lands in orderBy/where clauses.

Apply authorization and tenant scoping before the package sees the query. The package builds queries; it does not enforce policy. Pass in an already-scoped builder:

$query = User::query()
    ->where('tenant_id', $tenant->id)
    ->when(! auth()->user()->isAdmin(), fn ($q) => $q->where('team_id', auth()->user()->team_id));

Add a comment saying so, because the next developer will assume a package called "datatables" handles row visibility. It does not.

Use select aliases for computed columns. Anything you want sortable or filterable must exist as a column in the SQL result. If the UI shows full_name or order_total, build it with selectRaw/withSum and alias it so the package can sort on it:

$query->select('users.*')
      ->selectRaw("CONCAT(users.first_name, ' ', users.last_name) as full_name")
      ->withSum('orders', 'total')
      ->selectRaw('COALESCE(orders_sum_total, 0) as order_total');

Normalize the request mapping in one place. PrimeVue's contract is not quite Laravel's. sortOrder arrives as 1/-1, pagination may arrive as page/rows or first/rows depending on the PrimeVue version, and filter values arrive as objects with value and matchMode (and a constraints array for date ranges). Put that translation in a small request object or middleware so it is written once and testable:

$sortDir = (int) $request->input('sortOrder') === -1 ? 'desc' : 'asc';
$perPage = min((int) $request->input('per_page', 15), 100); // always cap

Cap page size hard. rows/per_page comes from the client. An uncapped value is a free way for someone to ask your database for 500,000 records.

Shape the response with API Resources where you need control. If you need to transform values (dates, enums, currency) rather than emit raw model attributes, either map through a Resource collection or do the formatting on the frontend. Doing half of it server-side and half client-side is where inconsistency bugs breed.

Test the contract, not the package. Feature tests should assert your endpoint's behavior: "sorting by name descending orders results", "filtering email contains foo returns the matching row", "JSON metadata column settings.plan filters case-insensitively", "page 2 has the right records", "a tenant scoping test returns 403/empty for another tenant". These tests are what let you pick up a v1.6.0 or a fork without re-reading the source.

Gotchas and Tips

The JSON case-insensitive filter (v1.5.0) is a behavior change and a performance risk. It is genuinely useful — exposing nested metadata fields in a list view goes from a custom query project to an afternoon. But LOWER()-style comparisons on JSON paths can force full scans and behave differently across MySQL, PostgreSQL, and SQLite. Test it against production-like row counts on your actual driver, run EXPLAIN, and add expression indexes where your driver supports them. Restrict which JSON paths are filterable in the column definition rather than exposing the whole document.

Every pitfall here is a whitelist pitfall. If a column is not in your explicit sortable/filterable map, it should not reach orderBy or where. This is the one discipline the package cannot give you, and it is the one that becomes a vulnerability when it is skipped.

sortField naming is a silent failure mode. PrimeVue dataKeys are whatever you typed in the Vue template; database columns are snake_case. A mismatch does not throw — it usually returns unsorted data. If sorting "does nothing," check the field name before you check the query.

Empty and null filters. PrimeVue sends filters: {} when nothing is applied, and individual filter entries can be { value: null, matchMode: 'contains' }. The backend must drop those rather than generate where email like '%%' — which is harmless but also means % in a user's search string is a wildcard. Decide whether you escape wildcards.

Date filters have a different shape. PrimeVue date filters use matchMode values like dateIs, dateAfter, dateBefore, and range filters arrive as a constraints array rather than a single value. Budget an hour to normalize these into whereDate/whereBetween calls; it is the most common source of "the date filter is off by a day" tickets, especially with timezones.

Debug the payload, then the SQL. When something is wrong, dump the raw request ($request->all()) and log the generated query with DB::listen(). Nine times out of ten the fix is on the request-normalization side, not in the package. Do not start by reading vendor source.

Extend via the published config and your own definition classes — do not patch vendor/. You will likely need a fork eventually. Keep that fork small by putting your customizations in your app: column definitions, a base controller, request normalization middleware. If you do fork, keep it as a VCS repository in composer.json with a branch alias, and keep the diff to upstream minimal so you can rebase.

Know where the package boundary is and price it honestly. It does not do exports, saved views, column-level permissions, complex joins, aggregations, or row-level policy. Each of those is a real ticket. Discover that before the sprint commitment, not during it — an export that reuses the datatable's filter state usually needs its own query path, and pretending otherwise produces two divergent filter implementations.

Pin, monitor, and name an owner. There has been no release since July 2023, there are two first-time contributors on the last release, and zero dependent packages to lean on for production signal. Assign a person to watch the repository, write down the fork-or-replace contingency, and re-evaluate at the next Laravel major. That is a ten-minute task now and an unbounded one during an upgrade crunch.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route