DevToolBoxGRÁTIS
Blog

GraphQL to TypeScript: Code Generation & Type-Safe Development Guide

13 min readby DevToolBox

TL;DR

GraphQL schemas are strongly typed, and you can automatically generate TypeScript interfaces, enums, and input types from them. This eliminates manual type duplication, prevents runtime errors from schema drift, and keeps your frontend code fully type-safe. Try our free GraphQL to TypeScript converter for instant one-click conversion, or set up @graphql-codegen for automated pipeline integration.

Key Takeaways

  • GraphQL type systems map cleanly to TypeScript interfaces, enums, and union types.
  • Automated code generation eliminates type drift between your API schema and frontend code.
  • The @graphql-codegen toolchain supports typed hooks for React Query, Apollo, and urql.
  • Nullable fields in GraphQL translate to T | null union types in TypeScript.
  • Custom scalars like DateTime and JSON need explicit type mappings in your codegen config.
  • GraphQL provides inherently stronger typing than REST, reducing the need for manual interface definitions.

Why Generate TypeScript from GraphQL

GraphQL APIs define a complete type system in the schema. Every field, argument, and return type is explicitly declared. When you build a frontend that consumes a GraphQL API, you need TypeScript types that match the schema exactly. Writing these types by hand creates two sources of truth that inevitably diverge.

Consider a typical scenario: your backend team adds a new required field to a User type. Without automated type generation, your handwritten TypeScript interface stays unchanged. The mismatch only surfaces as a runtime error in production when the field is undefined where the code expected a string.

Automated GraphQL to TypeScript code generation solves this by treating the schema as the single source of truth. When the schema changes, you regenerate types, and the TypeScript compiler immediately flags every location in your frontend that needs updating. This workflow typically saves 2 to 5 hours per week on medium-sized projects and eliminates an entire class of bugs.

If you need a quick conversion without setting up a full pipeline, our GraphQL to TypeScript converter handles it instantly in the browser.

GraphQL Type System Overview

Before diving into code generation, you need to understand the GraphQL Schema Definition Language (SDL) and its type constructs. GraphQL supports six primary type categories:

  • Object types (type) - define the shape of data objects with named fields
  • Input types (input) - define argument shapes for mutations and queries
  • Enum types (enum) - define a fixed set of allowed values
  • Interface types (interface) - abstract types that other types implement
  • Union types (union) - represent one of several possible object types
  • Scalar types (scalar) - primitive values like String, Int, Boolean, Float, ID, or custom scalars

The exclamation mark (!) denotes a non-nullable field. Without it, the field defaults to nullable. This is the opposite of most programming languages and is critical to understand when mapping to TypeScript. Here is a schema demonstrating all major constructs:

# Object type with various field types
type User {
  id: ID!
  name: String!
  email: String!
  age: Int
  bio: String
  role: UserRole!
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  status: PostStatus!
  author: User!
  tags: [String!]
  publishedAt: DateTime
}

# Enum types
enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

# Input type for mutations
input CreateUserInput {
  name: String!
  email: String!
  age: Int
  role: UserRole = VIEWER
}

# Union type
union SearchResult = User | Post

# Custom scalar
scalar DateTime

Manual Conversion Examples

Understanding manual conversion helps you validate generated output and debug codegen issues. Below are the four most common GraphQL-to-TypeScript mappings you will encounter.

Object Type to Interface

GraphQL object types map to TypeScript interfaces. Non-nullable fields (!) become required properties, and nullable fields become union types with null:

// From GraphQL: type User { id: ID!, name: String!, age: Int, bio: String }
interface User {
  id: string;
  name: string;
  age: number | null;
  bio: string | null;
}

Input Type to Interface

Input types follow the same mapping rules. Fields with default values in GraphQL become optional in TypeScript:

// From GraphQL: input CreateUserInput { name: String!, email: String!, age: Int, role: UserRole = VIEWER }
interface CreateUserInput {
  name: string;
  email: string;
  age?: number | null;
  role?: UserRole;
}

Enum to TypeScript Enum or Union

GraphQL enums can map to either TypeScript enums or string literal unions. Both approaches are valid, but string literal unions are often preferred for tree-shaking:

