bagisto/graphql-api
GraphQL API for Bagisto e-commerce: expose catalog, cart, checkout, customer, and storefront data through a flexible GraphQL schema. Build headless storefronts and modern clients that query exactly what they need, powered by Laravel and Bagisto.
Confirm you actually need it. This is a Bagisto module, not a standalone GraphQL server. If the project is not on Bagisto core, the package gives you nothing. If the project is on Bagisto and the roadmap includes a decoupled storefront, mobile client, or PWA, this is the shortest path to a typed commerce contract. Everything below assumes a working Bagisto install (storefront renders, checkout works in Blade) before you touch GraphQL.
Install.
composer require bagisto/graphql-api:^2.3.2
php artisan vendor:publish --provider="Webkul\GraphQLAPI\Providers\GraphQLAPIServiceProvider"
php artisan migrate
php artisan storage:link
Pin to ^2.3.2 or newer. Earlier 2.3.x releases shipped a broken GraphiQL install command and a stray namespace; the GDPR fix also landed in 2.3.2. Auto-discovery should register the provider, but confirm.
Where to look first, in order.
Resources/graphql or the project's graphql/ directory). This is the real contract. Read it instead of third-party writeups — community docs for a 110-star package are thin.php artisan list | grep -i graphql to find the install/route commands, then browse the schema interactively at the GraphiQL route. Do not write client code before this works.config/lighthouse.php. The package is built on Lighthouse. Route, middleware, guards, query depth and complexity limits, and debug flags all live here. Most "why did I get a 401" or "why is this field null" puzzles resolve in this file.config/graphql-api.php or equivalent) for route prefix, auth guards, and feature flags.First use case — catalog read plus a guest cart. Run these in GraphiQL, then wire them into a client:
query {
products(first: 5) {
edges { node { id sku name price } }
}
}
mutation {
createCart(input: {}) { cart { id } }
}
Confirm the cart ID comes back, then chain addToCart, address assignment, shipping, payment, and placeOrder in the playground. Introspect to confirm exact field names — the schema is the source of truth, not blog snippets. The moment those calls return sensible payloads, generate a typed client with graphql-codegen against the introspected schema. Hand-written types on a schema that follows Bagisto's release cadence are a maintenance tax you don't need.
Treat the SDL as a versioned artifact. Introspect into your repo, diff in CI, fail the build on unintended changes. Regenerate types in CI, never on a laptop. This single habit protects you from the package upgrading out from under your front end.
php artisan lighthouse:print-schema > graphql/schema.graphql
npx graphql-codegen --config codegen.yml
Wire auth as an explicit link, not scattered headers. Apollo/urql pattern:
Authorization: Bearer ... on every request.UNAUTHENTICATED by refreshing the token, then retrying once.Guest cart flow is the pattern you'll actually live in. Anonymous visitors get a cart from createCart tied to a token/cookie. On login, run an explicit cart-merge mutation rather than assuming the server merges automatically. Treat "token expired between addToCart and placeOrder" as a first-class test case — it is the single most common production bug on this stack.
Keep Bagisto as the source of commerce truth. Resolvers call Bagisto repositories and services for pricing, tax, promotions, stock. Do not re-derive a tax rate or a discounted price on the client. The reason to buy this package instead of building one is that Bagisto's rules stay authoritative.
Add dataloaders where the graph is deep. A category page fans out into products → prices → stock → media. Lighthouse supports batching; enable it on the relations your top pages traverse. Profile a cold category page with Telescope or a Blackfire trace before launch, not after p95 collapses.
type Product {
price: Float @dataloader(class: "App\\GraphQL\\DataLoaders\\PriceLoader")
}
Split caching by audience. Anonymous catalog reads are cacheable — key on query + variables, tag by product:{id} and category:{id}, and invalidate from Bagisto model events. Cart, checkout, and customer reads are per-user and must never be cached. That split should be visible in code, not just in a diagram.
Custom fields belong in your own schema, not vendor/. Need loyalty balance, a B2B price tier, or an ERP-synced field? Extend the type with a Lighthouse @field and a resolver that calls a Bagisto service:
extend type Product {
loyaltyPoints: Int @field(resolver: "App\\GraphQL\\Resolvers\\LoyaltyPoints@resolve")
}
Register the extension via your own schema file in config/lighthouse.php. Forks of the package turn every future upgrade into a merge conflict.
Checkout is where your work lives. The schema gives you cart mutations and placeOrder. Payment gateways, 3DS, webhook idempotency, and inventory reservation are not in scope. Use idempotency keys around placeOrder, an outbox for gateway callbacks, and short-lived stock reservations if two customers can race the last unit. Budget this as a named workstream.
CI contract tests. Run php artisan lighthouse:validate-schema on every push. Execute your five or six canonical storefront queries against a seeded database and assert shape, not just status code. On Bagisto upgrades, re-run these first — they catch drift far cheaper than a production incident.
Version coupling is the real risk. Resolvers reach into Bagisto core internals, and the two ship in lockstep. Pin both majors in composer.json, upgrade them together, and read release notes before every bump. Patch releases carry fixes invisible to a casual composer update — 2.3.2 is the example (GraphiQL install command, namespace, GDPR module).
GraphiQL on 2.3.x. If the install command fails or the namespace looks wrong, you're on an older 2.3.x. Upgrade rather than debugging a tool you don't own.
Mutations returning null is almost always a guard mismatch. Shop vs. admin schema, wrong middleware in config/lighthouse.php, or a missing bearer token. Check the guard before you check the resolver.
Guest cart "disappearing" is a token scope problem, not a resolver bug. The cart is tied to the token/cookie issued at createCart. If the token rotates or expires, the cart is unreachable and the failure is silent. Set an explicit session lifetime, refresh proactively, and surface a real "your cart has expired" UI state instead of an opaque error.
CORS bites in local dev and again in prod. Your config/cors.php needs Authorization in Access-Control-Allow-Headers and a permissive origin locally. Copying that permissive config to production is a common, painful mistake.
N+1 shows up on catalog and cart, not on simple queries. Bagisto's Eloquent relations don't always batch cleanly under GraphQL. When resolver time spikes, the fix is usually a dataloader, occasionally a targeted index — rarely both.
Bound pagination and complexity from day one. Set max_query_depth and max_query_complexity in config/lighthouse.php and cap first: on catalog connections. first: 10000 on a public commerce endpoint is a trivial DoS vector.
Debug mode is local-only. Lighthouse debug mode leaks stack traces and SQL fragments in the error payload. Useful in GraphiQL, dangerous in production.
Debugging order that works:
Cannot query field "X" after an upgrade means the SDL moved; re-introspect and regenerate.null on a protected field → check guard and token in config/lighthouse.php.php artisan lighthouse:validate-schema catches a surprising number of "the schema is fine" assumptions.Extensions to prefer. Lighthouse directives, custom resolvers calling Bagisto services, middleware on the GraphQL route, schema stitching for adjacent systems. Anything requiring an edit under vendor/ is a signal to redesign.
Staff for self-support. MIT, ~110 stars, no dependents, no SLA. You will read the resolvers you depend on and you may send upstream fixes. Budget roughly 0.25–0.5 FTE for upgrades and schema diffs, and keep the Blade storefront deployable as a fallback until you've run a peak cycle headless. That trade is still favourable — it's months of schema and resolver design you don't have to build or defend — but only if someone owns it.
How can I help you explore Laravel packages today?