Download the PHP package rebing/graphql-laravel without Composer

On this page you can find all versions of the php package rebing/graphql-laravel. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package graphql-laravel

Laravel GraphQL

Latest Stable Version License Tests codecov Downloads Get on Slack

This package provides a code-first integration of GraphQL for Laravel. It is based on the PHP port of GraphQL reference implementation. You define your schema entirely in PHP classes (types, queries, mutations) rather than in .graphql schema files. You can find more information about GraphQL in the Introduction to GraphQL or you can read the GraphQL specifications.

Note: GraphQL subscriptions are not supported by this package. If you need real-time push functionality, consider a dedicated solution like Lighthouse (which has subscription support) or implement subscriptions separately via Laravel broadcasting / WebSockets.

Table of Contents

Requirements

Dependency Version
PHP ^8.2
Laravel 12.x - 13.x
webonyx/graphql-php ^15.22.1

Optional dependencies:

Package Purpose
open-telemetry/api ^1.0 Required for the OpenTelemetry tracing driver
mll-lab/laravel-graphiql Interactive in-browser GraphiQL IDE

Installation

Require the package via Composer:

Publish the configuration file via Laravel artisan:

Review the configuration file:

Quick Start

Get a working GraphQL endpoint in under 5 minutes -- no database required.

1. Create a Type

Use the artisan generator to scaffold a type:

Edit the generated app/GraphQL/Types/BookType.php:

2. Create a Query

Edit app/GraphQL/Queries/BooksQuery.php:

3. Register in config

Add the type and query to the default schema in config/graphql.php:

4. Test it

Start the dev server and send a query:

Expected response:

Try filtering with an argument:

Tip: For an interactive experience, install GraphiQL (composer require mll-lab/laravel-graphiql --dev) and visit /graphiql in your browser.

Note: Introspection is disabled by default. To enable it during development (required for GraphiQL and IDE tooling), set GRAPHQL_DISABLE_INTROSPECTION=false in your .env file.

What's next?

You now have a working GraphQL API. From here you can:

Concepts

Before diving head first into code, it's good to familiarize yourself with the concepts surrounding GraphQL. If you've already experience with GraphQL, feel free to skip this part.

Typically, all queries/mutations/types are defined using the $attributes property and the args() / fields() methods as well as the resolve() method.

args/fields again return a configuration array for each field they supported. Those fields usually support these shapes

Optional keys are:

A word on declaring a field nonNull

It's quite common, and actually good practice, to see the gracious use of Type::nonNull() on any kind of input and/or output fields.

The more specific the intent of your type system, the better for the consumer.

Some examples

There exists a lot of tooling in the GraphQL ecosystem, which benefits the more specific your type system is.

Data loading

The act of loading/retrieving your data is called "resolving" in GraphQL. GraphQL itself does not define the "how" and leaves it up to the implementor.

You can use any kind of data source you like (Eloquent, static data, ElasticSearch results, caching, etc.) in your resolvers, but you need to be mindful of the execution model to avoid repetitive fetches. This library supports two strategies for optimized data loading. Dataloaders are the recommended starting point -- they work with any data source and follow the standard GraphQL community pattern for n+1 prevention. The optional rebing/graphql-laravel-select-fields package is available as an Eloquent-specific alternative that offers column-level precision.

Dataloaders (deferred resolution)

Dataloaders take advantage of the "deferred" execution model built into webonyx/graphql-php. Instead of analyzing the query upfront, each field resolver collects the keys it needs and defers the actual fetch. Once all non-deferred fields are resolved, the deferred callbacks fire, batching all collected keys into a single query.

This is the recommended approach for most applications -- it works with any data source (Eloquent, APIs, caches, etc.) and does not require any special type configuration. See Dataloaders for usage and examples.

SelectFields (Eloquent eager loading)

Note: SelectFields has been extracted to a separate optional package: rebing/graphql-laravel-select-fields. Install it via composer require rebing/graphql-laravel-select-fields to use the SelectFields class and its field configuration keys (selectable, is_relation, always, query). See the package's README for full documentation.

Choosing an approach

SelectFields Dataloaders
Data source Eloquent only Any (Eloquent, APIs, caches, etc.)
N+1 strategy Upfront eager loading via query AST analysis Deferred batching at resolve time
Column precision Selects only requested columns Typically all columns (customizable per loader)
Setup composer require rebing/graphql-laravel-select-fields Create a loader class, register in the container
Best for Eloquent-heavy apps needing column-level optimization Most applications; especially mixed data sources and cross-resolver batching

