TypeScript with GraphQL Code Generator: Types from the Schema
AI generated
<T>
type
TypeScript · GraphQL Code Generator · Codegen · React
TypeScript with GraphQL Code Generator
automatically generating types from the schema

Manually maintained TypeScript interfaces for GraphQL types almost inevitably drift out of sync once the schema changes. GraphQL Code Generator solves this problem by generating server resolver types, client operation types and even ready made React hooks directly and automatically from a single schema file.

17 min read codegen.yml · typed-document-node · React Hooks GraphQL Code Generator 5.x · TypeScript 5.x

1. The problem with manually maintained GraphQL types

GraphQL already ships with a complete, language independent type description via its schema, yet many teams still write the corresponding TypeScript interfaces by hand. The result is predictable: when a field in the schema changes, that change has to be manually carried over into every affected interface, which in larger projects with many queries quickly leads to forgotten spots and silent type inconsistencies.

GraphQL Code Generator solves this problem by treating the schema as the single source of truth and automatically generating all needed TypeScript types from it, both for the server and for the client. The toolkit consists of a core package and a large number of plugins, each responsible for a specific output style, for example resolver types, typed documents, or ready made React hooks.

For TypeScript teams already using GraphQL, switching to generated types is usually one of the most effective single measures against type drift between frontend and backend. This article shows the path from basic configuration to CI pipeline integration.

2. Installation and basic configuration of codegen.yml

Configuring GraphQL Code Generator happens through a codegen.yml file in the project root, which needs at least three pieces of information: the path or URL to the schema, the documents containing GraphQL operations to scan, and a list of output files with the desired plugins for each. This structure allows generating several different outputs from the same schema, for example server types in one file and client types in another.

A common beginner mistake: the schema is referenced as a local, static file even though it changes frequently, which leads to developers forgetting to update the schema file before generating. For projects with a live GraphQL server, it is usually more robust to point directly at the running server's introspection URL, so Code Generator always queries the current state.


// codegen.ts — root configuration for GraphQL Code Generator
import type { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  schema: "http://localhost:4000/graphql",
  documents: ["src/**/*.graphql", "src/**/*.tsx"],
  generates: {
    "src/generated/graphql.ts": {
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-apollo",
      ],
    },
    "src/generated/schema-types.ts": {
      plugins: ["typescript", "typescript-resolvers"],
    },
  },
};

export default config;

3. Generating types for the schema itself

The base typescript plugin generates a matching TypeScript interface for every type in the GraphQL schema, including enums, union types and input types. These types form the foundation for all further plugins and are rarely used directly in application code, but serve as the shared base on which resolver types and operation types build.

Important for TypeScript with GraphQL Code Generator: custom scalar types like DateTime or JSON that go beyond the standard GraphQL types must be explicitly mapped to a concrete TypeScript type in the configuration. Without this mapping, custom scalars default to any, which immediately undoes part of the gained type safety.

4. Typed resolvers with typescript-resolvers

The typescript-resolvers plugin generates a matching resolver type for every type in the schema, with parent type, arguments and return type already correctly typed. A server developer therefore no longer needs to write resolver signatures by hand or check them against the schema, the generated type guarantees that the resolver implementation matches the declared schema exactly.

This plugin is particularly valuable when the schema changes: if a field is renamed or an argument removed, the compiler immediately reports an error in every resolver that still expects the old signature. This feedback loop prevents a resolver from silently returning wrong or outdated data after a schema change.


// resolvers/post.ts — generated Resolvers type enforces the schema contract
import type { Resolvers } from "../generated/schema-types";

export const postResolvers: Resolvers = {
  Query: {
    post: async (_parent, args, ctx) => {
      // args.id is typed as string, derived directly from the schema
      return ctx.postService.findById(args.id);
    },
  },
  Post: {
    // Field resolver: parent is typed as the generated Post type
    author: async (parent, _args, ctx) => {
      return ctx.userService.findById(parent.authorId);
    },
  },
};

5. Client side types with typed-document-node

The typed-document-node plugin generates a TypedDocumentNode object for every GraphQL operation, meaning every query, mutation or subscription, that serves simultaneously as an executable GraphQL document and as a type carrier for variables and result. Client libraries such as Apollo Client or urql recognize this type automatically, so a call like useQuery(GetPostDocument) already delivers fully typed results without a manual type annotation.

This approach is more library agnostic than pure hook generation, because TypedDocumentNode is supported by most modern GraphQL clients without requiring a client specific plugin. For teams that might switch between different GraphQL clients or use several clients in parallel, typed-document-node is therefore often the more future proof choice compared to client specific hook generators.


// generated/graphql.ts (excerpt) — typed document for a single query
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";

export type GetPostQuery = {
  post: { id: string; title: string; author: { name: string } };
};

export type GetPostQueryVariables = {
  id: string;
};

export const GetPostDocument: TypedDocumentNode<
  GetPostQuery,
  GetPostQueryVariables
> = /* generated GraphQL document */ null as unknown as TypedDocumentNode<
  GetPostQuery,
  GetPostQueryVariables
>;

// component.tsx — no manual generics needed, types come from the document
import { useQuery } from "@apollo/client";
import { GetPostDocument } from "./generated/graphql";

function PostView({ id }: { id: string }) {
  const { data } = useQuery(GetPostDocument, { variables: { id } });
  // data?.post.title is fully typed, no cast required
  return <p>{data?.post.title}</p>;
}

6. Generating React hooks for queries

