Claude Code for GraphQL Code Generation: Type-Safe Queries with graphql-codegen — Claude Skills 360 Blog
Blog / Development / Claude Code for GraphQL Code Generation: Type-Safe Queries with graphql-codegen
Development

Claude Code for GraphQL Code Generation: Type-Safe Queries with graphql-codegen

Published: June 28, 2026
Read time: 8 min read
By: Claude Skills 360

GraphQL’s type system is valuable — but only if your client code reflects it. Without code generation, you’re writing TypeScript types by hand that drift from the schema, or using any and losing all type safety. graphql-codegen generates TypeScript types, React hooks, and document types directly from your schema and queries. Claude Code handles the configuration that makes codegen actually useful in a real project.

This guide covers GraphQL code generation with Claude Code: codegen setup, typed hooks, fragment patterns, and schema-first development.

graphql-codegen Setup

Set up graphql-codegen to generate TypeScript types and
Apollo Client hooks from our GraphQL schema and queries.
# codegen.ts (or codegen.yml)
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  schema: 'http://localhost:4000/graphql',  # Or path to local schema.graphql
  documents: ['src/**/*.graphql', 'src/**/*.{ts,tsx}'],
  generates: {
    # 1. Base TypeScript types from schema
    'src/generated/graphql.ts': {
      plugins: ['typescript', 'typescript-operations'],
      config: {
        scalars: {
          DateTime: 'string',
          UUID: 'string',
          JSON: 'Record<string, unknown>',
        },
        enumsAsTypes: true,
        avoidOptionals: true,  # Use null instead of undefined for nullable fields
      },
    },
    # 2. Apollo Client typed hooks
    'src/generated/graphql-hooks.ts': {
      preset: 'import-types',
      presetConfig: {
        typesPath: './graphql',
      },
      plugins: ['typescript-react-apollo'],
      config: {
        withHooks: true,
        withResultType: true,
        withMutationOptionsType: true,
      },
    },
    # 3. Per-file fragments (introspection)
    'src/generated/fragment-matcher.ts': {
      plugins: ['fragment-matcher'],
    },
  },
  hooks: {
    afterAllFileWrite: ['prettier --write'],
  },
};

export default config;
// package.json
{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --config codegen.ts --watch"
  }
}

Defining Queries with Fragments

Set up a pattern where each component declares the data it needs
using fragments, and the parent composes them into full queries.

Component Fragments

# src/components/UserCard/UserCard.graphql
fragment UserCard_user on User {
  id
  name
  email
  avatarUrl
  joinedAt
}
# src/components/PostCard/PostCard.graphql
fragment PostCard_post on Post {
  id
  title
  excerpt
  publishedAt
  author {
    ...UserCard_user
  }
}
# src/pages/BlogPage/BlogPage.graphql
query BlogPage($page: Int! = 1, $limit: Int! = 20) {
  posts(page: $page, limit: $limit) {
    items {
      ...PostCard_post
    }
    pagination {
      currentPage
      totalPages
      totalItems
    }
  }
}

After running npm run codegen, TypeScript types are generated automatically:

// Auto-generated — src/generated/graphql.ts (excerpt)
export type UserCard_UserFragment = {
  id: string;
  name: string;
  email: string;
  avatarUrl: string | null;
  joinedAt: string;
};

export type PostCard_PostFragment = {
  id: string;
  title: string;
  excerpt: string;
  publishedAt: string;
  author: UserCard_UserFragment;
};

export type BlogPageQueryVariables = Exact<{
  page: Scalars['Int']['input'];
  limit: Scalars['Int']['input'];
}>;

export type BlogPageQuery = {
  posts: {
    items: Array<PostCard_PostFragment>;
    pagination: {
      currentPage: number;
      totalPages: number;
      totalItems: number;
    };
  };
};

Using Generated Hooks

// src/pages/BlogPage.tsx
import { useBlogPageQuery } from '../generated/graphql-hooks';
import { PostCard } from '../components/PostCard';