The two approaches are independent and can coexist in the same application -- use SelectFields for some resolvers and dataloaders for others.

Middleware Overview

The following middleware concepts are supported:

Briefly said, a middleware usually is a class:

HTTP middleware

Any Laravel compatible HTTP middleware can be provided on a global level for all GraphQL endpoints via the config graphql.route.middleware or on a per-schema basis via graphql.schemas.<yourschema>.middleware. The per-schema middleware overrides the global one.

GraphQL execution middleware

The processing of a GraphQL request, henceforth called "execution", flows through a set of middlewares.

They can be set on global level via graphql.execution_middleware or per-schema via graphql.schemas.<yourschema>.execution_middleware.

By default, the recommended set of middlewares is provided on the global level.

Note: the execution of the GraphQL request itself is also implemented via a middleware, which is usually expected to be called last (and does not call further middlewares). In case you're interested in the details, please see \Rebing\GraphQL\GraphQL::appendGraphqlExecutionMiddleware

GraphQL resolver middleware

After the HTTP middleware and the execution middleware is applied, the "resolver middleware" is executed for the query/mutation being targeted before the actual resolve() method is called.

See Resolver middleware for more details.

Schemas

Schemas are required for defining GraphQL endpoints. You can define multiple schemas and assign different HTTP middleware and execution middleware to them, in addition to the global middleware. For example:

Together with the configuration, in a way the schema defines also the route by which it is accessible. Per the default configuration of prefix = graphql, the default schema is accessible via /graphql.

Route attributes

You can customize the HTTP route generated for a specific schema using the route_attributes key. This is useful for setting parameters supported by Laravel routes, e.g. a custom domain. The attributes are merged into the route's action array, so standard Laravel route attributes like domain, prefix, as (route name), and where (parameter constraints) are all supported.

Schema classes

You may alternatively define the configuration of a schema in a class that implements ConfigConvertible.

In your config, you can reference the name of the class, rather than an array.

You can use the php artisan make:graphql:schemaConfig command to create a new schema configuration class automatically.

Creating a query

First you usually create a type you want to return from the query. The Eloquent 'model' is only required if specifying relations.

Note: If you use the rebing/graphql-laravel-select-fields package, you can set 'selectable' => false on computed/virtual fields that don't correspond to a database column (e.g. accessors, custom resolvers).

The best practice is to start with your schema in config/graphql.php and add types directly to your schema (e.g. default):

Alternatively you can:

Then you need to define a query that returns this type (or a list). You can also specify arguments that you can use in the resolve method.

Add the query to the config/graphql.php configuration file

Fetching a single record

And that's it. You should be able to query GraphQL with a POST request to the url /graphql (or anything you choose in your config). Try a POST request with the following query input

Note: The resolve() method supports dependency injection for parameters beyond the first three ($root, $args, $context). You can typehint ResolveInfo $info to receive the GraphQL resolve info. Any other class typehint will be resolved from Laravel's service container. External packages can register custom parameter injectors via Field::registerParameterInjector(). See Resolve method for full details.

For example, using curl:

Creating a mutation

A mutation is like any other query. It accepts arguments and returns an object of a certain type. Mutations are meant to be used for operations modifying (mutating) the state on the server (which queries are not supposed to perform).

This is conventional abstraction, technically you can do anything you want in a query resolve, including mutating state.

For example, a mutation to update the password of a user. First you need to define the Mutation:

As you can see in the resolve() method, you use the arguments to update your model and return it.

You should then add the mutation to the config/graphql.php configuration file:

You can then use the following query on your endpoint to do the mutation:

For example, using curl:

Mutation with inline validation rules

You can attach Laravel validation rules directly to each argument via the 'rules' key. This is often simpler than overriding the rules() method for straightforward validations:

See Validation for more advanced rule definitions including the rules() method, callable rules, and nested input type rules.

File uploads

This library uses https://github.com/laragraph/utils which is compliant with the spec at https://github.com/jaydenseric/graphql-multipart-request-spec .

You have to add the \Rebing\GraphQL\Support\UploadType first to your config/graphql schema types definition (either global or in your schema):

It is important that you send the request as multipart/form-data:

