All skills
apollographql avatar

/graphql-operations

@194056e official
by Apollo GraphQLapollographql/skills115 stars
13

Guide for writing GraphQL operations (queries, mutations, fragments) following best practices. Use this skill when: (1) writing GraphQL queries or mutations, (2) organizing operations with fragments, (3) optimizing data fetching patterns, (4) setting up type generation or linting, (5) reviewing operations for efficiency.

Use this Skill: https://skilld.dev/gh/apollographql/skills/graphql-operations

This session only. Nothing lands on disk.

referencesvariables.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Variable Patterns

This reference covers patterns for using variables in GraphQL operations.

Table of Contents

Variable Basics

Declaring Variables

Variables are declared in the operation definition:

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

Using Variables

Variables are referenced with $ prefix:

query GetUser($id: ID!) {
  user(id: $id) {
    # $id used here
    id
    name
  }
}

Passing Variables

Variables are passed as a separate JSON object:

const { data } = await client.query({
  query: GET_USER,
  variables: {
    id: "user_123",
  },
});

Multiple Variables

query SearchPosts($query: String!, $status: PostStatus, $first: Int!, $after: String) {
  searchPosts(query: $query, status: $status, first: $first, after: $after) {
    edges {
      node {
        id
        title
      }
    }
  }
}
{
  "query": "graphql",
  "status": "PUBLISHED",
  "first": 10,
  "after": "cursor_abc"
}

Variable Types

Scalar Types

query Example(
  $id: ID!
  $name: String!
  $count: Int!
  $price: Float!
  $active: Boolean!
) {
  # ...
}

Custom Scalar Types

query Example(
  $date: DateTime!
  $email: Email!
  $url: URL!
) {
  # ...
}

Enum Types

query GetPosts($status: PostStatus!) {
  posts(status: $status) {
    id
    title
  }
}
{
  "status": "PUBLISHED"
}

List Types

query GetUsers($ids: [ID!]!) {
  users(ids: $ids) {
    id
    name
  }
}
{
  "ids": ["user_1", "user_2", "user_3"]
}

Input Object Types

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
  }
}
{
  "input": {
    "title": "My Post",
    "content": "Post content...",
    "tags": ["graphql", "api"]
  }
}

Required vs Optional

query Example(
  $required: String!     # Must be provided, cannot be null
  $optional: String      # Can be omitted or null
  $requiredList: [String!]!  # List required, items required
  $optionalList: [String]    # List optional, items optional
) {
  # ...
}

Default Values

Simple Defaults

query GetPosts($first: Int = 10, $status: PostStatus = PUBLISHED) {
  posts(first: $first, status: $status) {
    id
    title
  }
}

If not provided, uses defaults:

{}
// Equivalent to: { "first": 10, "status": "PUBLISHED" }

Override defaults:

{
  "first": 20
}
// Uses first: 20, status: PUBLISHED (default)

Defaults with Optional Variables

# Variable is optional (no !) but has default
query GetPosts($first: Int = 10) {
  posts(first: $first) {
    id
  }
}

Defaults for Complex Types

query GetPosts($orderBy: PostOrderInput = { field: CREATED_AT, direction: DESC }) {
  posts(orderBy: $orderBy) {
    id
    title
  }
}

When to Use Defaults

Use defaults for:

  • Pagination limits (first: Int = 20)
  • Sort order (direction: SortDirection = DESC)
  • Common filter values (status: Status = ACTIVE)
  • Feature flags (includeArchived: Boolean = false)

Complex Inputs

Nested Input Objects

mutation CreateOrder($input: CreateOrderInput!) {
  createOrder(input: $input) {
    id
    total
  }
}
{
  "input": {
    "customer": {
      "email": "customer@example.com",
      "name": "John Doe"
    },
    "items": [
      { "productId": "prod_1", "quantity": 2 },
      { "productId": "prod_2", "quantity": 1 }
    ],
    "shippingAddress": {
      "street": "123 Main St",
      "city": "New York",
      "state": "NY",
      "zipCode": "10001",
      "country": "US"
    }
  }
}

Lists of Input Objects