export function BlogPage() {
  const [page, setPage] = useState(1);
  
  // useBlogPageQuery is generated — fully typed, matches the query exactly
  const { data, loading, error } = useBlogPageQuery({
    variables: { page, limit: 20 },
    notifyOnNetworkStatusChange: true,
  });
  
  if (loading && !data) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;
  
  return (
    <div>
      {data?.posts.items.map(post => (
        // PostCard receives PostCard_PostFragment shape — enforced by TypeScript
        <PostCard key={post.id} post={post} />
      ))}
      
      <Pagination
        current={data?.posts.pagination.currentPage ?? 1}
        total={data?.posts.pagination.totalPages ?? 1}
        onChange={setPage}
      />
    </div>
  );
}
// src/components/PostCard.tsx
import type { PostCard_PostFragment } from '../../generated/graphql';

interface Props {
  post: PostCard_PostFragment;  // Type comes from codegen
}

export function PostCard({ post }: Props) {
  return (
    <article>
      <h2>{post.title}</h2>
      <p>{post.excerpt}</p>
      <UserCard user={post.author} />  {/* UserCard type also enforced */}
      <time>{new Date(post.publishedAt).toLocaleDateString()}</time>
    </article>
  );
}

The fragment on PostCard_PostFragment is the source of truth. If someone removes a field from the GraphQL schema, TypeScript errors appear immediately — at compile time, before the code ships.

Mutations with Types

Generate typed mutation hooks for creating and updating posts.
Include optimistic updates.
# src/mutations/CreatePost.graphql
mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    ...PostCard_post
    content  # Additional fields for the detail view
  }
}

mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {
  updatePost(id: $id, input: $input) {
    ...PostCard_post
    content
    updatedAt
  }
}
// Generated hook usage
import { useCreatePostMutation } from '../generated/graphql-hooks';

export function CreatePostForm() {
  const [createPost, { loading }] = useCreatePostMutation({
    // Update Apollo cache to add new post to list
    update(cache, { data }) {
      if (!data?.createPost) return;
      
      cache.modify({
        fields: {
          posts(existing = { items: [] }) {
            const newPostRef = cache.writeFragment({
              data: data.createPost,
              fragment: PostCard_PostFragmentDoc,  // Generated fragment document
            });
            return { ...existing, items: [newPostRef, ...existing.items] };
          },
        },
      });
    },
  });
  
  const handleSubmit = async (formData: CreatePostInput) => {
    // createPost variables are typed — TypeScript errors if input doesn't match schema
    await createPost({
      variables: { input: formData },
      optimisticResponse: {
        createPost: {
          __typename: 'Post',
          id: 'temp-id',
          ...formData,
          publishedAt: null,
          author: currentUser,
        },
      },
    });
  };

Schema-First Development with Claude Code

We need to add a notification system. Start from the GraphQL schema.

Claude Code generates the schema additions first:

# New types added to schema
type Notification {
  id: ID!
  type: NotificationType!
  message: String!
  readAt: DateTime
  createdAt: DateTime!
  actor: User  # Who triggered the notification
  target: NotificationTarget  # What it's about (union type)
}

union NotificationTarget = Post | Comment | User

enum NotificationType {
  POST_LIKED
  POST_COMMENTED
  USER_FOLLOWED
  MENTION
}

extend type Query {
  notifications(unreadOnly: Boolean = false): [Notification!]!
  unreadCount: Int!
}

extend type Mutation {
  markNotificationRead(id: ID!): Notification!
  markAllRead: Boolean!
}

extend type Subscription {
  notificationReceived: Notification!
}

After adding this to the schema file, npm run codegen generates all the types and hooks. Then Claude Code implements the resolvers that match — starting from the schema contract rather than guessing what the client needs.

For the GraphQL subscription patterns including real-time WebSocket transport, see the GraphQL subscriptions guide. For the foundational Apollo Server setup and resolver patterns, see the GraphQL guide. The Claude Skills 360 bundle includes schema-first development skill sets with codegen configurations. Start with the free tier to set up type-safe GraphQL for your frontend.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free