// Approach 1: TypeScript enum
enum UserRole {
  ADMIN = 'ADMIN',
  EDITOR = 'EDITOR',
  VIEWER = 'VIEWER',
}

// Approach 2: String literal union (preferred for tree-shaking)
type UserRole = 'ADMIN' | 'EDITOR' | 'VIEWER';

Union Type to Discriminated Union

GraphQL union types become TypeScript discriminated unions. The __typename field acts as the discriminator:

// From GraphQL: union SearchResult = User | Post
type SearchResult =
  | { __typename: 'User'; id: string; name: string; email: string }
  | { __typename: 'Post'; id: string; title: string; content: string };

// Usage with type narrowing
function renderResult(result: SearchResult) {
  switch (result.__typename) {
    case 'User':
      return result.name; // TypeScript knows this is User
    case 'Post':
      return result.title; // TypeScript knows this is Post
  }
}

For more on TypeScript generics and advanced type patterns, see our guide on TypeScript generics explained.

Using the Online Converter Tool

For quick prototyping, learning, or one-off conversions, you do not need a full codegen pipeline. Our GraphQL to TypeScript converter provides instant browser-based conversion. Here is a typical workflow:

  1. Paste your GraphQL SDL into the input panel. The tool accepts full schemas with multiple types, enums, inputs, and unions.
  2. View the generated TypeScript in the output panel. Types are generated with proper nullability, array handling, and enum mapping.
  3. Copy the output directly into your project. Use the one-click copy button to grab the generated code.

The online tool handles all standard GraphQL constructs including nested types, recursive references, and list fields. It is ideal when you want to:

  • Quickly check how a schema change affects your TypeScript types
  • Generate initial type definitions for a new project before setting up codegen
  • Learn the mapping rules by comparing GraphQL input to TypeScript output
  • Share type definitions with team members who do not have codegen configured

If you also work with JSON data, check out our JSON to TypeScript converter and JSON to GraphQL converter for related transformations.

graphql-codegen Setup Guide

For production projects, @graphql-codegen is the industry standard. It reads your GraphQL schema and operations, then generates TypeScript types, typed document nodes, and even framework-specific hooks. Here is a complete setup:

Step 1: Install Dependencies

# Core packages
npm install -D @graphql-codegen/cli @graphql-codegen/typescript
npm install -D @graphql-codegen/typescript-operations
npm install -D @graphql-codegen/typescript-resolvers

# Optional: framework-specific plugins
npm install -D @graphql-codegen/typescript-react-query  # for React Query
npm install -D @graphql-codegen/typescript-react-apollo  # for Apollo Client

Step 2: Create Configuration File

// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
  // Schema source: local file or remote endpoint
  schema: './schema.graphql',
  // Or use introspection: schema: 'http://localhost:4000/graphql',

  // Where your .graphql operation files live
  documents: ['src/**/*.graphql'],

  generates: {
    // Generated types output path
    'src/generated/graphql.ts': {
      plugins: [
        'typescript',
        'typescript-operations',
      ],
      config: {
        // Use string literal unions instead of TS enums
        enumsAsTypes: true,
        // Make nullable fields T | null instead of Maybe<T>
        strictScalars: true,
        // Map custom scalars
        scalars: {
          DateTime: 'string',
          JSON: 'Record<string, unknown>',
          Upload: 'File',
        },
      },
    },
  },
};

export default config;

Step 3: Add npm Scripts

// package.json
{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.ts",
    "codegen:watch": "graphql-codegen --config codegen.ts --watch",
    "dev": "concurrently \"next dev\" \"npm run codegen:watch\"",
    "precommit": "npm run codegen && tsc --noEmit"
  }
}

Step 4: Run Generation

# One-time generation
npm run codegen

# Watch mode during development
npm run codegen:watch

After running codegen, all your TypeScript types are available in src/generated/graphql.ts. Import them directly in your components and hooks.

Generated Types with React Query and Apollo

The real power of GraphQL code generation emerges when you generate typed hooks for your data-fetching library. Instead of manually typing query results, codegen produces hooks where the return data is already fully typed.

With React Query (TanStack Query)

Add the typescript-react-query plugin to your codegen config:

