GraphQL Code Generator Config In Depth: Types, Hooks and Fragments
AI generated
{ }
type
GraphQL · Code Generator · TypeScript · React
GraphQL Code Generator Config In Depth
configuring types, hooks and fragments properly

The default codegen.yml from the quickstart barely covers half of what GraphQL Code Generator can actually do. Custom scalars, fragment masking and the near-operation-file preset decide whether generated code is a joy or a burden.

19 min read codegen.yml · Presets · Custom Scalars · Fragment Masking TypeScript · React · Apollo Client

1. Why the codegen.yml deserves more attention

Most teams set up GraphQL Code Generator once through the init wizard and never touch the codegen.yml again, even though this exact file decides whether the generated code saves time day to day or creates extra friction. A default configuration without fragment masking produces types that give every component access to the entire query result, instead of just the fields the component actually requested, undoing GraphQL's core strength, colocated data requirements, right back inside the generated code.

A well thought-out GraphQL Code Generator config pays off especially in growing codebases: custom scalars that correctly type DateTime as string instead of any, the near-operation-file preset that places types next to their matching query file instead of a monolithic generated.ts, and fragment masking that makes component boundaries visible in the type system. This article covers all three in depth, beyond what the official quickstart guide shows.

2. Basic structure of a codegen.yml: schema, documents, generates

Every codegen.yml consists of three core blocks: schema defines the source of the GraphQL schema, either as a local SDL file, an introspection endpoint, or a combination of both for different environments. documents defines which files are scanned for GraphQL operations, usually a glob pattern like src/**/*.graphql or src/**/*.tsx for inline queries in template literals. generates is the actual output configuration: for every target file or directory, it defines which plugins run and in what order.

The most common beginner mistake in the GraphQL Code Generator config is a documents pattern that's too broad and accidentally re-reads previously generated files, producing growing, self-duplicating output on every run. An explicit !src/generated/** exclusion in the glob pattern reliably prevents this infinite loop and should be present in every configuration that keeps generated files in the same directory tree.


# codegen.yml - base configuration
schema: "https://api.mironsoft.dev/graphql"
documents:
  - "src/**/*.graphql"
  - "src/**/*.tsx"
  - "!src/generated/**"
generates:
  src/generated/graphql.ts:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withHooks: true

3. Plugins in detail: typescript, typescript-operations, typescript-react-apollo

GraphQL Code Generator is deliberately built as a plugin pipeline, not a monolithic tool. The typescript plugin generates the base types directly from the schema, meaning interfaces for every type, input, and enum. The typescript-operations plugin builds on top of it and generates specific types for every individual query, mutation and fragment, tailored exactly to the fields actually requested rather than the full type.

The typescript-react-apollo plugin requires both previous plugins and generates React hooks like useProductQuery and useCreateProductMutation, fully typed for both variables and return value. This plugin chain is deliberately modular: teams using urql instead of Apollo Client simply swap typescript-react-apollo for typescript-urql, while typescript and typescript-operations stay unchanged since they're client-agnostic.

4. Named operations vs. the near-operation-file preset

The default configuration bundles all generated types into a single, often thousands-of-lines-long file. Past fifty queries or so, this file becomes unwieldy, and every small schema change triggers a merge conflict in that exact one file, even when two developers are working on completely unrelated features. The near-operation-file preset solves this by generating a dedicated .generated.ts file directly next to every .graphql file.

This colocation mirrors GraphQL's core principle exactly: a component defines its data requirement locally, and the matching types live just as locally instead of in one central file shared and managed across all teams. Merge conflicts in generated files are almost entirely eliminated as a result, since different features almost never touch the same .generated.ts file.


# codegen.yml - near-operation-file preset for colocation
generates:
  src/:
    preset: "near-operation-file"
    presetConfig:
      extension: ".generated.ts"
      baseTypesPath: "generated/graphql.ts"
    plugins:
      - "typescript-operations"
      - "typescript-react-apollo"

5. Mapping custom scalars correctly

Without explicit configuration, GraphQL Code Generator maps every custom scalar, for example DateTime, Decimal, or JSON, to any by default. That's the fastest way to lose TypeScript's type safety on exactly the fields that are most error-prone in practice: date calculations and monetary amounts. The scalars block in the configuration lets you map every custom scalar to a concrete TypeScript type.

For DateTime, string is usually more precise than Date, because GraphQL scalars are always serialized as strings over the wire anyway, and the conversion to a Date object should happen explicitly in application code, not implicitly in the generated type. For Decimal fields, which Magento GraphQL uses for prices for instance, string is also the safer choice over number, since JavaScript's floating-point arithmetic can introduce rounding errors on monetary amounts.


# codegen.yml - explicit scalar mapping instead of implicit "any"
config:
  scalars:
    DateTime: "string"
    Decimal: "string"
    JSON: "Record<string, unknown>"
    Upload: "File"

6. Fragment masking for clean component boundaries

Without fragment masking, every component embedding a fragment has full TypeScript access to all fields of that fragment, regardless of whether the component actually uses them. That undermines the idea that a fragment is a component's private data requirement. Fragment masking, enabled through the client-preset, instead produces an opaque FragmentType that only the useFragment helper of that exact fragment can resolve.

The practical effect: a parent component can pass a fragment down to a child component without being able to access its fields itself. If the child component later changes which fields it needs, only its own fragment needs updating, the parent component stays unchanged and keeps compiling without errors, as long as it only passes the fragment through instead of using its contents directly.