WARNING: when you are uploading files, Laravel will use FormRequest - it means that middlewares which are changing request, will not have any effect.

Note: You can test your file upload implementation using Altair as explained here.

Vue.js example

Vanilla JavaScript

Validation

Laravel's validation is supported on queries, mutations, input types and field arguments.

Note: The support is "sugar on top" and is provided as a convenience. It may have limitations in certain cases, in which case regular Laravel validation can be used in your respective resolve() methods, just like in regular Laravel code.

Adding validation rules is supported in the following ways:

Using the configuration key 'rules' is very convenient, as it is declared in the same location as the GraphQL type itself. However, you may hit certain restrictions with this approach (like multi-field validation using *), in which case you can override the rules() method.

Example defining rules in each argument

Example using the rules() method

Example using Laravel's validator directly

Calling validate() in the example below will throw Laravel's ValidationException which is handed by the default error_formatter by this library:

The format of the 'rules' configuration key, or the rules returned by the rules() method, follows the same convention that Laravel supports, e.g.:

For the args() method or the 'args' definition for a field, the field names are directly used for the validation. However, for input types, which can be nested and occur multiple times, the field names are mapped as e.g. data.0.fieldname. This is important to understand when returning rules from the rules() method.

Handling validation errors

Exceptions are used to communicate back in the GraphQL response that validation errors occurred. When using the built-in support, the exception \Rebing\GraphQL\Error\ValidationError is thrown. In your custom code or when directly using the Laravel Validator, Laravel's built-in \Illuminate\Validation\ValidationException is supported too. In both cases, the GraphQL response is transformed to the error format shown below.

To support returning validation errors in a GraphQL error response, the 'extensions' are used, as there's no proper equivalent.

On the client side, you can check if message for a given error matches 'validation', you can expect the extensions.validation key which maps each field to their respective errors:

You can customize the way this is handled by providing your own error_formatter in the configuration, replacing the default one from this library.

Customizing error messages

The validation errors returned can be customised by overriding the validationErrorMessages method. This method should return an array of custom validation messages in the same way documented by Laravel's validation. For example, to check an email argument doesn't conflict with any existing data, you could perform the following:

Note: the keys should be in field_name.validator_type format, so you can return specific errors per validation type.

Customizing attributes

The validation attributes can be customised by overriding the validationAttributes method. This method should return an array of custom attributes in the same way documented by Laravel's validation.

Cross-field validation rules in nested input types

When using Laravel validation rules that reference sibling fields (like prohibits, required_without, required_if, etc.) within an InputType, the library automatically transforms those references into fully-qualified dot-notation paths that Laravel's Validator can resolve correctly.

For example, given an InputType:

Used in a mutation as a list:

The prohibits:mintParams rule on recipients.0.createParams is automatically transformed to prohibits:recipients.0.mintParams so that Laravel's Validator correctly resolves the sibling field reference.

This applies to all dependent rules including prohibits, required_with, required_with_all, required_without, required_without_all, present_with, present_with_all, missing_with, missing_with_all, exclude_with, exclude_without, same, different, required_if, required_unless, prohibited_if, prohibited_unless, exclude_if, exclude_unless, accepted_if, declined_if, present_if, present_unless, missing_if, missing_unless, required_if_accepted, required_if_declined, prohibited_if_accepted, prohibited_if_declined, and comparison rules like gt, gte, lt, lte, before, after, before_or_equal, after_or_equal (when they reference a sibling field). For rules like required_if that take both a field reference and a value (e.g. required_if:mode,advanced), only the field reference parameter is transformed. See RulesPrefixer for the full list.

Disabling automatic prefixing: If you need to opt out of this behavior for a specific query or mutation, override processCollectedRules():

Misc notes

Certain type declarations of GraphQL may cancel our or render certain validations unnecessary. A good example is using Type::nonNull() to ultimately declare that an argument is required. In such a case a 'rules' => 'required' configuration will likely never be triggered, because the GraphQL execution engine already prevents this field from being accepted in the first place.

Or to be more clear: if a GraphQL type system violation occurs, then no Laravel validation will be even executed, as the code does not get so far.

Resolve method

The resolve method is used in both queries and mutations, and it's here that responses are created.

