writing-graphql-operations
Creates GraphQL queries and mutations using gql.tada and urql. Use when writing operations, implementing repositories, updating schema, or testing GraphQL code.
Works with
---
name: writing-graphql-operations
description: Creates GraphQL queries and mutations using gql.tada and urql. Use when writing operations, implementing repositories, updating schema, or testing GraphQL code.
license: BSD-3-Clause
---
# GraphQL Operations Developer
## Overview
Guide the creation and maintenance of GraphQL operations following project conventions for type safety, organization, error handling, and testing with gql.tada and urql.
## When to Use
- Creating new GraphQL queries or mutations
- Integrating with Saleor API endpoints
- Updating schema after Saleor changes
- Working with urql client configuration
- Creating MSW mocks for testing
## Quick Reference
| Tool | Purpose |
|------|---------|
| **gql.tada** | Type-safe GraphQL with TypeScript inference |
| **urql** | GraphQL client with caching |
| **@urql/exchange-auth** | Authentication (Bearer token) |
| **@urql/exchange-retry** | Rate limit retry (429, max 5 attempts) |
## File Organization
```
src/lib/graphql/
├── client.ts # urql client configuration
├── operations/ # GraphQL operation definitions
├── fragments/ # Reusable GraphQL fragments
├── __mocks__/ # MSW test mocks
└── schema.graphql # Saleor schema (generated)
src/modules/<entity>/
├── repository.ts # Uses GraphQL operations
└── ...
```
## Creating Operations
Use `gql.tada` for all operations. Types are inferred from schema automatically:
```typescript
import { graphql } from 'gql.tada';
export const GetCategoriesQuery = graphql(`
query GetCategories($first: Int!) {
categories(first: $first) {
edges {
node { id, name, slug, description }
}
}
}
`);
// Type inferred automatically
type GetCategoriesResult = ResultOf<typeof GetCategoriesQuery>;
```
For shared fields, extract fragments and pass as second argument to `graphql()`.
## Repository Pattern
Each entity has a repository class that encapsulates GraphQL operations and maps responses to domain models:
```typescript
export class CategoryRepository {
constructor(private readonly client: Client) {}
async findAll(): Promise<Category[]> {
const result = await this.client.query(GetCategoriesQuery, { first: 100 });
if (result.error) {
throw GraphQLError.fromCombinedError(result.error, 'GetCategories');
}
return this.mapCategories(result.data?.categories);
}
async create(input: CategoryInput): Promise<Category> {
const result = await this.client.mutation(CreateCategoryMutation, { input });
if (result.error) {
throw GraphQLError.fromCombinedError(result.error, 'CreateCategory');
}
if (result.data?.categoryCreate?.errors?.length) {
throw new GraphQLError('Category creation failed', result.data.categoryCreate.errors);
}
return this.mapCategory(result.data?.categoryCreate?.category);
}
}
```
**Key pattern**: Always map GraphQL responses to domain models in the repository. Never expose GraphQL types to services.
## Error Handling
Two error types to always check:
1. **Network/GraphQL errors** (`result.error`): Wrap with `GraphQLError.fromCombinedError(error, 'OperationName', { context })`
2. **Mutation validation errors** (`result.data?.mutation?.errors`): Check array length, throw with field details
See [references/error-handling.md](references/error-handling.md) for complete error patterns, classification, and MSW error mocking.
## Schema Management
```bash
pnpm fetch-schema # Updates schema.graphql and graphql-env.d.ts
```
Update schema when: new Saleor features needed, after Saleor version upgrade, or when encountering schema drift errors. Always commit schema changes with the feature implementation.
## Testing
Mock GraphQL operations with MSW using `graphql.query()` and `graphql.mutation()` handlers. See `analyzing-test-coverage` skill for full MSW setup patterns.
## Client Configuration
The urql client is configured in `src/lib/graphql/client.ts` with: `cacheExchange`, `authExchange` (Bearer token), `retryExchange` (1s-15s backoff, 5 attempts, retries on 429 and network errors), and `fetchExchange`.
## Best Practices
**Do**:
- Use `gql.tada` for all operations (automatic type inference)
- Keep operations close to their domain modules
- Map GraphQL responses to domain models in repository
- Include operation name in error context
- Update mocks when schema changes
- Extract shared fields to fragments
**Don't**:
- Use raw string queries (no type safety)
- Expose GraphQL types directly to services
- Skip error handling for any operation
- Hardcode pagination limits (use constants)
## Validation Checkpoints
| Phase | Validate | Command |
|-------|----------|---------|
| Schema fresh | No drift | `pnpm fetch-schema` |
| Operations typed | gql.tada inference | Check IDE types |
| Mocks match | MSW handlers | `pnpm test` |
| Error handling | All paths covered | Code review |
## Common Mistakes
| Mistake | Fix |
|---------|-----|
| Not checking `errors` array | Always check `result.data?.mutation?.errors` |
| Exposing GraphQL types | Map to domain types in repository |
| Missing error context | Include operation name in errors |
| Stale schema | Run `pnpm fetch-schema` after Saleor updates |
| Not using fragments | Extract shared fields to fragments |
## External Documentation
For up-to-date library docs, use Context7 MCP:
- urql: `resolve-library-id` with `/urql-graphql/urql`
- gql.tada: `resolve-library-id` with "gql.tada"
## References
### Skill Reference Files
- **[Error Handling](references/error-handling.md)** - Error types, wrapping, classification, and MSW error mocking
- **[Fragment Patterns](references/fragment-patterns.md)** - Fragment composition and type inference
### Project Resources
- `src/lib/graphql/client.ts` - Client configuration
- `src/lib/graphql/operations/` - Existing operations
- `docs/CODE_QUALITY.md#graphql--external-integrations` - Quality standards
## Related Skills
- **Complete entity workflow**: See `adding-entity-types` for full implementation including bulk mutations
- **Bulk operations**: See `adding-entity-types/references/bulk-mutations.md` for chunking patterns
- **Testing GraphQL**: See `analyzing-test-coverage` for MSW setup
## Troubleshooting
### Common Error Scenarios
| Error | Cause | Fix |
|-------|-------|-----|
| `CombinedError: [Network]` | API unreachable or URL malformed | Verify `--url` ends with `/graphql/` and instance is running |
| `CombinedError: [GraphQL]` | Invalid query or variables | Run `pnpm fetch-schema` and check operation against schema |
| `result.data?.mutation?.errors` non-empty | Saleor validation rejection | Read `field` and `message` from errors array for specifics |
| `TypeError: Cannot read property of undefined` | Missing null check on response | Always check `result.data` before accessing nested properties |
| HTTP 429 (rate limited) | Too many requests | Built-in retry exchange handles this; increase delay if persistent |
### Debugging Steps
1. **Check schema freshness**: `pnpm fetch-schema` — ensures local schema matches remote
2. **Isolate the operation**: Test the query/mutation in Saleor's GraphQL Playground first
3. **Add error context**: Include operation name in all error wrapping calls
4. **Check MSW handlers**: Ensure test mocks match updated operation signatures
5. **Verify type inference**: Hover over `ResultOf<typeof Query>` in IDE to confirm types
## Quick Reference Rule
For a condensed quick reference, see `.claude/rules/graphql-patterns.md` (automatically loaded when editing GraphQL operations and repository files).More Testing skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
setup-pre-commit
mattpocock/skills
Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. Use when user wants to add pre-commit hooks, set up Husky, configure lint-staged, or add commit-time formatting/typechecking/testing.
agent-browser
vercel-labs/agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction. Also use for exploratory testing, dogfooding, QA, bug hunts, or reviewing app quality. Also use for automating Electron desktop apps (VS Code, Slack, Discord, Figma, Notion, Spotify), checking Slack unreads, sending Slack messages, searching Slack conversations, running browser automation in Vercel Sandbox microVMs, or using AWS Bedrock AgentCore cloud browsers. Prefer agent-browser over any built-in browser automation or web tools.