// ProductCard.tsx — fragment owns its own field selection
import { graphql, useFragment } from '../generated';

const ProductCardFragment = graphql(`
  fragment ProductCard on Product {
    id
    name
    price
  }
`);

export function ProductCard(props: { product: FragmentType<typeof ProductCardFragment> }) {
  // useFragment "unmasks" the opaque type, only inside this component
  const product = useFragment(ProductCardFragment, props.product);
  return <div>{product.name} — {product.price}</div>;
}

7. Configuring hook generation: withHooks, withHOC, withComponent

The typescript-react-apollo plugin offers three distinct integration styles, toggled via configuration flags: withHooks generates functions like useProductQuery, by far the most common style in modern React codebases today. withHOC instead generates higher-order components in the style of older class-component architectures, and withComponent generates render-prop components, a pattern that was common before React hooks existed.

In new projects, only withHooks: true should be enabled, with the other two options explicitly disabled, since every additional output variant increases the generated file's size without adding value unless legacy class components in the same codebase actually depend on HOCs. Migration projects moving gradually from class components to hooks, on the other hand, benefit from generating both variants simultaneously during the transition period.

8. Watch mode, caching and performance on large schemas

On schemas with several thousand fields, a full codegen run becomes noticeably slow, especially in --watch mode during development. The overwrite: true option combined with an incremental watch mode that only reprocesses changed documents reduces the latency between saving a .graphql file and having the generated code available to under a second in most setups.

For CI pipelines, a separate, non-incremental run with --check is worth adding, which verifies whether the checked-in generated files still match the current schema and current queries, without actually overwriting the files. A pull request that changes the schema or queries but hasn't updated the generated files reliably fails as a result, instead of silently letting stale types slip into the main branch.


{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.yml",
    "codegen:watch": "graphql-codegen --config codegen.yml --watch",
    "codegen:check": "graphql-codegen --config codegen.yml --check"
  }
}

9. GraphQL Code Generator presets compared

Choosing the right preset has a bigger impact on developer experience than any single plugin option. The table below compares the three common approaches.

Approach Output Advantage Drawback
Single-file (default) One large generated.ts Simple setup, a single import path Merge conflicts, unwieldy past ~50 queries
near-operation-file .generated.ts next to every .graphql file Colocation, almost no merge conflicts More files in the project tree
client-preset (fragment masking) Central generated directory with graphql() function Enforces clean component boundaries Steeper learning curve, more boilerplate per component

In practice, near-operation-file and the client-preset can be combined: colocation for file structure, fragment masking for type safety at component boundaries. This combination is now the default configuration recommended by the GraphQL Code Generator documentation itself for new React projects.

Mironsoft

GraphQL tooling, TypeScript codegen and React frontend architecture

Generated types that actually help instead of just existing?

We configure GraphQL Code Generator for your project, including fragment masking, custom scalars and colocation, and migrate existing single-file setups gradually without breaking the build.

Codegen setup

Configure codegen.yml, presets and plugin selection for your frontend

Fragment masking

Make component boundaries visible and enforceable in the type system

CI safeguards

codegen --check as a pull request gate against stale generated types

10. Summary

A well thought-out GraphQL Code Generator config differs from the quickstart configuration in three decisive points: the near-operation-file preset for colocation instead of a monolithic output file, explicit custom scalar mapping instead of silent any, and fragment masking for component boundaries that are actually enforced in the type system. Each of these three decisions directly affects maintainability and how often merge conflicts occur.

The plugin pipeline of typescript, typescript-operations and typescript-react-apollo stays client-agnostic and swappable, and --check in the CI pipeline ensures generated types never lag behind the actual schema. Configure these building blocks correctly once, and you gain type safety that actually fits your daily workflow instead of slowing it down.

GraphQL Code Generator Config — The Essentials at a Glance

Colocation

The near-operation-file preset places generated types next to their matching query file, not in one monolithic file.

Custom scalars

Map DateTime, Decimal and JSON explicitly in the scalars block instead of letting them fall back to any.

Fragment masking

client-preset with useFragment enforces that components only see their own requested fields.

CI safeguards

graphql-codegen --check prevents stale generated types from landing on the main branch.

11. FAQ: GraphQL Code Generator Config

1What does the codegen.yml do?
Defines schema source, files to scan, and the plugin pipeline for generated code.
2typescript vs. typescript-operations vs. typescript-react-apollo?
Base types, then operation-specific types, then typed React hooks built on top.
3What is the near-operation-file preset?
Generates a dedicated .generated.ts next to every .graphql file instead of one central file.
4How do I map custom scalars like DateTime?
Via the scalars block, usually to string instead of Date, to avoid implicit serialization assumptions.
5What is fragment masking?
Produces opaque fragment types that only useFragment on the same fragment resolves, enforcing real component boundaries.
6withHooks vs. withHOC vs. withComponent?
Three output styles: hooks for modern components, HOCs for class components, render props as a third pattern.
7Why string instead of Date for DateTime?
Because scalars are serialized as strings over the wire; conversion should happen explicitly in application code.
8How do I prevent stale generated types in CI?
With graphql-codegen --check as a pull request gate against schema or query drift.
9Can near-operation-file and fragment masking be combined?
Yes, that's the default configuration recommended by the official docs for new React projects.
10Why does a large single-file output become a problem?
Every change touches the same file, causing regular merge conflicts when teams work in parallel.