The first three parameters to the resolve method are hard-coded:

  1. The $root object this resolve method belongs to (can be null)
  2. The arguments passed as array $args (can be an empty array)
  3. The query specific GraphQL context Can be customized by implementing a custom "execution middleware", see \Rebing\GraphQL\Support\ExecutionMiddleware\AddAuthUserContextValueMiddleware for an example.

Arguments here after will be attempted to be injected, similar to how controller methods works in Laravel.

You can typehint any class that you will need an instance of.

There are two hardcoded classes which depend on the local data for the query:

Example:

Resolver middleware

These are GraphQL specific resolver middlewares and are only conceptually related to Laravel's "HTTP middleware". The main difference:

Defining middleware

To create a new middleware, use the make:graphql:middleware Artisan command

This command will place a new ResolvePage class within your app/GraphQL/Middleware directory. In this middleware, we will set the Paginator current page to the argument we accept via our PaginationType:

Registering middleware

If you would like to assign middleware to specific queries/mutations, list the middleware class in the $middleware property of your query class.

If you want a middleware to run during every GraphQL query/mutation to your application, list the middleware class in the $middleware property of your base query class.

Alternatively, you can override getMiddleware to supply your own logic:

If you want to register middleware globally, use the resolver_middleware_append key in config/graphql.php (defaults to null, treated as an empty array):

You can also use the appendGlobalResolverMiddleware method in any ServiceProvider:

If your middleware needs to wrap all other resolver middleware (including per-field middleware), use prependGlobalResolverMiddleware instead:

The resulting pipeline order is: prepended global middleware, per-field middleware, appended global middleware. This is used internally by the tracing system but is available for any middleware that must run outermost.

Terminable middleware

Sometimes a middleware may need to do some work after the response has been sent to the browser. If you define a terminate method on your middleware and your web server is using FastCGI, the terminate method will automatically be called after the response is sent to the browser:

The terminate method receives both the resolver arguments and the query result.

Once you have defined a terminable middleware, you should add it to the list of middleware in your queries and mutations.

Authorization

For authorization similar to Laravel's Request (or middleware) functionality, we can override the authorize() function in a Query or Mutation.

Important: The authorize() method must return exactly true (strict comparison) for the request to proceed. Returning other truthy values (e.g. 1, "yes") will be treated as unauthorized.

Note: Authorization is checked before validation rules are evaluated. This prevents unauthenticated users from probing validation rules to discover API structure.

An example of Laravel's 'auth' middleware:

Or we can make use of arguments passed via the GraphQL query:

You can also provide a custom error message when the authorization fails (defaults to Unauthorized):

If you share the same authorization logic across multiple queries or mutations, extract it into a reusable trait:

Then use it on any query or mutation:

Privacy

You can set custom privacy attributes for every Type's Field. If a field is not allowed, null will be returned. Privacy is enforced at the field resolver level, so it works universally - whether the type is a root query result or a nested sub-type.

The privacy callback receives four arguments: the root value ($root - the parent object being resolved), the field's own arguments ($args), the query context ($ctx), and optionally the ResolveInfo ($info).

Using a closure:

Using a Privacy class:

You can also create a class that extends the abstract Privacy class:

Then reference it by class name on the field:

Using field arguments in a privacy check:

If the field declares its own args, they are available in $args:

$root - the parent object. This is the result of the parent field's resolver. For fields on a UserType resolved from a query returning Eloquent models, $root will be the User model instance. This allows per-row privacy decisions (e.g. only show a field if the current user "owns" the object).

$args - field arguments, not query arguments. The $args parameter contains the arguments declared on the field itself (via the args key). If the field declares no arguments, $args will be an empty array. These are not the root query/mutation arguments.

$ctx - the query context value. This is the context value passed to the GraphQL execution. By default, the built-in AddAuthUserContextValueMiddleware execution middleware sets this directly to the authenticated user model (i.e. Auth::user()), or null if no user is authenticated. The guard is resolved in this order: graphql.schemas.<name>.group_attributes.guard (per-schema), then the global graphql.route.group_attributes.guard, then the application's default guard. This keeps the GraphQL context consistent with the guard that authenticated the route — important in multi-guard apps where the default guard (typically web) and the schema's actual guard (e.g. api) differ. You can customize the context via your own execution middleware.

Privacy vs Authorization. authorize() on a Query or Mutation gates the entire operation - if it fails, the whole request is rejected with an error. privacy on a Type field gates individual fields and silently returns null when denied. Use authorize() for access control on operations and privacy for field-level visibility within types.

