GraphQL Development for Saleor
Before writing code
Fetch live docs:
- Web-search
site:docs.saleor.io api-reference for the current Saleor GraphQL schema and API reference
- Web-search
site:the-guild.dev graphql-codegen for GraphQL Code Generator configuration and plugins
- Web-search
site:graphql.org learn for GraphQL specification fundamentals
- Web-search
saleor GraphQL TypedDocumentNode urql for typed client patterns
- Fetch
https://docs.saleor.io/docs/developer/api-conventions for Saleor-specific GraphQL conventions
GraphQL Fundamentals
| Operation | Purpose | Saleor Example |
|---|
| Query | Read data | Fetch products, orders, categories |
| Mutation | Write data | Create checkout, update product, complete order |
| Subscription | Real-time events | Webhook subscription payloads |
Saleor API Characteristics
- GraphQL is the only API — there are no REST endpoints
- All operations are accessed at a single
/graphql/ endpoint
- Mutations return both the result object and an
errors array
- Queries support filtering, sorting, and cursor-based pagination
- Channel context is set via the
channel argument or HTTP header
Fragments for Reuse
| Fragment Use Case | Benefit |
|---|
| Product fields | Reuse across product list, detail, and search queries |
| Address fields | Share between checkout, order, and customer queries |
| Money fields | Consistent currency/amount selection |
| Error fields | Uniform error handling across mutations |
| Image fields | Consistent image URL and alt text selection |
- Name fragments as
{TypeName}Fragment (e.g., ProductFragment, AddressFragment)
- Keep fragments focused on a single concern
- Co-locate fragments with the components that use them
Variables and Input Types
| Concept | Description | Example |
|---|
| Variable | Dynamic value passed to operation | $id: ID!, $channel: String! |
| Input type | Structured input for mutations | ProductCreateInput, CheckoutCreateInput |
| Required | Non-null variable | $id: ID! (with !) |
| Optional | Nullable variable | $filter: ProductFilterInput |
| Default | Variable with default value | $first: Int = 10 |
- Always use variables instead of string interpolation for dynamic values
- Pass channel as a variable for multi-channel operations
GraphQL Code Generation
| Package | Purpose |
|---|
@graphql-codegen/cli | Code generation CLI |
@graphql-codegen/typescript | Generate TypeScript types from schema |
@graphql-codegen/typescript-operations | Generate types for operations |
@graphql-codegen/typed-document-node | Generate TypedDocumentNode objects |
Configuration (codegen.ts)
| Setting | Value | Description |
|---|
schema | Saleor GraphQL endpoint URL | Source of truth for types |
documents | "src/**/*.graphql" or "src/**/*.ts" | Location of operations |
generates | Output file paths | Where to write generated code |
plugins | Array of codegen plugins | Which code to generate |
- Run
graphql-codegen after schema changes or when adding new operations
- Commit generated files to version control for CI consistency
TypedDocumentNode Pattern
| Aspect | Benefit |
|---|
| Query result | Fully typed response data |
| Variables | Type-checked at compile time |
| Fragments | Types flow through fragment composition |
| IDE support | Autocomplete for fields and variables |
| Refactoring | Rename detection across operations |
- Generated by
@graphql-codegen/typed-document-node
- Works with urql, Apollo Client, and other GraphQL clients
- Catches schema mismatches at compile time, not runtime
Error Handling
| Error Type | Location | Cause |
|---|
| GraphQL errors | response.data.mutation.errors[] | Validation, permission, business logic |
| Network errors | Caught by client | Connection failure, timeout, 5xx |
| Schema errors | Build-time (codegen) | Operation doesn't match schema |
Saleor Mutation Error Structure
| Field | Type | Description |
|---|
field | String | Which input field caused the error |
message | String | Human-readable error message |
code | Enum | Machine-readable error code |
- Always check
data.mutation.errors after every mutation
- Map error codes to user-friendly messages in the storefront
Client Libraries
| Client | Package | SSR Support | Caching | Best For |
|---|
| urql | @urql/core, @urql/next | Yes (via @urql/next) | Document cache, Graphcache | Saleor recommended |
| Apollo Client | @apollo/client | Yes | Normalized cache | Complex caching needs |
| graphql-request | graphql-request | N/A (no cache) | None | Simple scripts, server-side |
| gql (Python) | gql | N/A | None | Python scripts and tests |
- Include
Authorization: Bearer <token> header for authenticated operations
- Include
saleor-channel: <slug> header for channel-scoped queries
- For SSR: ensure proper request deduplication and cache hydration
Operation Naming Conventions
| Convention | Example | Benefit |
|---|
| Prefix with verb | GetProducts, CreateCheckout | Clear intent |
| Suffix queries | ProductListQuery, OrderDetailQuery | Distinguish from mutations |
| Suffix mutations | CheckoutCreateMutation | Distinguish from queries |
| Unique names | No duplicates across codebase | Required by codegen |
Pagination Patterns
Saleor uses Relay-style cursor-based pagination:
| Parameter | Type | Description |
|---|
first | Int | Number of items from the start |
after | String | Cursor after which to fetch |
last | Int | Number of items from the end |
before | String | Cursor before which to fetch |
Response Structure
| Field | Path | Description |
|---|
| edges | connection.edges[] | Array of edge objects |
| node | edge.node | The actual entity |
| cursor | edge.cursor | Opaque cursor string |
| pageInfo.hasNextPage | connection.pageInfo | More items forward |
| totalCount | connection.totalCount | Total items matching filter |
- Use
first + after for forward pagination (most common)
- Avoid using
totalCount for large datasets (can be slow)
Introspection and Schema Downloading
| Method | Tool | Use Case |
|---|
| Introspection query | Built-in GraphQL client | Development exploration |
| GraphQL Playground | /graphql/ in browser | Interactive schema browser |
| Schema download | graphql-codegen | CI/CD and type generation |
| SDL export | rover graph introspect <url> | Save schema as .graphql file |
Testing GraphQL Operations
| Test Type | Tool | What to Test |
|---|
| Unit | Vitest, Jest | Operation result parsing, error mapping |
| Integration | Saleor test instance | Full round-trip query/mutation |
| Type check | tsc --noEmit | Generated types match operations |
| Linting | @graphql-eslint/eslint-plugin | Operation naming, fragment usage |
Best Practices
- Use GraphQL code generation with TypedDocumentNode for end-to-end type safety
- Define reusable fragments for common field selections
- Always use variables for dynamic values — never concatenate strings
- Check
data.mutation.errors after every mutation call
- Name every operation uniquely with descriptive PascalCase names
- Use cursor-based pagination with
first/after for list queries
- Include the
saleor-channel header for channel-scoped operations
- Keep operations minimal — request only the fields you need
- Run
graphql-codegen in CI to catch schema mismatches
Fetch the GraphQL documentation for current Saleor schema, code generation setup, and client library configuration before implementing.