foxbytehq/laravel-inertia-dataproviders
Laravel Inertia Data Providers lets you organize page props into reusable provider classes, keeping Inertia responses clean and predictable. Define and compose structured data from Laravel controllers to your frontend with less boilerplate.
v4.0.0 is a Laravel 13 compatibility release — the package's surface API is unchanged from 3.x, so if you have used it before, the upgrade is mostly a dependency bump plus a much larger test suite behind it. If you are new to the package, the mental model is simple: a DataProvider is a class that describes how to fetch one index/listing dataset (model, columns, searchable fields, sortable fields, default sort, page size, filters), and the package turns that description into an Inertia prop plus a matching frontend table contract.
Minimal steps:
composer require foxbytehq/laravel-inertia-dataproviders and publish the config (php artisan vendor:publish — the config file controls defaults such as per-page size and the query-string keys used for page, sort, filter, search).php artisan make:data-provider UserDataProvider --model=User. Read the generated stub first — it is the authoritative reference for the exact property/method names in your installed version, and it is faster than guessing from the docs.?sort=-created_at&search=foo&page=2 to confirm the provider — not your controller — is driving the query.First use case: replace a hand-rolled User::query()->when($request->search, ...)->paginate()->withQueryString() with a provider. That is a ~10-line controller action collapsed to one line, and it is the highest-value place to start because it exercises search, sort, and pagination together.
One provider per listing, not per model. A UserDataProvider for the admin user list and a ActiveUserDataProvider for a marketing export are different classes even though they share a model. This keeps the "what is allowed to be searched/sorted" contract explicit and avoids the classic bug where a filter added for the admin screen silently changes the public page.
Override query() for scoping, not for column selection. The idiomatic place for where('tenant_id', ...), policy scoping, eager loads (->with('team')), and select() narrowing is the provider's query builder hook. Keep the declarative properties for the frontend contract and the query method for authorization and performance. This split is what makes the provider testable and reusable.
Scope by tenancy/policy inside the provider, always. Because the provider reads sort/filter/search straight off the request, an unscoped provider is an enumeration risk: ?filter[team_id]=7 must not be able to reach another tenant. Either restrict the filterable set per-request in a constructor/boot-style method, or reject unknown filter keys (most setups do this by whitelisting filters explicitly rather than accepting arbitrary query-string keys).
Sorting and searching across relations needs explicit column mapping. $sortable = ['team' => 'teams.name'] style mappings let the frontend send a friendly key while the provider applies a joined column. The moment you allow sorting by a related column you need the join() in query(); forgetting it produces a "no such column" error only in production because your test data happened not to sort by it.
Compose with actions, not inheritance. For multi-step filters (date ranges, status enums, archived-toggles) prefer small query scopes on the model and call them from the provider's query(). Subclassing a provider to add one filter gets unmaintainable quickly and breaks the frontend's expectations about which keys exist.
Test the provider directly, not through HTTP. $provider->toArray() (or the equivalent resolve call) is a plain method — instantiate it with a fake request, assert the paginated payload shape, the applied sort, and that an unauthorized filter key is ignored. This is much cheaper than a full Inertia request test and is exactly where the v4 test suite upgrade helps you: the package now demonstrably handles the query-string permutations, so your tests can focus on your scoping rules rather than on the plumbing.
Keep the prop name stable across pages. Frontend components from the companion JS package expect a specific payload shape (data rows, meta/pagination, sort state, applied filters). Renaming the prop or wrapping the provider output in a transformer will break the table silently — the component renders empty rather than erroring.
v4 is a Laravel 13-only release. The major bump is the Laravel 13 upgrade; combined with a "comprehensive test suite" PR it strongly implies dropped support for older framework versions. If you are on Laravel 11 or 12, do not let composer update float you to ^4.0 — pin "foxbytehq/laravel-inertia-dataproviders": "^3.3" and upgrade the framework first. Check the changelog/composer.json constraints before assuming, since the release note only mentions the Laravel 13 work.
Verify the PHP floor too. Laravel 13 raises the minimum PHP version; a v4 upgrade can fail in CI on a matrix entry that still runs an older PHP. This is the most common "it worked locally" failure for releases like this one.
Because the package API is unchanged, treat v4 as a framework-version lock, not a feature upgrade. Budget the change as: bump Laravel, bump the package, run your provider tests, then review nothing else. If you were hoping for new provider features, they are not here.
The new test suite is a confidence signal, not a guarantee. A first comprehensive suite on a 4.0 release often means previously-untested edge cases (nested filter arrays, empty search strings, sorting by a column the user cannot see) are now pinned to whatever the maintainers decided. Re-run your own provider tests after upgrading and diff any payload-shape assertions — that is where a quiet behavioural fix would surface.
Debugging empty tables: log the actual SQL. The failure mode is almost always (a) searchable columns declared but the search term being applied to a joined column that isn't selected, (b) a sort key the frontend sends that isn't in the sortable whitelist and is therefore dropped, or (c) per_page defaults resolving to 0 or null from config. Dump the toArray() payload first; it tells you which of the three it is in one step.
Config quirks: the query-string parameter names (page, sort, search, filter[...]) are configurable, and the frontend package assumes the defaults. If you customise them in config/inertia-dataproviders.php, you must pass the matching names to the JS component or every interaction will silently no-op.
Extension points to prefer: the provider's query hook for scoping, model query scopes for reusable filters, and a base DataProvider subclass in your app for cross-cutting defaults (per-page size, standard date filter). Avoid overriding the package's internal resolution methods — that is the surface most likely to shift between majors, and it is exactly what the new test suite in v4 now pins down.
Pagination plus large tables: the provider paginates by default, so SELECT * on a million-row table still hurts when the sort column is unindexed. Add indexes for every column in your sortable list before shipping; the package cannot do that for you.
How can I help you explore Laravel packages today?