Caution with non-null fields. When privacy denies access, the field resolver returns null. If the field is typed as Type::nonNull(...), this null violates the GraphQL non-null contract and causes an error that propagates up to the nearest nullable parent. Always use nullable types for privacy-protected fields.

Query variables

GraphQL offers you the possibility to use variables in your query so you don't need to "hardcode" value. This is done like that:

When you query the GraphQL endpoint, you can pass a JSON encoded variables parameter.

For example, using curl:

Custom field

You can also define a field as a class if you want to reuse it in multiple types.

You can then use it in your type declaration

Even better reusable fields

Instead of using the class name, you can also supply an actual instance of the Field. This allows you to not only re-use the field, but will also open up the possibility to re-use the resolver.

Let's imagine we want a field type that can output dates formatted in all sorts of ways.

You can use this field in your type as follows:

Dataloaders

Dataloaders are the standard GraphQL pattern for solving n+1 problems and are the recommended default data loading strategy. They use deferred resolution -- a mechanism built into webonyx/graphql-php, the GraphQL engine this library is built on. Dataloaders do not require Eloquent models or special type configuration, and they work with any data source.

The pattern has two phases:

  1. Collect -- each field resolver registers the key it needs (e.g. a user ID) and returns a GraphQL\Deferred instead of a value.
  2. Batch -- once all non-deferred fields are resolved, the deferred callbacks fire. The first callback triggers a bulk fetch for all collected keys; the rest read from the already-loaded result.

Creating a loader

A loader is a plain PHP class that accumulates keys and performs a single bulk query when triggered. Registering it as a scoped singleton ensures a fresh instance per request (safe for Laravel Octane and queue workers).

Register it in a service provider:

Using a loader in a type

Resolve the loader from the container and call load() with the key. The returned Deferred is handled transparently by the GraphQL execution engine -- no changes to your schema, middleware, or controller are required.

If a query requests 50 posts, the author resolver is called 50 times -- but only one SELECT * FROM users WHERE id IN (...) query is executed, because all 50 IDs are collected during the "collect" phase and fetched together when the first Deferred callback fires.

How it works

GraphQL\Deferred is a synchronous promise provided by webonyx/graphql-php. When a resolver returns a Deferred, the executor sets the value aside and continues resolving other fields. Once no more immediate fields remain, it drains the deferred queue: each callback runs, and if any callback returns another Deferred, that is queued too. This continues until all values are fully resolved.

Because this library's execution middleware calls GraphQL::executeQuery() (which uses the built-in SyncPromiseAdapter internally), Deferred works out of the box with no additional configuration.

Using a DataLoader library

For applications with many loaders, the overblog/dataloader-php library provides a higher-level DataLoader class with automatic request batching, per-request memoization, and cache priming. It ships with a webonyx/graphql-php sync promise adapter. For most Laravel applications the simple loader pattern shown above is sufficient, but overblog/dataloader-php can reduce boilerplate when you have dozens of entity types to batch-load.

Eager loading relationships

Note: The SelectFields class has been moved to the separate rebing/graphql-laravel-select-fields package. Install it with composer require rebing/graphql-laravel-select-fields to use Eloquent-optimized field selection and eager loading in your resolvers. See the package's README for full documentation.

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. If you use the rebing/graphql-laravel-select-fields package, set the is_relation attribute to false in your Type to prevent SelectFields from treating it as an Eloquent relation:

Field deprecation

Sometimes you would want to deprecate a field but still have to maintain backward compatibility until clients completely stop using that field. You can deprecate a field using directive. If you add deprecationReason to field attributes it will become marked as deprecated in GraphQL documentation. You can validate schema on client using Apollo GraphOS.

Default field resolver

It's possible to override the default field resolver provided by the underlying webonyx/graphql-php library using the config option defaultFieldResolver.

You can define any valid callable (static class method, closure, etc.) for it:

The parameters received are your regular "resolve" function signature.

Macros

If you would like to define some helpers that you can re-use in a variety of your queries, mutations and types, you may use the macro method on the GraphQL facade.

For example, from a service provider's boot method:

The macro function accepts a name as its first argument, and a Closure as its second.

Automatic Persisted Queries support

Automatic Persisted Queries (APQ) improve network performance by sending smaller requests, with zero build-time configuration.

