Download the PHP package ayimdomnic/laragraph without Composer
On this page you can find all versions of the php package ayimdomnic/laragraph. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package laragraph
Laragraph
A modern, feature-rich GraphQL package for Laravel.
Laragraph gives Laravel developers a clean, expressive, code-first API for building GraphQL services โ powered by webonyx/graphql-php.
๐ Read the developer guide: a step-by-step explanation of every feature, from your first query to production. ๐งช Explore the example app: a complete API that uses every feature, with a test for each one.
Table of Contents
- Features
- Requirements
- Why Laragraph?
- Installation
- Quick Start
- Native PHP Enums
- GraphiQL
- Artisan Generators
- Deploying
- Pagination
- N+1-Safe Eloquent Relations
- Authorization
- Validation
- Error Handling & Localization
- Built-in Scalars
- Multiple Schemas
- Security
- GraphQL over HTTP
- Response Cache
- Persisted Queries
- Batched Queries
- File Uploads
- Facade
- Subscriptions
- Tracing
- Development
- Contributing
- License
Features
| Capability | Status |
|---|---|
| Queries & Mutations | โ |
| Real-time Subscriptions (Laravel Broadcasting) | โ |
| Object / Input / Enum / Interface / Union types | โ |
| Native PHP enums as GraphQL enums | โ |
| Custom scalars (DateTime, Date, JSON, Upload) | โ |
| Built-in argument validation (Laravel rules) | โ |
| Per-field authorization | โ |
| N+1-safe Eloquent relation batching | โ |
| Relay cursor pagination + simple paginator | โ |
| Batched queries | โ |
| Automatic Persisted Queries + trusted-documents mode | โ |
GraphQL-over-HTTP (application/graphql-response+json, no mutations over GET) |
โ |
| Per-user response cache | โ |
| File uploads (multipart spec) | โ |
| Multiple named schemas | โ |
| Query complexity & depth limiting | โ |
| Introspection toggle | โ |
| Per-field tracing (Apollo Tracing format) | โ |
| GraphiQL browser IDE | โ |
| Artisan generators | โ |
Auto-discovery, cacheable via php artisan optimize |
โ |
php artisan about integration + laragraph:validate deploy check |
โ |
| PHPStan level 8, PER-CS, 100% line coverage | โ |
Requirements
- PHP 8.2 โ 8.5
- Laravel 10 / 11 / 12 / 13 (Laravel 13 requires PHP 8.3+)
Every combination Laravel itself supports is tested in CI.
Why Laragraph?
Laragraph is code-first: types, queries and mutations are plain PHP classes, so your IDE, refactoring tools and PHPStan understand your whole API. On top of webonyx/graphql-php it adds the parts a Laravel team otherwise builds by hand:
- Laravel-native everything โ validation rules, policies and guards, Broadcasting for
subscriptions, cache stores,
php artisan about,optimize, and generators. - Performance by default โ N+1-safe Eloquent relation batching through the model's own eager loading, a per-user response cache, persisted queries and a cached discovery manifest.
- Secure by default โ depth/complexity/alias limits, no mutations over GET, trusted-documents mode, and per-user cache partitioning so one user's data is never served to another.
- Modern PHP โ native enums, readonly value objects, strict types throughout and no dynamic properties (deprecated since PHP 8.2).
Installation
Laravel auto-discovers the package. Publish the config:
Quick Start
1. Create a Type
2. Create a Query
3. Create a Mutation
4. Register in config/laragraph.php
5. Make requests
Native PHP Enums
Register a backed or pure enum directly โ no wrapper class needed:
Resolvers return enum cases and receive cases for enum arguments. #[Description] and
#[Deprecated] attributes on the enum and its cases are exposed through introspection. An
EnumType subclass may also simply return UserStatus::cases(); from values().
Enums (and input, interface, union and scalar types) placed in app/GraphQL/Types are
auto-discovered โ including subdirectories such as app/GraphQL/Types/Billing/.
GraphiQL
Built-in browser IDE at /graphql/graphiql. By default ('enabled' => null) it is only served
while app.debug is on โ never in production.
Artisan Generators
| Command | Creates |
|---|---|
laragraph:make:type UserType |
app/GraphQL/Types/UserType.php |
laragraph:make:query UsersQuery |
app/GraphQL/Queries/UsersQuery.php |
laragraph:make:mutation CreateUserMutation |
app/GraphQL/Mutations/CreateUserMutation.php |
laragraph:make:subscription UserCreatedSubscription |
app/GraphQL/Subscriptions/UserCreatedSubscription.php |
laragraph:make:input CreateUserInput |
app/GraphQL/Types/Inputs/CreateUserInput.php |
laragraph:make:exception InvalidCredentialsException |
app/GraphQL/Exceptions/InvalidCredentialsException.php |
laragraph:make:loader UserLoader |
app/GraphQL/Loaders/UserLoader.php โ a custom BatchResolver (see docs/05) |
laragraph:scaffold User --with-crud |
Type, queries and CRUD mutations for a model โ deny-by-default (see below) |
laragraph:schema:export --output=schema.graphql |
SDL for client code generation / schema diffing |
laragraph:schema:diff --against=schema.graphql |
CI gate: fails on breaking changes vs. a committed baseline (see Deployment) |
Scaffolded code is deny-by-default: every generated query and mutation calls
Gate::allows() for the matching policy ability (viewAny, view, create, update,
delete), so nothing is reachable until you write a policy, and attributes in the model's
$hidden list (passwords, tokens) are never added to the generated type.
Deploying
Like Laravel's event and route caches, a cached manifest is not refreshed automatically โ rerun
laragraph:cache (or optimize) when you add GraphQL classes.
Pagination
Relay Cursor Pagination
Pass endCursor as after to fetch the next page, or startCursor as before with last to
walk backwards; the page size may change between requests. Page sizes are capped by
laragraph.pagination.max_per_page (default 100; null removes the cap). Eloquent builders,
query builders and relations are paged with exact offsets; any other object with Laravel's
paginate() signature supports page-aligned windows.
Simple Offset Pagination
Node re-fetching
A generic node(id: ID!): Node root field for Relay clients โ register
Ayimdomnic\Laragraph\Relay\NodeQuery, then override resolveNode() on any type
implementing Node. See Pagination โ Relay Node re-fetching.
N+1-Safe Eloquent Relations
Every GraphQL request gets a fresh DataLoaderRegistry attached to $context. For hand-written batch loaders, extend BatchResolver:
For HTTP requests $context is an Ayimdomnic\Laragraph\Http\GraphQLContext โ a regular
Illuminate\Http\Request (input, headers, user(), session all work) with declared slots for
Laragraph's per-request state, so no dynamic properties are ever added to Laravel's request.
For a plain Eloquent relation, skip the hand-written loader entirely โ Type::batchRelation() batches it through the relation's own eager-loading machinery (the same code path Model::with() uses), so it works for belongsTo, hasOne, hasMany, belongsToMany, and morph relations alike:
Regardless of how many Post parents are in the result set, comments resolves in one query per request instead of one query per post. The relation is loaded onto the parent models your resolver already returned, so relations you eager-loaded yourself (Post::with('comments')) cost no query at all, and parents hidden by global scopes still resolve.
Authorization
false โ AuthorizationException โ extensions.category = 'authorization'.
Or delegate to a Laravel policy โ return the policy class or the model it guards:
Policies registered with the Gate go through it (so Gate::before() hooks apply); other policy
classes are called directly, honouring their own before(). Guests are denied unless the policy
method accepts a nullable user.
Validation
Errors appear in extensions.validation:
Error Handling & Localization
Throw a GraphQLException for domain/business errors โ it's always client-safe, comes with a
machine-readable extensions.code, and its message is resolved through Laravel's translator:
Generate one with php artisan laragraph:make:exception OutOfStockException. Any exception
implementing graphql-php's ClientAware + ProvidesExtensions (which GraphQLException,
ValidationException and AuthorizationException all do) gets picked up by formatError()
automatically โ no instanceof chain to maintain.
Localization is opt-in and off by default. Turn it on to translate messages per request based
on the Accept-Language header, restricted to an allow-list:
Publish and translate lang/en/errors.php (php artisan vendor:publish --tag=laragraph-lang), and
add your app's own lang/{locale}/errors.php for messages passed to GraphQLException. See
Error Handling & Localization for the full guide,
including the supported_locales allow-list security note.
Built-in Scalars
Date accepts YYYY-MM-DD (parsed as midnight). DateTime accepts ISO-8601 with or without
fractional seconds (2024-01-15T09:30:00.123Z, as JavaScript's toISOString() produces), the SQL
format 2024-01-15 09:30:00, and plain dates. Impossible values such as 2024-02-31 are rejected
rather than rolled over.
Multiple Schemas
Endpoints: POST /graphql and POST /graphql/admin.
Security
Secure by default โ these are the shipped values:
Set a limit to null to remove it, or disable_introspection to true/false to force it.
Each named schema only contains the types registered for it (globally or under its own
types key); an admin-only type is never visible through another schema's introspection.
See also trusted documents.
GraphQL over HTTP
Laragraph follows the GraphQL-over-HTTP specification:
GETexecutes queries only; mutations overGETreceive405 Method Not Allowed(they would otherwise be exploitable via CSRF).- Clients sending
Accept: application/graphql-response+jsonget that media type, with a4xxstatus when a request fails before execution (parse/validation errors). Plainapplication/jsonclients keep the traditional always-200behaviour. - Request bodies may be
application/json,application/graphql, form-encoded or multipart. - Malformed requests (a non-string
query,variablesthat are not an object, unparseable JSON, a non-object batch entry) get400withextensions.code: BAD_REQUEST; an unknown schema in the URL gets404withSCHEMA_NOT_FOUND. - Requests rejected before execution carry a machine-readable
extensions.code(BAD_REQUEST,SCHEMA_NOT_FOUND,METHOD_NOT_ALLOWED,PERSISTED_QUERY_NOT_FOUND,PERSISTED_QUERY_REQUIRED, โฆ).
Response Cache
Only query operations are cached โ the operation type is read from the parsed document, so
comments or operationName cannot smuggle a mutation into the cache. With the default user
scope, entries are partitioned per authenticated user (guests share one partition). Invalidate
everything with ResponseCache::flush().
Persisted Queries
- Automatic Persisted Queries โ compatible with Apollo Client's persisted-queries link: the
client sends a SHA-256 hash, and on
PersistedQueryNotFoundretries with the full query, which is then stored (hashes are verified). Works overGETtoo. - Trusted documents โ with
'only' => true, only query text already stored under its hash is executed. Pre-populate the store at deploy time (or use thearraystore) to restrict your API to the operations your own clients ship.
Batched Queries
File Uploads
Follows the GraphQL multipart request spec.
Facade
Subscriptions
webonyx/graphql-php has no subscription transport of its own, so Laragraph provides one on top of Laravel Broadcasting: the initial subscription request registers a subscriber and returns a channel; your app code later calls Laragraph::broadcast() to push a live update to every subscriber on that channel.
Trigger an update from anywhere โ typically at the end of a mutation:
Client flow:
-
POST the subscription operation like any other query:
The response carries no data yet โ instead:
- Listen for updates on that subscriber's private channel with Laravel Echo:
Delivery uses whichever broadcast driver your app has configured (Reverb, Pusher, โฆ) โ Laragraph only decides the channel and payload shape.
Queued fan-out. Laragraph::broadcast() re-runs every subscriber's query before returning.
Use Laragraph::broadcastLater('users', $user) to do that on the queue instead
(subscriptions.queue.connection / subscriptions.queue.queue).
Unsubscribing. Clients cancel with DELETE /graphql/subscriptions/{subscriberId} (only the
subscription's owner may; anything else answers 404), and server code can call
Laragraph::unsubscribe($subscriberId). Subscriptions also expire after subscriptions.ttl.
Security. Each update is resolved as the subscriber: the subscriber's identity is stored when
they subscribe, and Laragraph::broadcast() re-runs their query authenticated as them โ never as
whoever triggered the broadcast โ so authorize(), policies and auth() behave exactly as on the
original request. Laragraph also registers the private-channel rule for
graphql-subscriber.{subscriberId}, admitting only the user who created the subscription (turn off
with 'subscriptions' => ['authorize_channel' => false] to write your own in routes/channels.php).
Because updates use private channels, subscribers must be authenticated to receive them. Set 'subscriptions' => ['driver' => 'log'] to write updates to the log instead, useful for local development without a broadcast server.
No broadcaster available? Set 'subscriptions' => ['driver' => 'sse'] and clients get updates over a plain HTTP Server-Sent Events connection instead โ just a stock EventSource, no Echo, no Reverb/Pusher. Read the capacity trade-offs in Subscriptions โ SSE transport before using it beyond a handful of subscribers: each open connection holds one worker for a bounded duration, unlike a dedicated WebSocket server.
Tracing
Enable per-field resolver timing in the Apollo Tracing format, understood out of the box by existing GraphQL tooling:
Every resolved field is recorded โ root Query/Mutation/Subscription fields and nested Type fields alike. Leave this off in production unless you're actively debugging performance; it adds a small wrapping cost to every resolver call.
Set 'driver' => 'otel' instead to export real OpenTelemetry spans (a root span per operation, a child per resolver) via open-telemetry/api's global tracer provider โ your app wires up its own OTel SDK/exporter. See Observability โ The otel driver.
Development
See benchmarks/README.md for how the performance budgets and benchmarks work and how to check a change for regressions.
CI runs the suite on every supported PHP ร Laravel combination (including --prefer-lowest),
enforces 100% line coverage, and fails on any PHP deprecation.
Contributing
Contributions, issues, and feature requests are welcome! See CONTRIBUTING.md for the local dev loop and the quality gate every PR needs to pass, and CODE_OF_CONDUCT.md for community expectations. Found a security issue? See SECURITY.md instead of opening a public issue.
License
MIT ยฉ Odhiambo Dormnic
All versions of laragraph with dependencies
ext-json Version *
illuminate/broadcasting Version ^10.0|^11.0|^12.0|^13.0
illuminate/contracts Version ^10.0|^11.0|^12.0|^13.0
illuminate/http Version ^10.0|^11.0|^12.0|^13.0
illuminate/routing Version ^10.0|^11.0|^12.0|^13.0
illuminate/support Version ^10.0|^11.0|^12.0|^13.0
illuminate/validation Version ^10.0|^11.0|^12.0|^13.0
overblog/dataloader-php Version ^1.0
webonyx/graphql-php Version ^15.0