Besides typed-document-node, GraphQL Code Generator also offers client specific plugins such as typescript-react-apollo, which generate a ready made, named hook directly for every query, for example useGetPostQuery. This approach reduces boilerplate further, because developers no longer need to manage the document import or the type generics themselves, the generated hook already encapsulates both.

The tradeoff of this approach: the generated hooks bind the code more tightly to a specific client library. A later switch from Apollo Client to urql then requires not only a change to the codegen configuration but also replacing every hook call in the application code. For teams committing long term to one client library, this extra effort is usually acceptable, while teams uncertain about their client choice find typed-document-node the more flexible alternative.

7. Integrating codegen into CI/CD and watch mode

The command graphql-codegen --watch observes changes to the schema and to GraphQL documents and automatically regenerates the types on every change, which is excellent for local development. For the CI pipeline, a single run followed by a check makes more sense: graphql-codegen followed by git diff --exit-code ensures that nobody forgot to re-commit generated files after a schema change.

This check is especially important because generated files are versioned in many projects to shorten build times and make CI runs independent of a running GraphQL server. Without this CI check, outdated, committed generated types can silently drift from the actual schema definition, undermining the original purpose of GraphQL Code Generator.

8. Near operation file preset and modular output

By default, GraphQL Code Generator produces a single, often very large output file with all the project's types. The near-operation-file preset changes this behavior: for every file containing a GraphQL operation, a dedicated, adjacent generated file is created, for example GetPost.graphql and GetPost.generated.ts in the same directory. This significantly improves traceability, because developers find the generated types of a query directly next to the query itself instead of searching a central, growing file.

For large TypeScript projects with many teams, this preset also reduces merge conflicts, because changes to different queries land in different generated files instead of all competing in the same central file. Switching to this preset is usually worthwhile once a project has more than a handful of GraphQL operations.

Criterion GraphQL Code Generator Manual types tRPC
In sync with the schema Automatically on every run Only with disciplined manual upkeep Not applicable, no schema
Language agnostic consumers Yes, via the GraphQL schema Depends on extra documentation No, TypeScript to TypeScript only
Setup effort Medium, config file needed None additional Low
Client flexibility Multiple clients via plugins Arbitrary, but manual Tightly bound to the tRPC client

9. Code Generator compared to manual types and tRPC

Compared to manually maintained types, GraphQL Code Generator is superior in practically every respect once a project reaches a certain size: the one time configuration effort pays for itself quickly against the recurring risk of forgotten manual updates. Compared to tRPC, GraphQL with generated types remains the better choice once language agnostic consumers such as native mobile apps or external partner APIs come into play, because the GraphQL schema itself is language independent.

For pure TypeScript to TypeScript communication within a monorepo, tRPC can mean less setup effort, however, because no schema and no code generation step are needed. The decision between the two approaches ultimately depends on whether a project actually needs the language independence and introspection capability of GraphQL, or whether a pure TypeScript environment is sufficient.

Mironsoft

TypeScript GraphQL integration, codegen pipelines and API architecture

GraphQL types that are manually maintained and drifting?

We set up GraphQL Code Generator for your schema, including typed resolvers, typed-document-node for clients, and CI checks against outdated generated files.

Codegen setup

codegen.yml, plugin selection and near operation file structure

Resolver typing

Server resolvers guaranteed to match the declared schema

CI integration

Automatic checks for outdated generated files in the build

10. Summary

TypeScript with GraphQL Code Generator solves a fundamental problem: manually maintained types eventually drift from the schema, while generated types stay automatically in sync on every run. The codegen.yml configuration connects schema, documents and plugins, the typescript-resolvers plugin guarantees schema conformant server resolvers, and typed-document-node delivers cross client typed operations without binding to a specific library.

For teams with growing GraphQL schemas, investing in a clean Code Generator workflow, including watch mode for local development and a CI check against outdated files, is one of the most effective measures against unnoticed type inconsistencies. The comparison with manual types and with tRPC shows: GraphQL Code Generator has a particular edge wherever language independence and an introspectable schema are actually needed.

TypeScript with GraphQL Code Generator: The essentials at a glance

codegen.yml as the core

Schema, documents and plugin list combined in a single central configuration file.

typescript-resolvers

Generates resolver types that keep the server implementation and schema guaranteed in sync.

typed-document-node

Cross client typed operations without binding to a specific GraphQL library.

CI safeguard

git diff --exit-code after the codegen run prevents outdated, committed types.

11. FAQ: TypeScript with GraphQL Code Generator

1What is codegen.yml needed for?
Defines schema, documents and plugins per output file, otherwise nothing gets generated.
2What happens with scalars like DateTime?
Defaults to any without mapping, scalars option lets you set it explicitly.
3What does typescript-resolvers do?
Generates resolver types keeping server implementation and schema in sync.
4Advantage of typed-document-node?
Client agnostic, works with most modern GraphQL clients without an extra plugin.
5How do types stay current?
Watch mode locally, git diff --exit-code in CI after the codegen run.
6What does near-operation-file provide?
Dedicated generated file per query file instead of one large central file.
7Should I version generated files?
Usually yes, combined with a CI check against outdated versions.
8Multiple clients at once possible?
Yes, via typed-document-node or multiple output files with client specific plugins.
9When Code Generator instead of tRPC?
When language agnostic consumers like mobile apps or partner APIs are involved.
10Works without a running server?
Yes, schema referenceable as a local file, URL variant is more current with a live server.