PHPackages                             rebing/graphql-laravel-select-fields - 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. rebing/graphql-laravel-select-fields

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

rebing/graphql-laravel-select-fields
====================================

SelectFields (Eloquent eager loading) for rebing/graphql-laravel - optimizes GraphQL queries by analysing requested fields to build minimal SELECT and eager-load only the needed relations.

1.0.0(1mo ago)001[1 PRs](https://github.com/rebing/graphql-laravel-select-fields/pulls)MITPHPPHP ^8.2CI passing

Since May 24Pushed 3w agoCompare

[ Source](https://github.com/rebing/graphql-laravel-select-fields)[ Packagist](https://packagist.org/packages/rebing/graphql-laravel-select-fields)[ GitHub Sponsors](https://github.com/mfn)[ RSS](/packages/rebing-graphql-laravel-select-fields/feed)WikiDiscussions master Synced today

READMEChangelog (2)Dependencies (7)Versions (8)Used By (0)

SelectFields for graphql-laravel
================================

[](#selectfields-for-graphql-laravel)

[![Latest Stable Version](https://camo.githubusercontent.com/3028c9a6fc6b190ce217133fc8b324b6a9fe5dc16b471b681bda88501e7ec6fc/68747470733a2f2f706f7365722e707567782e6f72672f726562696e672f6772617068716c2d6c61726176656c2d73656c6563742d6669656c64732f762f737461626c65)](https://packagist.org/packages/rebing/graphql-laravel-select-fields)[![License](https://camo.githubusercontent.com/c7e2b69ae52b955d8c73f9449c5ba4be52368aa067a14a64dfe0243e84799053/68747470733a2f2f706f7365722e707567782e6f72672f726562696e672f6772617068716c2d6c61726176656c2d73656c6563742d6669656c64732f6c6963656e7365)](https://packagist.org/packages/rebing/graphql-laravel-select-fields)[![Tests](https://github.com/rebing/graphql-laravel-select-fields/workflows/Tests/badge.svg)](https://github.com/rebing/graphql-laravel-select-fields/actions?query=workflow%3ATests)[![Downloads](https://camo.githubusercontent.com/017f6ceb68e793f48b20c59ea7a027c68f855814ccad981e86c1d835fe07491f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f726562696e672f6772617068716c2d6c61726176656c2d73656c6563742d6669656c64732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/rebing/graphql-laravel-select-fields)[![Get on Slack](https://camo.githubusercontent.com/15aa806a5685b67b0cf43579dc41f6aca3a52b9ecab9a7db4c3711ed74c77629/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f736c61636b2d6a6f696e2d6f72616e67652e737667)](https://rebing-graphql.slack.com/join/shared_invite/enQtNTE5NjQzNDI5MzQ4LTdhNjk0ZGY1N2U1YjE4MGVlYmM2YTc2YjQ0MmIwODY5MWMwZWIwYmY1MWY4NTZjY2Q5MzdmM2Q3NTEyNDYzZjc#/shared-invite/email)

Optimizes GraphQL queries backed by Eloquent models. Analyzes the GraphQL request's field selection to generate minimal `SELECT` columns and eager-load only the requested relations - preventing N+1 queries and over-fetching.

This is an optional companion package for [rebing/graphql-laravel](https://github.com/rebing/graphql-laravel).

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

[](#requirements)

- PHP ^8.2
- Laravel 12+
- rebing/graphql-laravel 10.0.0+

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

[](#installation)

```
composer require rebing/graphql-laravel-select-fields
```

The package auto-discovers its service provider. No manual registration is needed. On boot it:

1. Registers a `ResolverParameterInjector` so that `Closure` and `SelectFields`type-hints in resolver methods work automatically.
2. Replaces the core pagination types with SelectFields-aware subclasses that implement `WrapType` and mark metadata fields as non-selectable.

Quick Start
-----------

[](#quick-start)

**1. Add `model` to your Type:**

```
use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    protected $attributes = [
        'name'  => 'User',
        'model' => User::class,
    ];

    public function fields(): array
    {
        return [
            'id'    => ['type' => Type::nonNull(Type::id())],
            'email' => ['type' => Type::nonNull(Type::string())],
            'posts' => [
                'type' => Type::listOf(GraphQL::type('Post')),
            ],
        ];
    }
}
```

**2. Use `$getSelectFields` in your Query:**

```
use Closure;
use App\Models\User;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;

class UsersQuery extends Query
{
    protected $attributes = [
        'name' => 'users',
    ];

    public function type(): Type
    {
        return Type::listOf(GraphQL::type('User'));
    }

    public function args(): array
    {
        return [
            'id'    => ['type' => Type::string()],
            'email' => ['type' => Type::string()],
        ];
    }

    public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        /** @var SelectFields $fields */
        $fields = $getSelectFields();
        $select = $fields->getSelect();
        $with = $fields->getRelations();

        return User::select($select)->with($with)->get();
    }
}
```

When a client queries `{ users { id email posts { title } } }`, SelectFields generates:

```
SELECT "users"."id", "users"."email" FROM "users";
SELECT "posts"."title", "posts"."user_id" FROM "posts" WHERE "posts"."user_id" IN (?, ?);
```

Only the requested columns are fetched, and the `posts` relation is eager-loaded in a single query.

Usage
-----

[](#usage)

### Resolver Injection Patterns

[](#resolver-injection-patterns)

SelectFields can be injected into your `resolve()` method in two ways.

#### Closure (lazy - recommended)

[](#closure-lazy---recommended)

```
public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
{
    /** @var SelectFields $fields */
    $fields = $getSelectFields();
    $select = $fields->getSelect();
    $with = $fields->getRelations();

    return User::select($select)->with($with)->get();
}
```

The `SelectFields` instance is only constructed when you call `$getSelectFields()`. If your resolver has an early return path (cache hit, authorization check), you avoid the cost of walking the query plan.

#### Class (eager)

[](#class-eager)

```
use Rebing\GraphQL\Support\SelectFields;

public function resolve($root, array $args, $context, ResolveInfo $info, SelectFields $fields)
{
    $select = $fields->getSelect();
    $with = $fields->getRelations();

    return User::select($select)->with($with)->get();
}
```

The `SelectFields` instance is constructed before your resolver runs.

### Type Configuration

[](#type-configuration)

#### The `model` Attribute

[](#the-model-attribute)

The `model` attribute on your Type's `$attributes` array is **required** for SelectFields to work. It enables:

- Table-qualified column names (`"users"."id"` instead of `"id"`)
- Automatic primary key inclusion in SELECT
- Eloquent relation traversal for eager loading

```
protected $attributes = [
    'name'  => 'User',
    'model' => User::class,
];
```

Without `model`, SelectFields operates in a degraded mode - no table qualification, no primary key inclusion, no relation detection.

#### Field Configuration Keys

[](#field-configuration-keys)

These keys can be placed in the arrays returned by your Type's `fields()` method:

KeyTypeDefaultPurpose`selectable``bool``true`Whether to include the field in SQL SELECT. Set to `false` for computed/virtual fields that have no database column (e.g. accessors).`is_relation``bool``true`Whether sub-fields represent an Eloquent relationship. Set to `false` for JSON columns or cast arrays.`always``string|string[]`-Additional columns always included in SELECT when this field is requested. Useful for computed properties that depend on other columns.`query``Closure`-Custom query callback applied to the Eloquent eager-loading query for this relation.`alias``string|Closure|Expression`field nameMaps a GraphQL field name to a different database column or relation method name.### Eager Loading Relationships

[](#eager-loading-relationships)

The `profile` and `posts` relations must also exist on the User Eloquent model. If some fields are required for the relation to load or for validation, you can define an `always` attribute that will add the given attributes to select.

The attribute can be a comma separated string or an array of attributes to always include.

```
// Array form:
'always' => ['title', 'body'],
// String form (comma-separated):
'always' => 'title,body',
```

```
declare(strict_types = 1);
namespace App\GraphQL\Types;

use App\Models\User;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;

class UserType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'User',
        'description'   => 'A user',
        'model'         => User::class,
    ];

    public function fields(): array
    {
        return [
            'uuid' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The uuid of the user'
            ],
            'email' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The email of user'
            ],
            'profile' => [
                'type' => GraphQL::type('Profile'),
                'description' => 'The user profile',
            ],
            'posts' => [
                'type' => Type::listOf(GraphQL::type('Post')),
                'description' => 'The user posts',
                // Can also be defined as a string
                'always' => ['title', 'body'],
            ]
        ];
    }
}
```

At this point we have a profile and a post type as expected for any model:

```
class ProfileType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'Profile',
        'description'   => 'A user profile',
        'model'         => UserProfileModel::class,
    ];

    public function fields(): array
    {
        return [
            'name' => [
                'type' => Type::string(),
                'description' => 'The name of user'
            ]
        ];
    }
}
```

```
class PostType extends GraphQLType
{
    protected $attributes = [
        'name'          => 'Post',
        'description'   => 'A post',
        'model'         => PostModel::class,
    ];

    public function fields(): array
    {
        return [
            'title' => [
                'type' => Type::nonNull(Type::string()),
                'description' => 'The title of the post'
            ],
            'body' => [
                'type' => Type::string(),
                'description' => 'The body the post'
            ]
        ];
    }
}
```

### Custom Relation Queries

[](#custom-relation-queries)

You can specify a `query` callback that will be applied to the Eloquent eager-loading query for a relation:

```
class UserType extends GraphQLType
{

    // ...

    public function fields(): array
    {
        return [
            // ...

            // Relation
            'posts' => [
                'type'          => Type::listOf(GraphQL::type('Post')),
                'description'   => 'A list of posts written by the user',
                'args'          => [
                    'date_from' => [
                        'type' => Type::string(),
                    ],
                 ],
                // $args are the local arguments passed to the relation
                // $query is the relation builder object
                // $ctx is the GraphQL context (customizable via execution middleware)
                // The return value should be the query builder or void
                'query'         => function (array $args, $query, $ctx): void {
                    $query->addSelect('some_column')
                          ->where('posts.created_at', '>', $args['date_from']);
                }
            ]
        ];
    }
}
```

### Pagination

[](#pagination)

Pagination will be used if a query or mutation returns a `PaginationType`.

Note that unless you use resolver middleware, you will have to manually supply both the limit and page values:

```
declare(strict_types = 1);
namespace App\GraphQL\Queries;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;

class PostsQuery extends Query
{
    public function type(): Type
    {
        return GraphQL::paginate('posts');
    }

    // ...

    public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        $fields = $getSelectFields();

        return Post::with($fields->getRelations())
            ->select($fields->getSelect())
            ->paginate($args['limit'], ['*'], 'page', $args['page']);
    }
}
```

Query `posts(limit:10,page:1){data{id},total,per_page}` might return:

```
{
    "data": {
        "posts": {
            "data": [
                {"id": 3},
                {"id": 5}
            ],
            "total": 21,
            "per_page": 10
        }
    }
}
```

Note that you need to add the extra `data` object when you request paginated resources, as the returned data gives you the paginated resources in a `data`object at the same level as the returned pagination metadata.

#### Simple Pagination

[](#simple-pagination)

[Simple Pagination](https://laravel.com/docs/pagination#simple-pagination)will be used if a query or mutation returns a `SimplePaginationType`.

```
class PostsQuery extends Query
{
    public function type(): Type
    {
        return GraphQL::simplePaginate('posts');
    }

    // ...

    public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        $fields = $getSelectFields();

        return Post::with($fields->getRelations())
            ->select($fields->getSelect())
            ->simplePaginate($args['limit'], ['*'], 'page', $args['page']);
    }
}
```

`SimplePaginationType` exposes the following fields: `data` (the paginated items), `per_page`, `current_page`, `from`, `to`, and `has_more_pages`. Unlike full pagination, `total` and `last_page` are **not** available.

#### Cursor Pagination

[](#cursor-pagination)

[Cursor Pagination](https://laravel.com/docs/pagination#cursor-pagination)will be used if a query or mutation returns a `CursorPaginationType`.

```
class PostsQuery extends Query
{
    public function type(): Type
    {
        return GraphQL::cursorPaginate('posts');
    }

    // ...

    public function resolve($root, array $args, $context, ResolveInfo $info, Closure $getSelectFields)
    {
        $fields = $getSelectFields();

        return Post::with($fields->getRelations())
            ->select($fields->getSelect())
            ->cursorPaginate($args['limit'], ['*'], 'cursorName', $args['cursor']);
    }
}
```

`CursorPaginationType` exposes the following fields: `data` (the paginated items), `per_page`, `previous_cursor` (`String`, nullable), and `next_cursor`(`String`, nullable).

#### Pagination type auto-replacement

[](#pagination-type-auto-replacement)

This package automatically replaces the core pagination types with SelectFields-aware subclasses. The subclasses implement `WrapType` (so SelectFields can traverse into the paginated data) and mark metadata fields like `total`, `per_page`, etc. as `selectable: false` (so they are not included in SQL SELECT).

If you have set a custom pagination type in your config, the package will **not**override it.

### JSON Columns

[](#json-columns)

When using JSON columns in your database, the field won't be defined as a "relationship", but rather a simple column with nested data. Use the `is_relation` attribute to tell SelectFields not to treat it as an Eloquent relation:

```
class UserType extends GraphQLType
{
    // ...

    public function fields(): array
    {
        return [
            // ...

            // JSON column containing all posts made by this user
            'posts' => [
                'type'          => Type::listOf(GraphQL::type('Post')),
                'description'   => 'A list of posts written by the user',
                // Now this will simply request the "posts" column, and it won't
                // query for all the underlying columns in the "post" object
                // The value defaults to true
                'is_relation' => false
            ]
        ];
    }

    // ...
}
```

### Wrap Types

[](#wrap-types)

If you use SelectFields in a query that returns a [wrap type](https://github.com/rebing/graphql-laravel#wrap-types), your wrapper class **must** implement the `WrapType` marker interface. This tells SelectFields to look through the wrapper's `data` field to find the underlying model type and generate the correct `SELECT`/`WITH` clauses.

```
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Contracts\WrapType;
use Rebing\GraphQL\Support\Facades\GraphQL;

class PostWrappedType extends ObjectType implements WrapType
{
    public function __construct()
    {
        parent::__construct([
            'name' => 'PostWrapped',
            'fields' => fn () => [
                'data' => [
                    'type' => Type::listOf(GraphQL::type('Post')),
                    'is_relation' => false,
                ],
                'message' => [
                    'type' => Type::string(),
                    'selectable' => false,
                ],
            ],
        ]);
    }
}
```

The package's pagination types already implement this interface. Custom pagination classes configured via the `pagination_type`, `simple_pagination_type`, or `cursor_pagination_type` config keys must also implement it.

### Abstract Types (Unions and Interfaces)

[](#abstract-types-unions-and-interfaces)

When using SelectFields with union or interface types, custom `query` callbacks on relation fields defined in member/concrete types are supported. SelectFields will match the concrete type at eager-load time and apply the callback automatically.

**Note:** When a query includes inline fragments on multiple member types that each request different relations, SelectFields will merge all requested relations into the eager-load set.

**Note:** For union types, SelectFields cannot determine the concrete type at query-build time, so it uses `SELECT *` instead of selecting specific columns.

API Reference
-------------

[](#api-reference)

### `SelectFields`

[](#selectfields)

```
use Rebing\GraphQL\Support\SelectFields;

// Constructed automatically via DI - you rarely need the constructor directly
$fields = new SelectFields($parentType, $queryArgs, $ctx, $fieldsAndArguments);

// Get the columns to select
$fields->getSelect();   // array

// Get the relations to eager-load (with constrained closures)
$fields->getRelations(); // array
```

### `WrapType`

[](#wraptype)

```
use Rebing\GraphQL\Support\Contracts\WrapType;

// Marker interface - no methods to implement
class MyCustomWrapper extends ObjectType implements WrapType { ... }
```

### `SelectFieldsParameterInjector`

[](#selectfieldsparameterinjector)

Implements `Rebing\GraphQL\Support\Contracts\ResolverParameterInjector`. This is registered automatically by the service provider. You only need to interact with it if you're building a custom SelectFields subclass:

```
use Rebing\GraphQL\Support\Field;
use Rebing\GraphQL\Support\SelectFieldsParameterInjector;

// Registered automatically - shown here for reference
Field::registerParameterInjector(new SelectFieldsParameterInjector());
```

Known Limitations
-----------------

[](#known-limitations)

- Resolving fields via aliases will only resolve them once, even if the fields have different arguments ([Issue](https://github.com/rebing/graphql-laravel/issues/604)).

License
-------

[](#license)

MIT

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance94

Actively maintained with recent releases

Popularity1

Limited adoption so far

Community20

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 82.6% 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 ~24 days

Total

2

Last Release

35d ago

Major Versions

0.1.0 → 1.0.02026-06-18

### Community

Maintainers

![](https://www.gravatar.com/avatar/4acecb37dd6c5733ab5d16a9e28b6e7cbd2ead45f824dc36d803230d9cd32c6c?d=identicon)[Rebing](/maintainers/Rebing)

---

Top Contributors

[![mfn](https://avatars.githubusercontent.com/u/87493?v=4)](https://github.com/mfn "mfn (1711 commits)")[![crissi](https://avatars.githubusercontent.com/u/3076578?v=4)](https://github.com/crissi "crissi (42 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (34 commits)")[![duncanmcclean](https://avatars.githubusercontent.com/u/19637309?v=4)](https://github.com/duncanmcclean "duncanmcclean (31 commits)")[![jasonvarga](https://avatars.githubusercontent.com/u/105211?v=4)](https://github.com/jasonvarga "jasonvarga (25 commits)")[![illambo](https://avatars.githubusercontent.com/u/1899805?v=4)](https://github.com/illambo "illambo (22 commits)")[![georgeboot](https://avatars.githubusercontent.com/u/884482?v=4)](https://github.com/georgeboot "georgeboot (22 commits)")[![stevelacey](https://avatars.githubusercontent.com/u/289531?v=4)](https://github.com/stevelacey "stevelacey (17 commits)")[![sforward](https://avatars.githubusercontent.com/u/3530221?v=4)](https://github.com/sforward "sforward (17 commits)")[![sowork](https://avatars.githubusercontent.com/u/15050765?v=4)](https://github.com/sowork "sowork (17 commits)")[![rebing](https://avatars.githubusercontent.com/u/8533161?v=4)](https://github.com/rebing "rebing (15 commits)")[![EdwinDayot](https://avatars.githubusercontent.com/u/3714657?v=4)](https://github.com/EdwinDayot "EdwinDayot (14 commits)")[![zjbarg](https://avatars.githubusercontent.com/u/26728749?v=4)](https://github.com/zjbarg "zjbarg (13 commits)")[![edgarsn](https://avatars.githubusercontent.com/u/6625918?v=4)](https://github.com/edgarsn "edgarsn (11 commits)")[![viktorruskai](https://avatars.githubusercontent.com/u/9396295?v=4)](https://github.com/viktorruskai "viktorruskai (9 commits)")[![matsn0w](https://avatars.githubusercontent.com/u/15019582?v=4)](https://github.com/matsn0w "matsn0w (8 commits)")[![alissn](https://avatars.githubusercontent.com/u/26966142?v=4)](https://github.com/alissn "alissn (7 commits)")[![alancolant](https://avatars.githubusercontent.com/u/19172637?v=4)](https://github.com/alancolant "alancolant (6 commits)")[![jacobdekeizer](https://avatars.githubusercontent.com/u/15017400?v=4)](https://github.com/jacobdekeizer "jacobdekeizer (6 commits)")[![kylekatarnls](https://avatars.githubusercontent.com/u/5966783?v=4)](https://github.com/kylekatarnls "kylekatarnls (6 commits)")

---

Tags

laravelgraphqleloquenteager-loadingselect-fields

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/rebing-graphql-laravel-select-fields/health.svg)

```
[![Health](https://phpackages.com/badges/rebing-graphql-laravel-select-fields/health.svg)](https://phpackages.com/packages/rebing-graphql-laravel-select-fields)
```

###  Alternatives

[illuminate/database

The Illuminate Database package.

2.8k54.9M12.2k](/packages/illuminate-database)[watson/validating

Eloquent model validating trait.

9803.5M55](/packages/watson-validating)[reedware/laravel-relation-joins

Adds the ability to join on a relationship by name.

2121.3M18](/packages/reedware-laravel-relation-joins)[wnx/laravel-backup-restore

A package to restore database backups made with spatie/laravel-backup.

213427.0k2](/packages/wnx-laravel-backup-restore)[lacodix/laravel-model-filter

A Laravel package to filter, search and sort models with ease while fetching from database.

17558.6k](/packages/lacodix-laravel-model-filter)[kolossal-io/laravel-multiplex

A Laravel package to attach versioned meta data to Eloquent models.

292110.7k1](/packages/kolossal-io-laravel-multiplex)

PHPackages © 2026

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