// codegen.ts - add to generates section
'src/generated/hooks.ts': {
  plugins: [
    'typescript',
    'typescript-operations',
    'typescript-react-query',
  ],
  config: {
    fetcher: {
      func: './fetcher#fetchGraphQL',
      isReactHook: false,
    },
    exposeQueryKeys: true,
  },
}

// Usage in a component - fully typed, zero manual interfaces
import { useGetUserQuery } from '../generated/hooks';

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = useGetUserQuery({ id: userId });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error occurred</div>;

  // data.user is fully typed: { id: string, name: string, ... }
  return <h1>{data?.user?.name}</h1>;
}

With Apollo Client

// codegen.ts - Apollo plugin
'src/generated/apollo.ts': {
  plugins: [
    'typescript',
    'typescript-operations',
    'typescript-react-apollo',
  ],
  config: {
    withHooks: true,
    withComponent: false,
  },
}

// Usage - Apollo hooks are fully typed
import { useGetUserQuery } from '../generated/apollo';

function UserProfile({ userId }: { userId: string }) {
  const { data, loading, error } = useGetUserQuery({
    variables: { id: userId },
  });

  // data?.user is fully typed from the schema
  return <h1>{data?.user?.name}</h1>;
}

Both approaches give you autocomplete in your editor, compile-time error checking, and zero chance of type drift. When the schema changes, regenerate types and fix any compile errors before deploying.

Handling Nullable Types and Custom Scalars

Two areas commonly cause confusion when generating TypeScript from GraphQL: nullability and custom scalars. Getting these right is essential for accurate type generation.

Nullable Field Mapping

In GraphQL, fields are nullable by default. The ! makes them non-nullable. This is the opposite of TypeScript where properties are non-nullable by default. Here is the complete mapping table:

GraphQL TypeTypeScript TypeExplanation
Stringstring | nullNullable string
String!stringNon-nullable string
[String](string | null)[] | nullNullable list of nullable strings
[String!]string[] | nullNullable list of non-nullable strings
[String!]!string[]Non-nullable list of non-nullable strings
[String]!(string | null)[]Non-nullable list of nullable strings

Custom Scalar Configuration

GraphQL supports custom scalars beyond the built-in String, Int, Float, Boolean, and ID. These need explicit TypeScript mappings in your codegen config:

// codegen.ts - scalars config section
config: {
  scalars: {
    // Date/time scalars
    DateTime: 'string',        // ISO 8601 date strings
    Date: 'string',            // YYYY-MM-DD format
    Time: 'string',            // HH:mm:ss format
    Timestamp: 'number',       // Unix timestamp

    // Data scalars
    JSON: 'Record<string, unknown>',
    JSONObject: 'Record<string, unknown>',

    // File uploads
    Upload: 'File',

    // Numeric precision
    BigInt: 'bigint',
    Decimal: 'string',  // Preserve precision as string

    // Other common scalars
    UUID: 'string',
    URL: 'string',
    EmailAddress: 'string',
    PhoneNumber: 'string',
    Void: 'void',
  },
}

For more advanced typing patterns like branded types for custom scalars, see our TypeScript generics guide.

Best Practices for Type-Safe GraphQL

Generating types is only the first step. Follow these best practices to maintain a robust, type-safe GraphQL workflow across your entire team:

  1. Never manually edit generated files. Add a comment header to generated files and configure your linter to skip them. If you need custom types, create a separate file that imports and extends the generated ones.
  2. Run codegen in watch mode during development. This keeps types updated automatically as you modify .graphql files, giving you instant feedback in your editor.
  3. Add codegen to your CI pipeline. Run npm run codegen followed by tsc --noEmit in CI to catch schema drift before code reaches production.
  4. Commit generated files to version control. This makes pull request diffs clearly show how schema changes affect your frontend types and lets developers work without running codegen locally.
  5. Use fragment colocation. Define GraphQL fragments alongside the components that consume them. This keeps data requirements close to the UI code and produces more focused generated types.
  6. Prefer string literal unions over TypeScript enums. Configure enumsAsTypes: true in your codegen config. String unions tree-shake better, have simpler runtime behavior, and work more naturally with discriminated unions.
  7. Enable strict mode. Set strictScalars: true to force explicit scalar mappings and avoidOptionals: true to use T | null instead of optional properties. This catches more errors at compile time.
  8. Use typed document nodes. The typed-document-node plugin generates typed query/mutation objects that work with any GraphQL client, providing type safety without framework-specific plugins.

