PHPackages                             ghostcompiler/laravel-querybuilder - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. ghostcompiler/laravel-querybuilder

ActiveLibrary[Database &amp; ORM](/categories/database)

ghostcompiler/laravel-querybuilder
==================================

Reusable query builder helpers for Eloquent APIs on Laravel 10 through 13.

v1.0.4(1mo ago)1302MITPHPPHP ^8.1CI passing

Since Apr 19Pushed yesterdayCompare

[ Source](https://github.com/ghostcompiler/laravel-querybuilder)[ Packagist](https://packagist.org/packages/ghostcompiler/laravel-querybuilder)[ Docs](https://github.com/ghostcompiler/laravel-querybuilder)[ RSS](/packages/ghostcompiler-laravel-querybuilder/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (6)Dependencies (16)Versions (7)Used By (0)

Laravel Query Builder
=====================

[](#laravel-query-builder)

 [![Laravel Uploads Logo](https://camo.githubusercontent.com/7a311440d646263aeef835cc3b8d3f30b2c35f8fbf32c26ecf9a971070371ca9/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f646a6776666c3174762f696d6167652f75706c6f61642f76313738303636363739312f6c6f676f5f6d716e716e342e706e67)](https://camo.githubusercontent.com/7a311440d646263aeef835cc3b8d3f30b2c35f8fbf32c26ecf9a971070371ca9/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f646a6776666c3174762f696d6167652f75706c6f61642f76313738303636363739312f6c6f676f5f6d716e716e342e706e67)

 [![Laravel](https://camo.githubusercontent.com/04ac196cc9d1ede920f94387cae5075133cbd568d7b69f7b11f953f0e45e1281/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3130253230746f25323031332d4646324432303f7374796c653d666f722d7468652d6261646765266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/04ac196cc9d1ede920f94387cae5075133cbd568d7b69f7b11f953f0e45e1281/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3130253230746f25323031332d4646324432303f7374796c653d666f722d7468652d6261646765266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465) [![PHP](https://camo.githubusercontent.com/0b828fae3809f5692b3303526d43b126732078cc61f176b8f877332096c98bc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d3737374242343f7374796c653d666f722d7468652d6261646765266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/0b828fae3809f5692b3303526d43b126732078cc61f176b8f877332096c98bc0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e312532422d3737374242343f7374796c653d666f722d7468652d6261646765266c6f676f3d706870266c6f676f436f6c6f723d7768697465) [![Laravel Storage](https://camo.githubusercontent.com/1aebc286c490f94bc84e66b992db5cd8503c5ba61c08450301ad092e1fa3cd02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230576974682d4c61726176656c25323053746f726167652d3046313732413f7374796c653d666f722d7468652d6261646765)](https://camo.githubusercontent.com/1aebc286c490f94bc84e66b992db5cd8503c5ba61c08450301ad092e1fa3cd02/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275696c74253230576974682d4c61726176656c25323053746f726167652d3046313732413f7374796c653d666f722d7468652d6261646765)

> A Laravel package for API-ready Eloquent query building with searchable fields, nested relation filters, relation sorting, strict mode, custom filters, pagination helpers, and safer public query interfaces.

Overview
--------

[](#overview)

Laravel Query Builder helps API endpoints safely accept request query parameters for filtering, sorting, relation includes, sparse fieldsets, pagination, and table-style responses.

The package is designed around explicit allow lists. A request cannot filter, sort, include, or select fields unless your model, schema, or fluent query definition permits it.

Use it when you want:

- schema-driven filtering, sorting, includes, and fields
- tenant-aware query scoping
- safe JSON:API-style query strings
- strict rejection of unknown filters, sorts, includes, and fields
- relation include validation
- optional policy-aware relation includes
- sensitive column masking before serialization
- custom filter classes and callbacks
- legacy trait-based helpers for existing models
- pagination metadata formatted for API responses

Quick Links
-----------

[](#quick-links)

- [Installation](#installation)
- [Fluent API Quick Start](#fluent-api-quick-start)
- [Schema Classes](#schema-classes)
- [JSON:API Query Syntax](#jsonapi-query-syntax)
- [Legacy Trait API](#legacy-trait-api)
- [Configuration](#configuration)
- [Security Model](#security-model)
- [Function Reference](#function-reference)
- [Functions Index](FUNCTIONS.md)

Compatibility
-------------

[](#compatibility)

- Laravel 10
- Laravel 11
- Laravel 12
- Laravel 13
- PHP 8.1+

Installation
------------

[](#installation)

Install from Packagist:

```
composer require ghostcompiler/laravel-querybuilder
```

Laravel package discovery registers the service provider automatically.

To publish the legacy config filename:

```
php artisan vendor:publish --tag=query-builder-config
```

To publish the modern config filename:

```
php artisan vendor:publish --tag=querybuilder-config
```

Both config files contain the same defaults. The package reads both `query-builder` and `querybuilder` config keys for compatibility.

### Local Path Install

[](#local-path-install)

If you are developing the package locally inside another Laravel app:

```
{
    "repositories": [
        {
            "type": "path",
            "url": "/absolute/path/to/laravel-querybuilder",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "ghostcompiler/laravel-querybuilder": "*"
    }
}
```

Then run:

```
composer update ghostcompiler/laravel-querybuilder
```

Fluent API Quick Start
----------------------

[](#fluent-api-quick-start)

The fluent API is the recommended interface for new code.

```
use GhostCompiler\LaravelQueryBuilder\Query;

Route::get('/users', function () {
    return Query::for(User::class)
        ->tenantScoped()
        ->schema(UserSchema::class)
        ->allowedIncludes()
        ->allowedFilters()
        ->allowedSorts()
        ->paginate();
});
```

Example request:

```
/users?filter[name]=john&include=roles.permissions&sort=-created_at&fields[users]=id,name,email&page[number]=1&page[size]=15

```

The fluent API runs in strict mode for its runtime definition. Unknown filters, sorts, includes, and disallowed field requests throw typed query-builder exceptions.

Schema Classes
--------------

[](#schema-classes)

Schemas centralize the public query contract for a model.

```
use GhostCompiler\LaravelQueryBuilder\QuerySchema;

class UserSchema extends QuerySchema
{
    public function filters(): array
    {
        return ['name', 'email'];
    }

    public function sorts(): array
    {
        return ['created_at', 'name'];
    }

    public function includes(): array
    {
        return ['roles.permissions', 'profile'];
    }

    public function fields(): array
    {
        return ['id', 'name', 'email'];
    }
}
```

Use the schema:

```
Query::for(User::class)
    ->schema(UserSchema::class)
    ->paginate();
```

Schema instances are cached by class name during the request lifecycle.

Allowed Filters
---------------

[](#allowed-filters)

You can define filters in a schema:

```
use GhostCompiler\LaravelQueryBuilder\Filters\AllowedFilter;

class UserSchema extends QuerySchema
{
    public function filters(): array
    {
        return [
            'email',
            AllowedFilter::exact('status'),
            AllowedFilter::partial('name'),
            AllowedFilter::scope('active'),
            AllowedFilter::custom('high_score', HighScoreFilter::class),
        ];
    }
}
```

Or define filters directly:

```
Query::for(User::class)
    ->allowedFilters([
        'email',
        AllowedFilter::partial('name'),
        'active' => ActiveUsersFilter::class,
    ])
    ->get();
```

Supported filter styles:

- `AllowedFilter::exact('status')`
- `AllowedFilter::partial('name')`
- `AllowedFilter::scope('active')`
- `AllowedFilter::custom('active', ActiveUsersFilter::class)`
- string shorthand: `'email'`
- array shorthand: `'name' => 'partial'`
- class shorthand: `'active' => ActiveUsersFilter::class`

Query examples:

```
/users?filter[email]=alice@example.com
/users?filter[name]=ali
/users?filter[active]=true

```

Custom Filter Classes
---------------------

[](#custom-filter-classes)

Create a filter class:

```
use GhostCompiler\LaravelQueryBuilder\Contracts\Filter;
use Illuminate\Database\Eloquent\Builder;

class ActiveUsersFilter implements Filter
{
    public function apply(Builder $query, mixed $value)
    {
        return $query->where('active', filter_var($value, FILTER_VALIDATE_BOOL));
    }
}
```

Register it:

```
Query::for(User::class)
    ->allowedFilters([
        'active' => ActiveUsersFilter::class,
    ])
    ->get();
```

Custom filters are trusted code. Avoid raw SQL with unsanitized request values.

Allowed Sorts
-------------

[](#allowed-sorts)

Schema example:

```
public function sorts(): array
{
    return ['name', 'created_at'];
}
```

Direct example:

```
Query::for(User::class)
    ->allowedSorts(['name', 'created_at'])
    ->get();
```

Query examples:

```
/users?sort=name
/users?sort=-created_at
/users?sort=name,-created_at

```

The package rejects non-allow-listed sort fields with `InvalidSortException` in strict mode.

Allowed Includes
----------------

[](#allowed-includes)

Schema example:

```
public function includes(): array
{
    return ['profile', 'roles.permissions'];
}
```

Direct example:

```
Query::for(User::class)
    ->allowedIncludes(['profile', 'roles.permissions'])
    ->get();
```

Query example:

```
/users?include=profile,roles.permissions

```

Includes are validated against:

- the explicit include allow list
- relation existence on the model
- configured maximum include depth
- optional `viewRelation` gate policy checks

Policy-Aware Includes
---------------------

[](#policy-aware-includes)

If a `viewRelation` gate ability is defined, the package checks it before eager loading a requested include:

```
use Illuminate\Support\Facades\Gate;

Gate::define('viewRelation', function ($user, $model, string $relation) {
    return $relation !== 'roles.permissions' || $user->can('viewPermissions');
});
```

If the gate denies the relation:

- strict includes enabled: `UnauthorizedRelationException`
- strict includes disabled: relation is skipped silently

If no `viewRelation` ability exists, the package assumes route/controller/model authorization is handled by the application.

Sparse Fieldsets
----------------

[](#sparse-fieldsets)

Schema example:

```
public function fields(): array
{
    return ['id', 'name', 'email'];
}
```

Request example:

```
/users?fields[users]=id,name,email

```

Sparse fieldsets are validated against allowed fields and masked columns. The model primary key is automatically kept when needed.

Column Masking
--------------

[](#column-masking)

Configure sensitive columns:

```
'masked_columns' => [
    'users' => ['password', 'remember_token'],
    'oauth_clients' => ['secret'],
],
```

When `mask_sensitive_columns` is enabled, matching attributes are hidden before serialization, including loaded relations.

Masked columns are also removed from selectable sparse fieldsets.

Tenant Scoping
--------------

[](#tenant-scoping)

Tenant scoping is enabled by default for the fluent API if the model table has the configured tenant column.

```
Query::for(User::class)
    ->tenantScoped()
    ->schema(UserSchema::class)
    ->get();
```

This applies:

```
where('tenant_id', auth()->user()->tenant_id)
```

Disable it for a trusted system query:

```
Query::for(User::class)
    ->tenantScoped(false)
    ->schema(UserSchema::class)
    ->get();
```

Use a custom tenant column:

```
Query::for(User::class)
    ->tenantScoped(true, 'account_id')
    ->schema(UserSchema::class)
    ->get();
```

Or configure it globally:

```
'tenant_column' => 'account_id',
```

JSON:API Query Syntax
---------------------

[](#jsonapi-query-syntax)

The fluent API normalizes JSON:API-style request parameters:

```
filter[name]=john
include=roles.permissions
sort=-created_at
fields[users]=id,name,email
page[number]=1
page[size]=15

```

Equivalent normalized structure:

```
[
    'filters' => ['name' => 'john'],
    'with' => ['roles.permissions'],
    'sort_by' => ['created_at'],
    'sort_dir' => ['desc'],
    'fields' => ['users' => ['id', 'name', 'email']],
    'page' => 1,
    'per_page' => 15,
]
```

Legacy parameter names are still supported:

```
filters[name]=john
with=roles.permissions
sort_by=created_at
sort_dir=desc
columns=id,name,email
page=1
per_page=15

```

Pagination
----------

[](#pagination)

The fluent `paginate()` method returns a JSON-friendly array:

```
$payload = Query::for(User::class)
    ->schema(UserSchema::class)
    ->paginate();
```

Shape:

```
[
    'data' => [...],
    'meta' => [
        'total' => 50,
        'per_page' => 15,
        'current_page' => 1,
        'last_page' => 4,
    ],
    'links' => [
        'first' => '...',
        'last' => '...',
        'prev' => null,
        'next' => '...',
    ],
]
```

Override per-page:

```
Query::for(User::class)
    ->schema(UserSchema::class)
    ->paginate(25);
```

Query Cache
-----------

[](#query-cache)

Cache a terminal operation:

```
Query::for(User::class)
    ->schema(UserSchema::class)
    ->cache(60)
    ->paginate();
```

The cache key includes the operation, model, request parameters, and runtime definition.

Fluent Terminal Methods
-----------------------

[](#fluent-terminal-methods)

```
Query::for(User::class)->schema(UserSchema::class)->get();
Query::for(User::class)->schema(UserSchema::class)->first();
Query::for(User::class)->schema(UserSchema::class)->paginate();
Query::for(User::class)->schema(UserSchema::class)->toEloquentBuilder();
```

`get()`, `first()`, and `paginate()` execute the query. `toEloquentBuilder()` applies the request rules and returns the underlying Eloquent builder for further trusted server-side work.

Legacy Trait API
----------------

[](#legacy-trait-api)

Existing projects can keep using the model trait.

```
use GhostCompiler\LaravelQueryBuilder\Concerns\HasQueryBuilder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasQueryBuilder;

    protected array $searchable = ['name', 'email', 'profile.bio'];
    protected array $filterable = ['status', 'roles.name'];
    protected array $sortable = ['name', 'created_at', 'profile.city'];
    protected array $selectable = ['id', 'name', 'email'];
    protected array $allowedRelations = ['profile', 'roles.permissions'];
}
```

Static builder:

```
$query = User::QueryBuild($request);
```

Local scope:

```
$query = User::query()->queryBuilder($request);
```

Paginator:

```
$paginator = User::query()->queryBuilder($request)->paginateQuery();
```

Table payload:

```
$payload = User::query()->queryBuilder($request)->paginateTable();
```

The legacy trait API uses model properties and `query-builder.strict_mode` to decide whether invalid input throws or is ignored.

Legacy Model Options
--------------------

[](#legacy-model-options)

Supported model properties:

```
protected array $searchable = ['name', 'email', 'profile.bio'];
protected array $filterable = ['status', 'roles.name'];
protected array $sortable = ['name', 'created_at'];
protected array $selectable = ['id', 'name', 'email'];
protected array $allowedRelations = ['profile', 'roles.permissions'];
protected array $allowedFilterOperators = [
    'status' => ['=', 'in'],
];
protected array $dateFilterable = ['created_at'];
protected array $customFilters = [
    'high_score' => 'applyHighScoreFilter',
];
protected string $defaultSortBy = 'created_at';
protected string $defaultSortDir = 'desc';
protected int $defaultPerPage = 15;
protected int $maxPerPage = 100;
protected bool $queryBuilderStrict = true;
```

Custom model callback:

```
protected function applyHighScoreFilter($query, mixed $value): void
{
    if (filter_var($value, FILTER_VALIDATE_BOOL)) {
        $query->where('score', '>=', 90);
    }
}
```

Legacy Request Parameters
-------------------------

[](#legacy-request-parameters)

ParameterExamplePurpose`search``?search=alice`Global search across `$searchable`.`filters``?filters[status]=active`Allow-listed field filters.`sort_by``?sort_by=created_at`Allow-listed sort fields.`sort_dir``?sort_dir=desc`Sort direction.`with``?with=profile,roles`Allow-listed eager loads.`columns``?columns=id,name,email`Allow-listed selected columns.`page``?page=2`Page number.`per_page``?per_page=25`Page size, capped by config/model max.`date_from``?date_from=2025-01-01`Start date boundary.`date_to``?date_to=2025-12-31`End date boundary.`date_column``?date_column=created_at`Date column, validated by allow list.`trashed``?trashed=with`Soft-delete handling: `with` or `only`.Filter Operators
----------------

[](#filter-operators)

Legacy operator syntax:

```
filters[score][operator]=>=
filters[score][value]=90

```

Supported operators:

- `=`
- `!=`
- ``
- `=`
- `like`
- `not_like`
- `in`
- `not_in`
- `between`
- `null`
- `not_null`

Restrict operators per field:

```
protected array $allowedFilterOperators = [
    'status' => ['=', 'in'],
    'score' => ['>=', 'between'],
];
```

Header-Based Query Input
------------------------

[](#header-based-query-input)

Enable headers:

```
'query_headers' => [
    'enabled' => true,
    'override_request_values' => true,
],
```

Example headers:

```
X-Query-Search: Alice
X-Query-Filter: {"status":"active"}
X-Query-Sort: created_at
X-Query-Sort-Dir: desc
X-Query-With: profile
X-Query-Per-Page: 15

```

Header values are normalized and validated through the same engine as query-string parameters.

Configuration
-------------

[](#configuration)

Default config:

```
return [
    'strict_mode' => false,
    'deny_unknown_filters' => true,
    'deny_unknown_sorts' => true,
    'deny_unknown_includes' => true,
    'handle_request_automatically' => true,
    'tenant_scoping_enabled' => true,
    'tenant_column' => 'tenant_id',
    'mask_sensitive_columns' => true,
    'masked_columns' => [],
    'strict_includes' => true,
    'policy_aware_includes' => true,
    'query_headers' => [
        'enabled' => false,
        'override_request_values' => true,
        'names' => [
            'search' => ['X-Query-Search'],
            'filters' => ['X-Query-Filters', 'X-Query-Filter'],
            'sort_by' => ['X-Query-Sort-By', 'X-Query-Sort'],
            'sort_dir' => ['X-Query-Sort-Dir'],
            'page' => ['X-Query-Page'],
            'per_page' => ['X-Query-Per-Page'],
            'date_from' => ['X-Query-Date-From'],
            'date_to' => ['X-Query-Date-To'],
            'date_column' => ['X-Query-Date-Column'],
            'columns' => ['X-Query-Columns'],
            'with' => ['X-Query-With', 'X-Query-Include', 'X-Query-Includes'],
            'trashed' => ['X-Query-Trashed'],
        ],
    ],
    'response' => [
        'status_key' => 'status',
        'status_value' => true,
        'message_key' => 'message',
    ],
    'search_like_mode' => 'contains',
    'filter_like_mode' => 'contains',
    'default_per_page' => 15,
    'max_per_page' => 100,
    'default_sort_direction' => 'asc',
    'min_search_length' => 3,
    'max_filter_count' => 15,
    'max_filter_value_count' => 100,
    'max_relation_depth' => 3,
    'max_include_depth' => 3,
    'cache_prefix' => 'laravel-querybuilder',
];
```

Important options:

- `strict_mode`: makes the legacy trait API throw on invalid query input.
- `tenant_scoping_enabled`: enables default tenant scoping in the fluent API when the tenant column exists.
- `tenant_column`: tenant column used with `auth()->user()`.
- `mask_sensitive_columns`: hides configured columns before serialization.
- `masked_columns`: table or model keyed sensitive columns.
- `strict_includes`: controls whether policy-denied includes throw or are skipped.
- `policy_aware_includes`: enables `Gate::allows('viewRelation', [$model, $relation])`.
- `max_include_depth`: caps dotted include depth.
- `max_relation_depth`: legacy relation-depth cap for dotted relation paths.
- `cache_prefix`: prefix for fluent query cache keys.

Exception Types
---------------

[](#exception-types)

All package exceptions extend `QueryBuilderException`.

ExceptionWhen it is used`InvalidFilterException`Unknown filter, unsupported operator, invalid filter shape, or disallowed filter.`InvalidIncludeException`Unknown, disallowed, or missing relation include.`InvalidSortException`Unknown or disallowed sort field/direction.`UnauthorizedRelationException``viewRelation` gate denies an include in strict include mode.`IncludeDepthExceededException`Requested include exceeds configured depth.`InvalidQueryBuilderQuery`General invalid query-builder input.Read validation errors:

```
try {
    Query::for(User::class)->schema(UserSchema::class)->get();
} catch (InvalidQueryBuilderQuery $exception) {
    return response()->json([
        'errors' => $exception->errors(),
    ], 422);
}
```

Security Model
--------------

[](#security-model)

The package is secure-by-default for query shaping in the fluent API:

- filters must be allow-listed
- sorts must be allow-listed
- includes must be allow-listed
- sparse fieldsets must be allow-listed
- masked columns are hidden before serialization
- nested includes are depth-limited
- requested includes must exist as model relations
- optional policy checks can block specific relations
- standard values are passed through Eloquent/PDO bindings

The package does not replace:

- user authentication
- route middleware
- controller authorization
- model policies
- business-specific data visibility rules

Custom filters are application code. Keep them parameterized and avoid unsafe raw SQL.

Performance Notes
-----------------

[](#performance-notes)

- Include only relations that are needed by the endpoint.
- Index columns used by filters and sorts.
- Keep `max_include_depth` low for public APIs.
- Use sparse fieldsets to reduce payload size.
- Use cache only for endpoints where user, tenant, and permission context are safe to cache.
- Prefer schema classes for stable public query contracts.

Function Reference
------------------

[](#function-reference)

The full clickable method list is also available in [FUNCTIONS.md](FUNCTIONS.md).

### `Query::for()`

[](#queryfor)

Create a fluent query builder for a model class or an existing Eloquent builder.

```
Query::for(User::class);
Query::for(User::query()->where('active', true));
```

### `Query::extend()`

[](#queryextend)

Register or read extension values by contract name.

```
Query::extend(Filter::class, ActiveUsersFilter::class);
$extensions = Query::extend(Filter::class);
```

### `schema()`

[](#schema)

Attach a `QuerySchema` class or instance.

```
Query::for(User::class)->schema(UserSchema::class);
```

### `allowedFilters()`

[](#allowedfilters)

Override schema filters directly.

```
Query::for(User::class)->allowedFilters(['email', AllowedFilter::partial('name')]);
```

### `allowedSorts()`

[](#allowedsorts)

Override schema sorts directly.

```
Query::for(User::class)->allowedSorts(['name', 'created_at']);
```

### `allowedIncludes()`

[](#allowedincludes)

Override schema includes directly.

```
Query::for(User::class)->allowedIncludes(['profile', 'roles.permissions']);
```

### `allowedFields()`

[](#allowedfields)

Override schema sparse fieldset columns directly.

```
Query::for(User::class)->allowedFields(['id', 'name', 'email']);
```

### `tenantScoped()`

[](#tenantscoped)

Enable, disable, or customize tenant isolation.

```
Query::for(User::class)->tenantScoped();
Query::for(User::class)->tenantScoped(false);
Query::for(User::class)->tenantScoped(true, 'account_id');
```

### `request()`

[](#request)

Provide request input manually.

```
Query::for(User::class)->request(request());
Query::for(User::class)->request(['filter' => ['name' => 'Alice']]);
```

### `cache()`

[](#cache)

Cache a terminal query operation for the given number of seconds.

```
Query::for(User::class)->schema(UserSchema::class)->cache(60)->paginate();
```

### `toEloquentBuilder()`

[](#toeloquentbuilder)

Apply request rules and return the underlying Eloquent builder.

```
$builder = Query::for(User::class)->schema(UserSchema::class)->toEloquentBuilder();
```

### `get()`

[](#get)

Execute and return an Eloquent collection.

```
$users = Query::for(User::class)->schema(UserSchema::class)->get();
```

### `first()`

[](#first)

Execute and return the first model or `null`.

```
$user = Query::for(User::class)->schema(UserSchema::class)->first();
```

### `paginate()`

[](#paginate)

Execute and return a JSON-friendly pagination array.

```
$payload = Query::for(User::class)->schema(UserSchema::class)->paginate();
$payload = Query::for(User::class)->schema(UserSchema::class)->paginate(25);
```

### `AllowedFilter::exact()`

[](#allowedfilterexact)

Allow exact matching for a filter.

```
AllowedFilter::exact('status');
```

### `AllowedFilter::partial()`

[](#allowedfilterpartial)

Allow partial `LIKE` matching for a filter.

```
AllowedFilter::partial('name');
```

### `AllowedFilter::scope()`

[](#allowedfilterscope)

Call an Eloquent local scope.

```
AllowedFilter::scope('active');
AllowedFilter::scope('active_users', 'active');
```

### `AllowedFilter::custom()`

[](#allowedfiltercustom)

Use a custom filter class, instance, callback, or callable.

```
AllowedFilter::custom('active', ActiveUsersFilter::class);
```

### `Filter::apply()`

[](#filterapply)

Implement a custom filter.

```
public function apply(Builder $query, mixed $value)
{
    return $query->where('active', true);
}
```

### `QuerySchema` Methods

[](#queryschema-methods)

Define allowed query surface.

```
public function filters(): array;
public function sorts(): array;
public function includes(): array;
public function fields(): array;
public function filterOperators(): array;
public function customFilters(): array;
```

### `QueryBuild()`

[](#querybuild)

Legacy static trait entry point.

```
$query = User::QueryBuild($request);
```

### `queryBuilder()`

[](#querybuilder)

Legacy local scope.

```
$query = User::query()->queryBuilder($request);
```

### `paginateQuery()`

[](#paginatequery)

Legacy paginator scope.

```
$paginator = User::query()->queryBuilder($request)->paginateQuery();
```

### `paginateTable()`

[](#paginatetable)

Legacy table response scope.

```
$payload = User::query()->queryBuilder($request)->paginateTable();
```

### `InvalidQueryBuilderQuery::errors()`

[](#invalidquerybuilderqueryerrors)

Read validation error details from a thrown query-builder exception.

```
$errors = $exception->errors();
```

### `QueryBuilderEngine` Methods

[](#querybuilderengine-methods)

Advanced service-level entry points used internally by the fluent and trait APIs.

```
app(QueryBuilderEngine::class)->apply($builder, $request);
app(QueryBuilderEngine::class)->applyWithDefinition($builder, $request, $definition);
app(QueryBuilderEngine::class)->paginate($builder, $request);
app(QueryBuilderEngine::class)->paginateTable($builder, $request);
```

Testing
-------

[](#testing)

Run tests:

```
composer test
```

Run formatting:

```
composer format
```

Run lint checks:

```
composer lint
```

Run static analysis:

```
composer stan
```

Run the full quality suite:

```
composer quality
```

Optional PostgreSQL test runs can be configured through environment variables:

```
TEST_DB_CONNECTION=pgsql
TEST_DB_HOST=127.0.0.1
TEST_DB_PORT=5432
TEST_DB_DATABASE=laravel_querybuilder_test
TEST_DB_USERNAME=postgres
TEST_DB_PASSWORD=secret
composer test
```

Quality and Security
--------------------

[](#quality-and-security)

Additional package docs:

- [Development Guide](DEVELOPMENT.md)
- [Quality Guide](QUALITY.md)
- [Security Policy](SECURITY.md)

License
-------

[](#license)

MIT

Development And Build Environment
---------------------------------

[](#development-and-build-environment)

This package was developed using **ServBay** as the local development environment.

### Development Tool Used

[](#development-tool-used)

- Local development tool: `ServBay`
- Website: [www.servbay.com](https://www.servbay.com/)

### ServBay your development friend

[](#servbay-your-development-friend)

 [![ServBay Icon](https://camo.githubusercontent.com/105428b77c86ff428ed63af91241f05b5992274ef8e9d16b233c1ca83d9c781d/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f646a6776666c3174762f696d6167652f75706c6f61642f76313738303636373036332f736572766261795f656463376a7a2e706e67)](https://camo.githubusercontent.com/105428b77c86ff428ed63af91241f05b5992274ef8e9d16b233c1ca83d9c781d/68747470733a2f2f7265732e636c6f7564696e6172792e636f6d2f646a6776666c3174762f696d6167652f75706c6f61642f76313738303636373036332f736572766261795f656463376a7a2e706e67)

### Testing And Build Machine

[](#testing-and-build-machine)

- Tested on: `Mac M4`
- Built on: `Mac M4`

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance97

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~2 days

Total

6

Last Release

40d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/fdfc8e246731b39477bdf1668097bd52dfa52a1a9588235cbdd75d2dc7c6b927?d=identicon)[ghostcompiler](/maintainers/ghostcompiler)

---

Top Contributors

[![ghostcompiler](https://avatars.githubusercontent.com/u/269910969?v=4)](https://github.com/ghostcompiler "ghostcompiler (11 commits)")

---

Tags

apilaraveleloquentquery builderfiltering

###  Code Quality

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ghostcompiler-laravel-querybuilder/health.svg)

```
[![Health](https://phpackages.com/badges/ghostcompiler-laravel-querybuilder/health.svg)](https://phpackages.com/packages/ghostcompiler-laravel-querybuilder)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[prettus/l5-repository

Laravel 8|9|10|11|12|13 - Repositories to the database layer

4.3k11.2M153](/packages/prettus-l5-repository)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8733.1M23](/packages/yajra-laravel-oci8)[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[api-platform/laravel

API Platform support for Laravel

59156.3k10](/packages/api-platform-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45344.0k1](/packages/pressbooks-pressbooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
