PHPackages                             redsky-thirty/laravel-api-query-builder - 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. [API Development](/categories/api)
4. /
5. redsky-thirty/laravel-api-query-builder

ActiveLibrary[API Development](/categories/api)

redsky-thirty/laravel-api-query-builder
=======================================

Composable query builder for Laravel APIs with dynamic field selection, filtering, sorting, and eager loading.

1.4.0(2mo ago)083MITPHPPHP ^8.3

Since May 19Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/RedskyThirty/laravel-api-query-builder)[ Packagist](https://packagist.org/packages/redsky-thirty/laravel-api-query-builder)[ Docs](https://github.com/RedskyThirty/laravel-api-query-builder)[ RSS](/packages/redsky-thirty-laravel-api-query-builder/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (2)Versions (23)Used By (0)

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

[](#laravel-api-query-builder)

A lightweight and composable query builder for Laravel APIs, inspired by GraphQL flexibility.
Select only the fields and relations you want. Filter, sort, paginate — cleanly.

**Current version:** 1.3.0
**Last update:** February 24, 2026

---

Table of contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
    - [Collection Mode](#collection-mode)
    - [Single Resource Mode](#single-resource-mode)
    - [Usage Without Executing a Query](#usage-without-executing-a-query)
- [Always Fields](#always-fields)
    - [Priority Rules](#-priority-rules)
- [Sorting](#sorting)
    - [Basic Usage](#basic-usage)
    - [Defining Allowed Sorts](#defining-allowed-sorts)
    - [Default Sorts](#default-sorts)
- [Local Scopes](#local-scopes)
    - [Basic Usage](#basic-usage)
    - [Whitelisting Allowed Scopes](#whitelisting-allowed-scopes)
    - [Syntax Variants](#syntax-variants)
    - [Wildcard Mode](#wildcard-mode)
- [Resource example](#resource-example)
- [DTO-backed Resources](#dto-backed-resources)
    - [When you own the DTO](#when-you-own-the-dto)
    - [When you don't own the DTO](#when-you-dont-own-the-dto)
    - [Accessing the DTO in data()](#accessing-the-dto-in-data)
- [Field Resolution Without a Query (ApiFieldResolver)](#field-resolution-without-a-query-apifieldresolver)
    - [Basic Usage](#basic-usage-1)
    - [alwaysFields Support](#alwaysfields-support)
    - [Strict Mode](#strict-mode)
    - [Inspecting Resolved Fields](#inspecting-resolved-fields)
- [Nested Relation Helpers](#nested-relation-helpers)
    - [Usage](#usage)
    - [Behavior](#behavior)
    - [Signature](#signature)
- [Demo](#demo)
    - [Getting started](#-getting-started)
    - [Customizing the Demo](#-customizing-the-demo)
- [Example URLs](#example-urls)
- [Requirements](#requirements)
- [License](#license)

---

Features
--------

[](#features)

- ✅ Dynamic field selection (`fields[users]=name,email`)
- ✅ Relation loading with nested control (`relations=posts.comments`)
- ✅ Filtering (`where[status]=active`)
- ✅ Logical AND / OR filtering (`where[name]=john|doe`)
- ✅ Sorting (`orderby=-created_at`)
- ✅ Strict mode for validation

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

[](#installation)

```
composer require redsky-thirty/laravel-api-query-builder
```

Usage
-----

[](#usage)

### Collection Mode

[](#collection-mode)

This mode is used to retrieve multiple results from the database. It can automatically decide between returning a full collection or a paginated response based on the presence of the `per_page` parameter.

```
use App\Http\Resources\UserResource;
use RedskyEnvision\ApiQueryBuilder\ApiQueryBuilder;
use RedskyEnvision\ApiQueryBuilder\Sorts\Sort;

/*
 * Use auto-mode based on URI parameters
 */

$results = ApiQueryBuilder::make(User::class, $request)
    ->allowedRelations(['profile', 'addresses', 'posts', 'posts.comments'])
    ->allowedScopes(['unverified'])
    ->allowedFields([
        'users' => ['id', 'email', 'created_at', 'profile', 'addresses', 'posts'],
        'profiles' => ['*'],
        'addresses' => ['*'],
        'posts' => ['title', 'excerpt', 'created_at', 'comments'],
        'comments' => ['username', 'message', 'created_at']
    ])
    ->alwaysFields([
        'posts' => ['author_id']
    ])
    ->allowedFilters(['name', 'email', 'created_at', 'addresses.*', 'profile.firstname', 'profile.lastname', 'posts.comments.username'])
    ->defaultSorts([Sort::make('created_at', 'desc')])

    ->prepare()
    ->fetch();

/*
 * Force "Collection"
 *
 * $results = ApiQueryBuilder::make(User::class, $request)
 *      ...
 *      ->get();
 *
 * Force "LengthAwarePaginator"
 *
 * $results = ApiQueryBuilder::make(User::class, $request)
 *      ...
 *      ->paginate();
 */

return UserResource::collection($results);
```

### Single Resource Mode

[](#single-resource-mode)

This mode allows you to build the query manually and return a single model instance (e.g., `User::find(...)`). Useful for retrieving one resource with relation and field selection logic applied.

```
use App\Http\Resources\UserResource;
use App\Models\User;
use RedskyEnvision\ApiQueryBuilder\ApiQueryBuilder;
use RedskyEnvision\ApiQueryBuilder\Resources\NotFoundResource;

$user = ApiQueryBuilder::make(User::class, $request)
    ->allowedRelations(['profile', 'addresses', 'posts', 'posts.comments'])
    ->allowedFields([
        'users' => ['id', 'email', 'created_at', 'profile', 'addresses', 'posts'],
        'profiles' => ['*'],
        'addresses' => ['*'],
        'posts' => ['title', 'excerpt', 'created_at', 'comments'],
        'comments' => ['username', 'message', 'created_at']
    ])
    ->alwaysFields([
        'posts' => ['author_id']
    ])
    ->allowedFilters(['name', 'email', 'created_at', 'addresses.*', 'profile.firstname', 'profile.lastname', 'posts.comments.username'])
    ->prepare()
    ->query()
    ->where('id', $id)
    ->first();

return $user !== null ? UserResource::make($user) : NotFoundResource::make();
```

### Usage Without Executing a Query

[](#usage-without-executing-a-query)

You can initialize field and relation selection logic without executing any database queries using the `prepareWithoutQuery()` method. This is particularly useful when preparing resource responses or resolving metadata without needing to fetch actual records.

This method parses the requested fields from the URL and stores them in the internal FieldRegistry, allowing your resources to behave consistently with the API expectations — all without triggering any Eloquent or SQL operations.

```
use App\Http\Resources\UserResource;
use App\Models\User;
use RedskyEnvision\ApiQueryBuilder\ApiQueryBuilder;

$user = User::with(['profile', 'addresses', 'posts', 'posts.comments'])->inRandomOrder()->first();

ApiQueryBuilder::make(User::class, $request)
    ->allowedRelations(['profile', 'addresses', 'posts', 'posts.comments'])
    ->allowedFields([
        'users' => ['id', 'email', 'created_at', 'profile', 'addresses', 'posts'],
        'profiles' => ['*'],
        'addresses' => ['*'],
        'posts' => ['title', 'excerpt', 'created_at', 'comments'],
        'comments' => ['username', 'message', 'created_at']
    ])
    ->prepareWithoutQuery();

return UserResource::make($user);
```

Always Fields
-------------

[](#always-fields)

Sometimes, certain fields are **required internally** even if the client hasn't explicitly requested them. For example, foreign keys used to link relationships.

To support this, the `alwaysFields()` method allows you to define fields that should **always be included in the response**, even if they are not present in the `fields[...]` parameters or in the `defaultFields()` fallback.

```
->alwaysFields([
    'posts' => ['author_id']
])
```

These fields will be automatically merged into the requested or default field set before the resource is rendered.

### ⚠️ Priority Rules

[](#️-priority-rules)

- `alwaysFields` are **not filtered** by `allowedFields`
- They are **injected unconditionally**
- Useful for internal fields like foreign keys or polymorphic links

Sorting
-------

[](#sorting)

The `orderby` parameter allows you to dynamically control the sort order of your API results.

### Basic Usage

[](#basic-usage)

You can specify one or multiple fields to sort by.
By default, the sort order is **ascending** unless you prefix the field with a minus (`-`) for **descending** order.

#### Examples

[](#examples)

```
# Sort by email ascending
GET /api/users?orderby=email

# Sort by created date descending
GET /api/users?orderby=-created_at

# Sort by multiple fields (first by created_at descending, then by email ascending)
GET /api/users?orderby=-created_at,email

```

### Defining Allowed Sorts

[](#defining-allowed-sorts)

To restrict which fields can be used for sorting, you can use the `allowedSorts()` method:

```
$results = ApiQueryBuilder::make(User::class, $request)
    ->allowedSorts(['id', 'email', 'created_at'])
    ->prepare()
    ->fetch();
```

If a user tries to sort by a field not in the allowed list, the query builder will ignore it (or throw an exception if **strict mode** is enabled).

### Default Sorts

[](#default-sorts)

You can define default sorts using the `defaultSorts()` method.
This will be applied automatically if no `orderby` parameter is provided.

```
use RedskyEnvision\ApiQueryBuilder\Sorts\Sort;

$results = ApiQueryBuilder::make(User::class, $request)
    ->defaultSorts([ Sort::make('created_at', 'desc') ])
    ->prepare()
    ->fetch();
```

This ensures that your API always returns predictable results even when no explicit sorting is requested.

Local Scopes
------------

[](#local-scopes)

The query builder supports applying **Eloquent local scopes** directly from the URL via the `scopes` parameter.

Local scopes let you encapsulate commonly used query constraints in your models (e.g. `scopeUnverified()` → `unverified()`).

### Basic Usage

[](#basic-usage-1)

```
GET /api/users?scopes=unverified
```

This will automatically call the method `scopeUnverified()` defined on the `User` model — equivalent to:

```
User::unverified()->get();
```

You can also pass multiple scopes separated by commas:

```
GET /api/users?scopes=unverified,visibleOnly
```

> **Note:** Scopes are applied **only to the root model**, not to nested relations.

### Whitelisting Allowed Scopes

[](#whitelisting-allowed-scopes)

To control which scopes can be applied from the URL, use the `allowedScopes()` method:

```
$results = ApiQueryBuilder::make(User::class, $request)
->allowedScopes(['unverified', 'visibleOnly'])
->prepare()
->fetch();
```

If a request includes a scope that is not allowed, it will either:

- be ignored (in **non-strict mode**), or
- throw an `InvalidArgumentException` (in **strict mode**, enabled by default).

### Syntax Variants

[](#syntax-variants)

The following formats are all accepted and automatically normalized:

InputInvoked Scope`unverified``scopeUnverified()``unverified()``scopeUnverified()``scopeUnverified``scopeUnverified()``scopeUnverified()``scopeUnverified()`### Wildcard Mode

[](#wildcard-mode)

To allow **all** local scopes to be accessible (not recommended in public APIs):

```
->allowedScopes(['*'])
```

Resource example
----------------

[](#resource-example)

```
class UserResource extends ApiResource {
    protected function defaultFields(): array {
        return ['id', 'email', 'profile', 'created_at', 'updated_at'];
    }

    protected function data(): array {
        return [
            'id' => $this->id,
            'email' => $this->email,
            'profile' => $this->whenLoaded('profile', fn () => ProfileResource::make($this->profile)),
            'posts' => $this->whenLoaded('posts', fn () => PostResource::collection($this->posts)),
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at
        ];
    }
}
```

DTO-backed Resources
--------------------

[](#dto-backed-resources)

By default, `ApiResource` expects an Eloquent model as its underlying resource. However, it is possible to back a resource with a DTO instead, while still benefiting from the full field-filtering logic provided by `ApiResource`.

To support this, the package provides the `ApiDtoResource` abstract class and the `ApiResourceable` contract.

### When you own the DTO

[](#when-you-own-the-dto)

If you have full control over the DTO class, implement the `ApiResourceable` interface on it. This requires two methods: `getTable()`, which returns the table or registry key used for field lookups, and `getAttributes()`, which returns the raw attributes as a key/value array.

```
use RedskyEnvision\ApiQueryBuilder\Contracts\ApiResourceable;

class UserData implements ApiResourceable
{
    public function __construct(
        public readonly int    $id,
        public readonly string $name,
        public readonly string $email,
    ) {}

    public function getTable(): string
    {
        return 'users';
    }

    public function getAttributes(): array
    {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'email' => $this->email
        ];
    }
}
```

If your DTO already exposes a `toArray()` method that matches the structure you want to expose, you can use it directly in `getAttributes()` instead of listing every property manually:

```
public function getAttributes(): array
{
    return $this->toArray();
}
```

Then extend `ApiDtoResource` instead of `ApiResource` in your resource class:

```
use RedskyEnvision\ApiQueryBuilder\Resources\ApiDtoResource;

class UserResource extends ApiDtoResource
{
    protected function defaultFields(): array
    {
        return ['id', 'name', 'email'];
    }

    protected function data(): array
    {
        return [
            'id'    => $this->dto()->id,
            'name'  => $this->dto()->name,
            'email' => $this->dto()->email
        ];
    }
}
```

Usage remains identical to a model-backed resource:

```
UserResource::make($dto);
UserResource::collection($dtos);
```

### When you don't own the DTO

[](#when-you-dont-own-the-dto)

If the DTO comes from a third-party library and you cannot make it implement `ApiResourceable`, override `resolveTable()` and `resolveAttributes()` directly in the resource class. No adapter or intermediate class is needed.

If the DTO exposes a `toArray()` method that fits your needs, you can use it directly in `resolveAttributes()`:

```
use RedskyEnvision\ApiQueryBuilder\Resources\ApiDtoResource;

class UserResource extends ApiDtoResource
{
    protected function resolveTable(): string
    {
        return 'users';
    }

    protected function resolveAttributes(): array
    {
        /** @var ThirdPartyUserData $dto */
        $dto = $this->resource;

        return $dto->toArray();
    }

    protected function defaultFields(): array
    {
        return ['id', 'name', 'email'];
    }

    protected function data(): array
    {
        /** @var ThirdPartyUserData $dto */
        $dto = $this->resource;

        return [
            'id'    => $dto->id,
            'name'  => $dto->name,
            'email' => $dto->email,
        ];
    }
}
```

Otherwise, list the properties manually:

```
protected function resolveAttributes(): array
{
    /** @var ThirdPartyUserData $dto */
    $dto = $this->resource;

    return [
        'id'    => $dto->id,
        'name'  => $dto->name,
        'email' => $dto->email,
    ];
}
```

Usage is unchanged:

```
UserResource::make($thirdPartyDto);
```

### Accessing the DTO in data()

[](#accessing-the-dto-in-data)

`ApiDtoResource` provides a `dto()` helper that returns `$this->resource` typed as `ApiResourceable`. This gives static analysis tools (PHPStan, Psalm) accurate type information, which `$this->resource` alone — declared as `mixed` in `JsonResource` — cannot provide.

```
protected function data(): array
{
    return [
        'id'   => $this->dto()->id,
        'name' => $this->dto()->name,
    ];
}
```

If you prefer accessing DTO properties directly via `$this->property` without calling `dto()`, you can add a `@mixin` annotation on the resource class. This is purely an IDE hint and has no effect on static analysis tools:

```
/** @mixin UserData */
class UserResource extends ApiDtoResource
{
    protected function data(): array
    {
        return [
            'id'   => $this->id,    // IDE-friendly, no dto() call needed
            'name' => $this->name,
        ];
    }
}
```

> **Note:** `@mixin` is recognized by PhpStorm and similar IDEs but is ignored by PHPStan and Psalm. If you rely on static analysis, prefer `dto()` for accurate type inference.

Field Resolution Without a Query (ApiFieldResolver)
---------------------------------------------------

[](#field-resolution-without-a-query-apifieldresolver)

When working with DTO-backed resources, there is no Eloquent model and no database query to execute. `ApiQueryBuilder` cannot be used in this context because it requires a model class. `ApiFieldResolver` fills this gap: it provides the same field resolution and `FieldRegistry` registration logic, without any query building or Eloquent dependency.

### Basic Usage

[](#basic-usage-2)

Instantiate `ApiFieldResolver` in your controller before returning the resource. The `prepare()` call parses the `fields[...]` parameters from the request, filters them against `allowedFields`, and registers the result in `FieldRegistry` so that the resource can apply field filtering automatically.

```
use App\DTOs\UserDto;
use App\Http\Resources\UserDtoResource;
use RedskyEnvision\ApiQueryBuilder\ApiFieldResolver;

class UserController extends Controller
{
    public function show(Request $request, int $id): UserDtoResource
    {
        $dto = new UserDto(
            id: $id,
            email: 'john@example.com',
            name: 'John',
            createdAt: now()
        );

        ApiFieldResolver::make($request)
            ->allowedFields([
                'users' => ['id', 'email', 'name', 'created_at']
            ])
            ->prepare('users');

        return UserDtoResource::make($dto);
    }
}
```

With `?fields[users]=id,email`, the response will only contain `id` and `email`.
Without any `fields` parameter, the resource falls back to its `defaultFields()`.

### alwaysFields Support

[](#alwaysfields-support)

`ApiFieldResolver` supports `alwaysFields()` with the same semantics as `ApiQueryBuilder`: the specified fields are injected unconditionally into any non-wildcard selection.

```
ApiFieldResolver::make($request)
    ->allowedFields([
        'users' => ['id', 'email', 'name', 'created_at']
    ])
    ->alwaysFields([
        'users' => ['id']
    ])
    ->prepare('users');
```

### Strict Mode

[](#strict-mode)

By default, strict mode is enabled: requesting a field not listed in `allowedFields` throws an `InvalidFieldException`. Pass `false` as the second argument to `make()` to silently drop disallowed fields instead.

```
ApiFieldResolver::make($request, strict: false)
    ->allowedFields([
        'users' => ['id', 'email', 'name']
    ])
    ->prepare('users');
```

### Inspecting Resolved Fields

[](#inspecting-resolved-fields)

After calling `prepare()`, you can query the resolved field list directly on the resolver instance if needed.

```
$resolver = ApiFieldResolver::make($request)
    ->allowedFields(['users' => ['id', 'email', 'name', 'created_at']])
    ->prepare('users');

// Returns the filtered field list, e.g. ['id', 'email']
$fields = $resolver->getRequestedFieldsFor('users');

// Returns true if 'email' is in the resolved list (or if wildcard is active)
$hasEmail = $resolver->hasRequestedField('users', 'email');
```

Nested Relation Helpers
-----------------------

[](#nested-relation-helpers)

Sometimes you may want to include data in a **Resource** only if a **nested relation** has been loaded.
Laravel provides the `whenLoaded()` method but it only works with **direct relations**.

To solve this, the package includes the `HasNestedWhenLoaded` trait.

### Usage

[](#usage-1)

```
use RedskyEnvision\ApiQueryBuilder\Resources\Concerns\HasNestedWhenLoaded;

class ContactResource extends ApiResource {
    use HasNestedWhenLoaded;

    protected function data(): array {
        return [
            'id' => $this->id,
            'user_id' => $this->user_id,

            // Only included if both "user" and "profile" are eager-loaded
            'profile' => $this->whenNestedLoaded(
                'user.profile',
                fn () => ProfileResource::make($this->user->profile)
            ),
        ];
    }
}
```

### Behavior

[](#behavior)

- If the full relation chain (`user.profile`) is loaded → the callback is executed.
- If any intermediate relation is missing → the default value (`null`) is returned.
- Supports unlimited depth (e.g. `user.profile.address.country`).

### Signature

[](#signature)

```
whenNestedLoaded(string $relation, callable $callback, mixed $default = null): mixed
```

- `$relation`: Nested relation using dot notation (`parent.child.grandchild`).
- `$callback`: Function executed if all relations in the chain are loaded.
- `$default`: Value returned if the relation is not loaded (defaults to `null`).

Demo
----

[](#demo)

This package includes a demo Laravel application for local testing and exploration.

### 🚀 Getting started

[](#-getting-started)

To launch the demo project:

```
cd demo
composer install
php artisan migrate:fresh --seed
php artisan serve
```

Then open your browser at .

### 🔧 Customizing the Demo

[](#-customizing-the-demo)

The logic used to test the API query builder is defined inside:

```
/demo/routes/api.php

```

You can modify or extend this file freely to experiment with:

- Custom endpoints
- Different models and relationships
- Field selection, filtering, sorting, and nested relations

This allows you to test the package without needing to copy files into a separate Laravel project.

> Note: The `demo/` folder is for local use only and should not be required in production.

Example URLs
------------

[](#example-urls)

### 1. Select default fields with relations

[](#1-select-default-fields-with-relations)

```
GET /api/users?relations=posts,posts.comments,profile

```

### 2. Select specific fields

[](#2-select-specific-fields)

```
GET /api/users?fields[users]=id,name,created_at

```

### 3. Load relations and limit fields

[](#3-load-relations-and-limit-fields)

```
GET /api/users?
    fields[users]=id,email,profile&
    fields[profiles]=firstname,lastname&
    relations=profile

GET /api/users?
    fields[users]=id,email,profile,addresses,posts&
    fields[profiles]=firstname,lastname&
    fields[addresses]=street,zip,locality,formatted_address&
    fields[posts]=title,description,excerpt,comments&
    fields[comments]=message,username,created_at&
    relations=posts,posts.comments,profile,addresses

```

### 4. Apply local scope

[](#4-apply-local-scope)

```
GET /api/users?scopes=unverified

```

### 5. Filter with equals, OR and AND

[](#5-filter-with-equals-or-and-and)

```
GET /api/users?
    fields[users]=profile&
    fields[profiles]=firstname,lastname&
    relations=profile&
    where[profile.firstname]=john

GET /api/users?
    fields[users]=profile&
    fields[profiles]=firstname,lastname&
    relations=profile&
    where[profile.firstname]=!john

GET /api/users?
    fields[users]=profile&
    fields[profiles]=firstname,lastname&
    relations=profile&
    where[profile.firstname]=john|jane

GET /api/users?
    fields[users]=profile&
    fields[profiles]=firstname,lastname&
    relations=profile&
    where[profile.firstname]=!john,!jane

```

### 6. Filter with operators

[](#6-filter-with-operators)

```
GET /api/users?
    fields[users]=id,email,created_at&
    where[created_at]=gt:2025-05-01%2023:59:59

GET /api/users?
    fields[users]=id,email,created_at&
    where[created_at]=gte:2025-05-01%2000:00:00,lte:2025-05-31%2023:59:59

GET /api/users?
    fields[users]=id,email,created_at&
    where[created_at]=lte:2023-12-31%2023:59:59|gte:2025-01-01%2000:00:00

```

### 7. Search (LIKE)

[](#7-search-like)

```
GET /api/users?
    fields[users]=id,email&
    like[email]=gmail

GET /api/users?
    fields[users]=id,email&
    like[email]=!gmail

GET /api/users?
    fields[users]=id,email&
    like[email]=gmail|yahoo

GET /api/users?
    fields[users]=id,email&
    like[email]=!gmail,!yahoo

```

### 8. Sort results

[](#8-sort-results)

```
GET /api/users?orderby=email
GET /api/users?orderby=-created_at

```

### 9. Pagination

[](#9-pagination)

```
GET /api/users?
    fields[users]=id,email,profile&
    fields[profiles]=firstname,lastname&
    relations=profile&
    per_page=10

```

### 10. "Single Resource Mode" and "Without Executing a Query"

[](#10-single-resource-mode-and-without-executing-a-query)

All URL parameters related to **field selection** demonstrated above can also be used with single-resource endpoints like `/api/users/{id}` or `/api/users/random`.

This works especially well when using the `prepareWithoutQuery()` method, which allows parsing and validation of requested fields and relations **without performing any database query**. This ensures consistent response shaping even when loading a single resource outside of the query builder's automatic mode.

All URL examples provided above are fully replicable with the **Single Resource** usage (e.g. via `/api/users/{id}`).
This ensures a consistent API experience whether you're fetching a list of resources or a single one.

When using the `prepareWithoutQuery()` method (query-less mode), **only field selection logic** (i.e. the `fields[...]` parameters) is parsed and validated. This is useful for shaping responses or metadata without performing any database access — such as in the demo endpoints `/api/users/random` — but it does not apply filters, sorting, or relation loading.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 12+

License
-------

[](#license)

MIT © [Redsky-Thirty](https://github.com/Redsky-Thirty)

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance86

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity61

Established project with proven stability

 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 ~15 days

Recently: every ~30 days

Total

21

Last Release

60d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4795fe2609d09081ab35323d9df5fd152da90fa54b4c0002d8213b0d48b6bf66?d=identicon)[RedskyEnvision](/maintainers/RedskyEnvision)

---

Top Contributors

[![RedskyThirty](https://avatars.githubusercontent.com/u/11631871?v=4)](https://github.com/RedskyThirty "RedskyThirty (147 commits)")

---

Tags

redsky-thirtyredsky-envisionlaravel-api-query-builder

### Embed Badge

![Health badge](/badges/redsky-thirty-laravel-api-query-builder/health.svg)

```
[![Health](https://phpackages.com/badges/redsky-thirty-laravel-api-query-builder/health.svg)](https://phpackages.com/packages/redsky-thirty-laravel-api-query-builder)
```

###  Alternatives

[mollie/laravel-mollie

Mollie API client wrapper for Laravel &amp; Mollie Connect provider for Laravel Socialite

3624.1M28](/packages/mollie-laravel-mollie)[mll-lab/laravel-graphiql

Easily integrate GraphiQL into your Laravel project

683.2M9](/packages/mll-lab-laravel-graphiql)[spatie/laravel-route-discovery

Auto register routes using PHP attributes

23645.0k2](/packages/spatie-laravel-route-discovery)[esign/laravel-conversions-api

A laravel wrapper package around the Facebook Conversions API

69145.4k](/packages/esign-laravel-conversions-api)[didww/didww-api-3-php-sdk

PHP SDK for DIDWW API 3

1218.2k](/packages/didww-didww-api-3-php-sdk)[surface/laravel-webfinger

A Laravel package to create an ActivityPub webfinger.

113.8k](/packages/surface-laravel-webfinger)

PHPackages © 2026

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