GraphQL vs REST Typing Comparison

Both GraphQL and REST APIs can be consumed with full type safety in TypeScript, but they achieve it through very different mechanisms. Understanding the differences helps you choose the right approach for your project.

AspectGraphQL + CodegenREST + OpenAPI
Schema sourceGraphQL SDL (built into protocol)OpenAPI/Swagger spec (external document)
Type generation@graphql-codegenopenapi-typescript or swagger-typescript-api
Response shapeClient-defined (query determines fields)Server-defined (fixed per endpoint)
Type precisionExact types per query (no over-fetching)Full response type (may include unused fields)
Enum supportNative (part of SDL)Via OpenAPI enum keyword
NullabilityExplicit per-field (! annotation)Via required and nullable keywords
Typed hooksGenerated per query/mutationManual or via libraries like orval
Schema drift detectionCompile-time via codegen + tscCompile-time via generated types + tsc

GraphQL has an inherent advantage in type precision because the client specifies exactly which fields it needs, and codegen generates types matching only those fields. REST types always represent the full response object, even when the client only uses a subset.

For a deeper comparison of these API paradigms, read our REST API best practices guide.

Frequently Asked Questions

What is the best tool for GraphQL to TypeScript code generation?

@graphql-codegen (GraphQL Code Generator) is the most widely adopted solution. It is plugin-based, supports every major GraphQL client (Apollo, urql, React Query), and generates not just types but also typed hooks, document nodes, and resolvers. For quick one-off conversions without toolchain setup, use our online GraphQL to TypeScript converter.

How do nullable GraphQL fields map to TypeScript types?

GraphQL fields are nullable by default (the opposite of TypeScript). A field without ! maps to T | null in TypeScript. A field with ! maps directly to T. Lists follow the same rules independently for the list itself and its elements. For example, [String!] becomes string[] | null (nullable list, non-nullable items).

Can I generate TypeScript types from a remote GraphQL endpoint?

Yes. Most code generators support introspection, meaning they query the running GraphQL server for its schema. In your codegen config, set the schema field to your endpoint URL (e.g., http://localhost:4000/graphql). For production APIs, you can also export the schema as an SDL file and use that as the source to avoid requiring a running server during builds.

Should I commit generated TypeScript files to version control?

Yes. Committing generated files provides two benefits: (1) pull request diffs clearly show how backend schema changes affect frontend types, and (2) developers can work on frontend code without needing to run the codegen pipeline locally. The key rule is to never manually edit generated files. If you need to extend a type, create a separate file that imports from the generated output.

What is the difference between graphql-codegen and gql.tada?

graphql-codegen generates TypeScript files at build time from your schema. gql.tada takes a different approach: it uses TypeScript template literal types to infer types at compile time without generating any files. graphql-codegen has broader ecosystem support and works with any GraphQL client. gql.tada offers zero-config type safety but requires TypeScript 5.0+ and advanced TypeScript understanding. For most teams, graphql-codegen is the safer and more practical choice.

Related Tools and Guides

𝕏 Twitterin LinkedIn
Isso foi útil?

Fique atualizado

Receba dicas de dev e novos ferramentas semanalmente.

Sem spam. Cancele a qualquer momento.

Try These Related Tools

GTGraphQL to TypeScriptTSJSON to TypeScriptGQLJSON to GraphQL

Related Articles

Geração de tipos GraphQL: Automatize seus tipos TypeScript

Automatize a geração de tipos TypeScript a partir de schemas GraphQL. Ferramentas de codegen, tipos de resolver, tipos de fragment e integração CI/CD.

TypeScript Generics Explicados: Guia Prático com Exemplos

Domine TypeScript generics do básico aos padrões avançados.

REST API Melhores Práticas: O Guia Completo para 2026

Aprenda as melhores práticas de design REST API: convenções de nomes, tratamento de erros, autenticação e segurança.