mutation BulkCreateUsers($inputs: [CreateUserInput!]!) {
  bulkCreateUsers(inputs: $inputs) {
    id
    email
  }
}
{
  "inputs": [
    { "email": "user1@example.com", "name": "User 1" },
    { "email": "user2@example.com", "name": "User 2" },
    { "email": "user3@example.com", "name": "User 3" }
  ]
}

Filter Inputs

query SearchProducts($filter: ProductFilter!) {
  products(filter: $filter) {
    id
    name
    price
  }
}
{
  "filter": {
    "category": "ELECTRONICS",
    "priceRange": {
      "min": 100,
      "max": 500
    },
    "inStock": true,
    "tags": ["featured", "sale"]
  }
}

Best Practices

Always Use Variables for Dynamic Values

# Good: Uses variable
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

# Bad: Hardcoded value
query GetUser {
  user(id: "123") {
    id
    name
  }
}

Match Variable Names to Arguments

# Good: Clear relationship
query GetUser($userId: ID!) {
  user(id: $userId) {
    id
  }
}

# Also good: Same name
query GetUser($id: ID!) {
  user(id: $id) {
    id
  }
}

# Bad: Confusing names
query GetUser($x: ID!) {
  user(id: $x) {
    id
  }
}

Use Descriptive Variable Names

# Good
query SearchPosts(
  $searchQuery: String!
  $authorId: ID
  $publishedAfter: DateTime
  $maxResults: Int = 20
) {
  searchPosts(
    query: $searchQuery
    author: $authorId
    after: $publishedAfter
    first: $maxResults
  ) {
    # ...
  }
}

# Bad
query SearchPosts($q: String!, $a: ID, $d: DateTime, $n: Int) {
  # ...
}

Group Related Variables

// Good: Variables object mirrors input structure
const variables = {
  input: {
    title: formData.title,
    content: formData.content,
    tags: formData.tags,
  },
};

// Less clear: Flat variables
const variables = {
  title: formData.title,
  content: formData.content,
  tags: formData.tags,
};

Validate Variables Client-Side

function createPost(input: CreatePostInput) {
  // Validate before sending
  if (!input.title?.trim()) {
    throw new Error("Title is required");
  }
  if (input.title.length > 200) {
    throw new Error("Title too long");
  }

  return client.mutate({
    mutation: CREATE_POST,
    variables: { input },
  });
}

Type Variables with TypeScript

// Generated types from schema
interface GetUserQueryVariables {
  id: string;
}

// Use with Apollo Client
const { data } = useQuery<GetUserQuery, GetUserQueryVariables>(GET_USER, {
  variables: { id: userId }, // Type-checked
});

Source: SKILL.md on GitHub

1 warning2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    The skill is a comprehensive guide for writing GraphQL operations and setting up associated development tools. It contains only instructional content and standard developer workflow examples. No security risks were identified.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer7mo

    6/6 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 194056e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 days ago
What it can do
Runs commands
compatibility
Any GraphQL client (Apollo Client, urql, Relay, etc.)
metadata
{
  "author": "apollographql",
  "version": "1.0.1"
}
All 1 allowed tools
Bash(npm:*) Bash(npx:*) Read Write Edit Glob Grep
  • graphql
  • queries
  • mutations
  • fragments
  • apollo-client
  • type-generation
  • best-practices
  • variables

README badge

README badge for apollographql/skills/graphql-operations

Teaches best practices for writing GraphQL operations (queries, mutations, subscriptions) and organizing them with fragments. Covers naming conventions, variable usage, field selection optimization, and colocating fragments with components across Apollo Client and other GraphQL clients.

Generated from the current SKILL.md.

Does this skill work with Apollo Client, urql, and other GraphQL clients?
Yes. The skill covers GraphQL operation best practices that apply to any GraphQL client library, including Apollo Client, urql, Relay, and others.
Does this skill include code generation or type safety setup?
The skill mentions type generation and linting as areas it addresses, with references to a tooling documentation file, but focuses primarily on writing and organizing operations by hand.
Can I use this skill for subscriptions?
Yes. The skill covers queries, mutations, and subscriptions equally, with examples and naming conventions for each.
Does this skill help optimize data fetching?
Yes. It includes principles like requesting only the fields you need, using fragments to avoid duplication, and leveraging directives like @include and @skip for conditional fetching.

Generated from the current SKILL.md. These answers refresh after source changes.