APQ is disabled by default and can be enabled in the config via apq.enable=true or by setting the environment variable GRAPHQL_APQ_ENABLE=true.

A persisted query is an ID or hash that can be generated on the client sent to the server instead of the entire GraphQL query string. This smaller signature reduces bandwidth utilization and speeds up client loading times. Persisted queries pair especially with GET requests, enabling the browser cache and integration with a CDN. Note that GET requests are disabled by default; to use APQ with GET, you must explicitly set 'method' => ['GET', 'POST'] in your schema configuration.

Behind the scenes, APQ uses Laravel's cache for storing / retrieving the queries. They are parsed by GraphQL before storing, so re-parsing them again is not necessary. Please see the various options there for which cache, prefix, TTL, etc. to use.

Note: it is advised to clear the cache after a deployment to accommodate for changes in your schema!

For more information see:

Note: the APQ protocol requires the hash sent by the client being compared with the computed hash on the server. In case a mutating middleware like TrimStrings is active and the query sent contains leading/trailing whitespaces, these hashes can never match resulting in an error.

In such case either disable the middleware or trim the query on the client before hashing.

Notes

Client example

Below a simple integration example with Vue 3 and Apollo Client, where createPersistedQueryLink automatically manages the APQ flow.

Tracing / Observability

GraphQL operations can be instrumented with timing data by configuring a tracing driver. Tracing is disabled by default ('driver' => null).

The built-in OpenTelemetryTracingDriver emits spans via the OpenTelemetry API following the GraphQL semantic conventions. It requires the open-telemetry/api ^1.0 package.

Enabling OpenTelemetry

Install the OpenTelemetry API package first:

Then configure the driver:

Without an OTel SDK configured, all spans are automatically no-ops.

To actually collect and export spans, you need to install and configure the OpenTelemetry PHP SDK along with an exporter for your backend. The Getting Started guide walks through a complete example. Once the SDK is configured, the driver automatically picks up the global TracerProvider - no additional wiring is needed in this package.

Per-field resolver tracing

By default, only the top-level operation is traced. To instrument individual field resolvers, enable field_tracing:

With OpenTelemetry this creates a child span for each resolved field.

When tracing is enabled (i.e. a driver is configured), the tracing execution and resolver middlewares (TracingExecutionMiddleware and TracingResolverMiddleware) are automatically registered - you do not need to add them to the execution_middleware or resolver_middleware_append config arrays manually.

Note: Field tracing produces high-cardinality data and is intended for development/debugging. Use it with caution in production.

Custom tracing drivers

You can implement the Rebing\GraphQL\Support\Tracing\TracingDriver interface to create your own driver. The interface has four methods:

Register your driver class in the tracing.driver config key and it will be resolved from the Laravel service container. If the driver constructor accepts an array $driverOptions parameter, it will receive the merged driver_options from the global and per-schema tracing config.

Per-schema tracing

By default, the global tracing configuration applies to every schema. You can override tracing on a per-schema basis by adding a tracing key inside the schema's config array.

Disable tracing for a specific schema:

Enable tracing only for a specific schema (no global driver):

Override driver options per schema (deep-merged over global):

Per-schema tracing arrays are deep-merged over the global config: schema values win for top-level keys, and driver_options is merged separately so you can override individual options without repeating the full array.

Security

GraphQL APIs have a different attack surface than REST APIs. A single endpoint accepts arbitrary queries, so without safeguards a client can craft deeply nested or highly complex queries that exhaust server resources.

Introspection

Schema introspection lets clients discover your entire type system -- every query, mutation, field, and argument. This is essential for development tooling (GraphiQL, IDE plugins, codegen) but exposes your full API surface in production.

Introspection is disabled by default:

Set GRAPHQL_DISABLE_INTROSPECTION=false in your .env during development.

Query depth limiting

Deeply nested queries can cause excessive resolver calls and memory usage. The query_max_depth option rejects queries that exceed the allowed nesting level:

For example, with a depth limit of 3, the query { users { posts { comments { author { name } } } } } would be rejected because it nests 4 levels deep.

Tune this based on your schema's legitimate nesting requirements. Start strict and increase only if real queries require it.

Query complexity analysis

Complex queries (many fields, large lists) can be expensive even when shallow. The query_max_complexity option assigns a cost to each resolved field and rejects queries that exceed the budget:

You can assign custom complexity to individual fields using the complexity callback supported by webonyx/graphql-php:

