One of GraphQL’s core advantages is its strongly typed Schema Definition Language (SDL). However, without automated type generation, frontend components querying GraphQL endpoints often rely on manual TypeScript interfaces that drift out of sync with backend schema changes, causing silent production bugs.
GraphQL Code Generator (@graphql-codegen/cli) and TypeScript schema converters bridge this gap by compiling your schema (schema.graphql or introspection JSON) and client queries (.graphql files) directly into strict, autocomplete-friendly TypeScript types, React hooks, or typed document nodes.
This guide covers schema-to-TypeScript compilation, operation document typing, client integration with TanStack Query / Apollo / urql, and best practices for continuous CI/CD schema synchronization.
1. The End-to-End Type Safety Workflow
[ Backend SDL Schema ] + [ Frontend Operations (.graphql) ]
│ │
└─────────────────┬───────────────┘
▼
[ GraphQL Code Generator ]
▼
[ Typed React Hooks / Resolver Types / SDKs ]
When changes occur in the backend schema (e.g. deprecating a field or making an argument non-nullable), running type generation immediately surfaces TypeScript compile errors across affected frontend files before deploying.
2. Converting GraphQL SDL to TypeScript Types
Let's examine a standard GraphQL SDL schema representing an organization and project management system:
# schema.graphql
enum ProjectStatus {
PLANNING
ACTIVE
ARCHIVED
}
type User {
id: ID!
name: String!
email: String!
avatarUrl: String
}
type Project {
id: ID!
title: String!
status: ProjectStatus!
owner: User!
collaborators(limit: Int = 10): [User!]!
createdAt: String!
}
type Query {
project(id: ID!): Project
listProjects(status: ProjectStatus, limit: Int): [Project!]!
}
input CreateProjectInput {
title: String!
ownerId: ID!
}
type Mutation {
createProject(input: CreateProjectInput!): Project!
}
Generated TypeScript Type Definitions
When compiled to TypeScript, GraphQL types and scalars map to interfaces and type aliases:
// types.generated.ts
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export enum ProjectStatus {
Planning = 'PLANNING',
Active = 'ACTIVE',
Archived = 'ARCHIVED'
}
export interface User {
__typename?: 'User';
id: Scalars['ID'];
name: Scalars['String'];
email: Scalars['String'];
avatarUrl?: Maybe<Scalars['String']>;
}
export interface Project {
__typename?: 'Project';
id: Scalars['ID'];
title: Scalars['String'];
status: ProjectStatus;
owner: User;
collaborators: Array<User>;
createdAt: Scalars['String'];
}
export interface CreateProjectInput {
title: Scalars['String'];
ownerId: Scalars['ID'];
}
export interface QueryProjectArgs {
id: Scalars['ID'];
}
export interface QueryListProjectsArgs {
status?: Maybe<ProjectStatus>;
limit?: Maybe<Scalars['Int']>;
}
Tip: Need an instant TypeScript interface conversion without configuring a full CLI build step? Use the DevFlow GraphQL Schema to TypeScript Generator.
3. Configuring Automated Codegen for Client Operations
In production applications, you should not query raw types directly; you should generate types specific to the exact fields selected in your .graphql query files.
1. Define Client Query Documents
# src/queries/GetProjectDetails.graphql
query GetProjectDetails($id: ID!) {
project(id: $id) {
id
title
status
owner {
id
name
avatarUrl
}
}
}
2. Configure codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: 'https://api.example.com/graphql', // or local path: './schema.graphql'
documents: ['src/**/*.graphql'],
generates: {
'./src/gql/': {
preset: 'client',
plugins: [],
config: {
useTypeImports: true,
strictScalars: true,
},
},
},
};
export default config;
3. Consume Fully Typed Results in Frontend Components
import React from 'react';
import { useQuery } from '@apollo/client';
import { GetProjectDetailsDocument } from '@/gql/graphql';
export function ProjectCard({ projectId }: { projectId: string }) {
// Both query variables and response payload are strictly typed
const { data, loading, error } = useQuery(GetProjectDetailsDocument, {
variables: { id: projectId },
});
if (loading) return <div>Loading project...</div>;
if (error || !data?.project) return <div>Error loading project.</div>;
return (
<div className="rounded-lg border p-4 shadow-sm">
<h2 className="text-xl font-bold">{data.project.title}</h2>
<p className="text-sm text-muted-foreground">Status: {data.project.status}</p>
<div className="mt-2 flex items-center gap-2">
<span>Owner: {data.project.owner.name}</span>
</div>
</div>
);
}
4. Generating Backend Resolver Signatures
GraphQL Codegen also prevents backend runtime errors by generating resolver signatures for Apollo Server, Yoga, or Fastify:
import type { Resolvers } from '@/generated/resolvers-types';
export const resolvers: Resolvers = {
Query: {
project: async (_parent, args, context) => {
// args.id is strictly typed as string
return await context.db.projects.findUnique({ where: { id: args.id } });
},
},
Mutation: {
createProject: async (_parent, { input }, context) => {
return await context.db.projects.create({ data: input });
},
},
};
Frequently Asked Questions
How do I handle custom GraphQL scalars like DateTime or JSON?
In your codegen configuration, map custom scalars under config.scalars:
config: {
scalars: {
DateTime: 'string',
JSON: 'Record<string, unknown>',
}
}
How do I build and test GraphQL queries interactively?
You can construct and format queries against any live endpoint using the DevFlow GraphQL Query Builder.
Can I generate TypeScript types from JSON introspection files?
Yes. Both GraphQL Code Generator and our online schema converter support raw .json introspection results as well as .graphql SDL files.