See the webonyx/graphql-php security documentation for full details on how complexity is calculated.

Batching limits

When batching is enabled, clients can send multiple operations in a single HTTP request. Without a cap, this can be used to amplify the impact of expensive queries. Batching is disabled by default, and when enabled the batching.max_batch_size option (default: 10) limits the number of operations per request.

GET requests and read-only enforcement

By default each schema only accepts POST ('method' => ['POST']). If you opt a schema in to GET (e.g. for CDN-cacheable persisted queries), enable the ReadOnlyOperationMiddleware execution middleware so that mutations and subscriptions submitted via GET are rejected.

The middleware is shipped pre-listed (commented out) in the published config:

Without this middleware, an operation submitted via GET is executed regardless of its type, which leaks mutation arguments into URLs, server access logs and CDN caches and bypasses defenses that assume GET is safe. This mirrors the protection that webonyx/graphql-php's Server\Helper::executeOperation() applies; we enforce it explicitly because the library calls GraphQL::executeQuery() directly.

The rejection produces the standard GraphQL error GET supports only query operation with HTTP 200. The middleware is opt-in so consumers running with the default POST-only configuration are unaffected.

Ordering matters when APQ is enabled. When used together with AutomaticPersistedQueriesMiddleware, list ReadOnlyOperationMiddleware after it (as shown above). APQ materialises the query body from the cache; running this middleware first against an APQ-only request would cause OperationParams::getParsedQuery() to throw No GraphQL query available. The ordering relative to GraphqlExecutionMiddleware is enforced by the framework, which always appends GraphqlExecutionMiddleware last.

CSRF protection

When your GraphQL endpoint uses cookie-based authentication (Laravel sessions, Sanctum cookie-mode), it is vulnerable to Cross-Site Request Forgery. A malicious page can submit a form POST to your /graphql endpoint and the browser will attach the session cookie automatically -- executing mutations on behalf of the logged-in user.

This does not apply when authentication uses explicit credentials only (Bearer tokens, API keys) because browsers never attach those automatically.

The CsrfGuard middleware protects against this by ensuring that every request either came from the same origin (verified via the browser's Sec-Fetch-Site header), or carries indicators that would have forced a CORS preflight (custom headers, non-simple Content-Type):

The middleware is opt-in and has strict safe defaults. See the PHPDoc on CsrfGuard::using() for a full explanation of each configurable flag.

Recommended production configuration

Additional measures to consider at the infrastructure level:

Error handling

This library has two distinct error layers:

Built-in error types

Class Category When thrown
ValidationError validation Argument validation rules fail (via rules() or inline 'rules' key)
AuthorizationError authorization authorize() returns anything other than true
AutomaticPersistedQueriesError apq APQ hash mismatch, query not found, or APQ disabled

Error response format

Errors are returned in the standard GraphQL errors array. The library enriches each error with an extensions key:

For AuthorizationError, the response contains extensions.category set to "authorization" and the message from getAuthorizationMessage() (defaults to "Unauthorized").

Error reporting

The default errors_handler selectively reports errors to Laravel's exception handler:

Customizing error formatting

You can replace the default error formatter and/or error handler via config:

The default formatter (GraphQL::formatError) respects app.debug: when debug mode is enabled, errors include debugMessage and trace fields for easier development. In production these are omitted.

Tip: Laravel's built-in ValidationException (thrown by Validator::validate()) is also handled by the default formatter -- it is automatically converted to the same extensions.validation format shown above.

Misc features

Detecting unused variables

By default, 'variables' provided alongside the GraphQL query which are not consumed, are silently ignored.

If you consider the hypothetical case you have an optional (nullable) argument in your query, and you provide a variable argument for it but you make a typo, this can go unnoticed.

Example:

Variables provided:

In this case, nothing happens and optional_id will be treated as not being provided.

To prevent such scenarios, you can add the UnusedVariablesMiddleware to your execution_middleware.

Configuration options

Option Default Description
route.prefix graphql URL prefix for GraphQL endpoints (without leading /)
route.controller Built-in Override the default controller class (supports string and array format)
route.middleware [] Global HTTP middleware for all schemas (unless overridden per-schema)
route.group_attributes [] Additional route group attributes
default_schema 'default' Name of the default schema when none is specified via the route
batching.enable false Enable/disable GraphQL batching
batching.max_batch_size 10 Max operations per batch (null for no limit)
error_formatter Built-in Callable receiving each Error object; must return an array
errors_handler Built-in Custom error handling; default passes exceptions to Laravel's error handler
security.query_max_complexity 500 Maximum allowed query complexity. See graphql-php security docs
security.query_max_depth 13 Maximum allowed query depth
security.disable_introspection true Disable schema introspection (env: GRAPHQL_DISABLE_INTROSPECTION)
pagination_type Built-in Custom pagination type class
simple_pagination_type Built-in Custom simple pagination type class
cursor_pagination_type Built-in Custom cursor pagination type class
defaultFieldResolver null Override the default field resolver
headers [] Headers added to responses from the default controller
json_encoding_options 0 JSON encoding options for responses from the default controller
apq.enable false Enable Automatic Persisted Queries
apq.cache_driver App default Cache driver for APQ (defaults to your app's cache.default driver; env: GRAPHQL_APQ_CACHE_DRIVER)
apq.cache_prefix '{cache.prefix}:graphql.apq' Cache key prefix for persisted queries
apq.cache_ttl 300 Cache TTL in seconds for persisted queries
schemas Defines available schemas and their settings. See Schemas
schemas.*.query [] Array of query classes for this schema
schemas.*.mutation [] Array of mutation classes for this schema
schemas.*.types [] Array of type classes scoped to this schema
schemas.*.middleware - Per-schema HTTP middleware (overrides route.middleware)
schemas.*.method ['POST'] HTTP methods to support (must be uppercase)
schemas.*.execution_middleware - Per-schema execution middleware (overrides global execution_middleware)
schemas.*.route_attributes [] Additional Laravel route attributes (e.g. domain, prefix)
schemas.*.controller - Override the controller for this schema
schemas.*.tracing - Per-schema tracing overrides
types [] Global types shared across all schemas. See Creating a query
execution_middleware Built-in set Global execution middleware classes. Terminal middleware is always appended automatically
resolver_middleware_append null Global resolver middleware appended after per-field middleware
tracing.driver null Tracing driver class (null = disabled). Built-in: OpenTelemetryTracingDriver. See Tracing
tracing.field_tracing false Instrument individual field resolvers
tracing.driver_options [] Array of options passed to the driver constructor (e.g. 'include_document' => true)

Performance considerations

Wrap Types

You can wrap types to add more information to the queries and mutations. Similar to how pagination works, you can do the same with your extra data that you want to inject. For instance, in your query:

Using wrap types with SelectFields

If you use the rebing/graphql-laravel-select-fields package with a query that returns a wrap type, your wrapper class must implement the WrapType marker interface from that package. See the SelectFields package documentation for details.

Known limitations

SelectFields related (see rebing/graphql-laravel-select-fields)

GraphQL testing clients

You can interact with your GraphQL API using any of these clients:

Client Notes
GraphiQL The reference GraphQL IDE. Use the laravel-graphiql package for seamless in-app integration (/graphiql route)
Altair Feature-rich desktop/browser client with file upload support, environments, and pre-request scripts
Postman Has native GraphQL support with schema introspection, auto-complete, and variable management
Insomnia Lightweight REST/GraphQL client with schema fetching and query auto-complete
Bruno Open-source, offline-first API client with GraphQL support. Collections are stored as files, making them easy to version control

Tip: Most of these clients rely on schema introspection. Introspection is disabled by default in this package. Set GRAPHQL_DISABLE_INTROSPECTION=false in your .env during development to enable it.

Testing

You can test your GraphQL API using Laravel's built-in HTTP testing helpers. No additional packages are required.

Querying an endpoint

Use postJson to send a GraphQL request and assert the response:

Using query variables

Testing mutations

Testing a non-default schema

Pass the schema name as part of the URL path:

Asserting errors

Tip: For database-backed tests, use Laravel's RefreshDatabase or DatabaseTransactions trait as you would in any feature test.

Upgrading

For upgrade guides, see UPGRADE.md:

Credits

This project was originally forked from Folklore's laravel-graphql.


All versions of graphql-laravel with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
ext-json Version *
illuminate/contracts Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
laragraph/utils Version ^2.0.1
thecodingmachine/safe Version ^3.0
webonyx/graphql-php Version ^15.31.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package rebing/graphql-laravel contains the following files

Loading the files please wait ...