PluginsPresetsclient-preset

Client preset

Package nameWeekly DownloadsVersionLicenseUpdated
@graphql-codegen/client-presetDownloadsVersionLicenseNov 22nd, 2024

Installation

npm i -D @graphql-codegen/client-preset

The client-preset provides typed GraphQL operations (Query, Mutation and Subscription) by perfectly integrating with your favorite GraphQL clients:

  • React

    • @apollo/client (since 3.2.0, not when using React Components (<Query>))
    • @urql/core (since 1.15.0)
    • @urql/preact (since 1.4.0)
    • urql (since 1.11.0)
    • graphql-request (since 5.0.0)
    • react-query (with graphql-request@5.x)
    • swr (with graphql-request@5.x)
  • Vue

    • @vue/apollo-composable (since 4.0.0-alpha.13)
    • villus (since 1.0.0-beta.8)
    • @urql/vue (since 1.11.0)

If your stack is not listed above, please refer to our framework/language specific plugins in the left navigation.

Getting started

For step-by-step instructions, please refer to our dedicated guide.

Config API

The client preset only exposes a set of underlying plugin’s config options. The preset is somewhat opinionated and crafted carefully for an optimal developer experience.

The client preset allows the following config options:

  • scalars: Extends or overrides the built-in scalars and custom GraphQL scalars to a custom type.
  • defaultScalarType: Allows you to override the type that unknown scalars will have. Defaults to any.
  • strictScalars: If scalars are found in the schema that are not defined in scalars an error will be thrown during codegen.
  • namingConvention: Available case functions in change-case-all are camelCase, capitalCase, constantCase, dotCase, headerCase, noCase, paramCase, pascalCase, pathCase, sentenceCase, snakeCase, lowerCase, localeLowerCase, lowerCaseFirst, spongeCase, titleCase, upperCase, localeUpperCase and upperCaseFirst.
  • useTypeImports: Will use import type {} rather than import {} when importing only types. This gives compatibility with TypeScript’s "importsNotUsedAsValues": "error" option.
  • skipTypename: Does not add __typename to the generated types, unless it was specified in the selection set.
  • arrayInputCoercion: The GraphQL spec allows arrays and a single primitive value for list input. This allows to deactivate that behavior to only accept arrays instead of single values.
  • enumsAsTypes: Generates enum as TypeScript string union type instead of an enum. Useful if you wish to generate .d.ts declaration file instead of .ts, or if you want to avoid using TypeScript enums due to bundle size concerns.
  • enumsAsConst: Generates enum as TypeScript const assertions instead of enum. This can even be used to enable enum-like patterns in plain JavaScript code if you choose not to use TypeScript’s enum construct.
  • futureProofEnums: Adds a catch-all entry to enum type definitions for values that may be added in the future.
  • dedupeFragments: Removes fragment duplicates for reducing data transfer. It is done by removing sub-fragments imports from fragment definition.
  • nonOptionalTypename: Automatically adds __typename field to the generated types, even when they are not specified in the selection set, and makes it non-optional.
  • avoidOptionals: This will cause the generator to avoid using TypeScript optionals (?) on types.
  • documentMode: Allows you to control how the documents are generated.

For more information or feature request, please refer to the repository discussions.

Fragment Masking

As explained in our guide, the client-preset comes with Fragment Masking enabled by default.

This section covers this concept and associated options in detail.

Embrace Fragment Masking principles

Fragment Masking helps express components’ data dependencies with GraphQL Fragments.

By doing so, we ensure that the tree of data is properly passed down to the components without “leaking” data. It also allows to colocate the Fragment definitions with their components counterparts:

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmItemFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: { film: FragmentType<typeof FilmItemFragment> }) => {
  const film = useFragment(FilmItemFragment, props.film)
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film

For a deeper and more visual explanation of Fragment Masking, please refer to Laurin’s article: Unleash the power of Fragments with GraphQL Codegen

For an introduction on how to design your GraphQL Query to leverage Fragment Masking, please refer to our guide.

The FragmentType<T> type

As explained in our guide, the top-level GraphQL Query should include the fragment (...FilmItem) and pass down the data to child components.

At the component props definition level, the FragmentType<T> type ensures that the passed data contains the required fragment (here: FilmItemFragment aka FilmItem in GraphQL).

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmItemFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: {
  /* the passed `film` property contains a valid `FilmItem` fragment 🎉 */
  film: FragmentType<typeof FilmItemFragment>
}) => {
  const film = useFragment(FilmItemFragment, props.film)
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film
⚠️

FragmentType<T> is not the Fragment’s type

A common misconception is to mix FragmentType<T> and the Fragment’s type. For example, for helper functions, testing, or if you don’t use Fragment Masking, you should get the Fragment’s type directly. In this scenario, you must import it from the generated files or extract it from the Fragment’s definition, as described in the next section.

The useFragment() helper

The useFragment() function helps narrow down the Fragment type from a given data object (ex: film object to a FilmItemFragment object):

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmItemFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: { film: FragmentType<typeof FilmItemFragment> }) => {
  const film = useFragment(FilmItemFragment, props.film)
  // `film` is of type `FilmItemFragment` 🎉
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film

useFragment() is not a React hook

useFragment() can be used without following React’s rules of hooks. To avoid any issue with ESLint, we recommend changing its naming to getFragmentData() by setting the proper unmaskFunctionName value:

codegen.ts
import { CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx', '!src/gql/**/*'],
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
        fragmentMasking: { unmaskFunctionName: 'getFragmentData' }
      }
    }
  }
}
 
export default config

Getting a Fragment’s type

Getting a Fragment’s type is achieved by importing the type that corresponds to your fragment, which is named based on the fragment name with a Fragment suffix:

src/Film.tsx
import { FilmItemFragment } from './gql'
 
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
  query allFilmsWithVariablesQuery($first: Int!) {
    allFilms(first: $first) {
      edges {
        node {
          ...FilmItem
        }
      }
    }
  }
`)
 
function myFilmHelper(film: FilmItemFragment) {
  // ...
}

Or, if you have access to the Fragment’s definition, you can extract the type from it without having to “guess” the name:

src/Film.tsx
import { ResultOf } from '@graphql-typed-document-node/core'
 
export const FilmItemFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
function myFilmHelper(film: ResultOf<typeof FilmItemFragment>) {
  // ...
}

Fragment Masking with nested Fragments

When dealing with nested Fragments, the useFragment() should also be used in a “nested way”.

You can find a complete working example here: Nested Fragment example on GitHub.

Fragment Masking with @defer Directive

If you use the @defer directive and have a Fragment Masking setup, you can use an isFragmentReady helper to check if the deferred fragment data is already resolved. The isFragmentReady function takes three arguments: the query document, the fragment definition, and the data returned by the query. You can use it to conditionally render components based on whether the data for a deferred fragment is available, as shown in the example below:

// index.tsx
import { useQuery } from '@apollo/client';
import { useFragment, graphql, FragmentType, isFragmentReady } from './gql';
 
const OrdersFragment = graphql(`
  fragment OrdersFragment on User {
    orders {
      id
      total
    }
  }
`)
const GetUserQueryWithDefer = graphql(`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      ...OrdersFragment @defer
    }
  }
`)
 
const OrdersList = (props: { data: FragmentType<typeof OrdersFragment> }) => {
  const data = useFragment(OrdersFragment, props.data);
  return (
    // render orders list
  )
};
 
function App() {
  const { data } = useQuery(GetUserQueryWithDefer);
  return (
    <div className="App">
      {data && (
        <>
          <span>Name: {data.name}</span>
          <span>Id: {data.name}</span>
          {isFragmentReady(GetUserQueryWithDefer, OrdersFragment, data) // <- HERE
						&& <OrdersList data={data} />}
        </>
      )}
    </div>
  );
}
export default App;

Fragment Masking and testing

A React component that relies on Fragment Masking won’t accept “plain object” as follows:

ProfileName.spec.ts
// ...
 
type ProfileNameProps = {
  profile: FragmentType<typeof ProfileName_PersonFragmentDoc>
}
 
const ProfileName = ({ profile }: ProfileNameProps) => {
  const { name } = useFragment(ProfileName_PersonFragmentDoc, profile)
  return (
    <div>
      <h1>Person Name: {name}</h1>
    </div>
  )
}
ProfileName.spec.ts
// ...
 
describe('<ProfileName />', () => {
  it('renders correctly', () => {
    const profile = { name: 'Adam' }
    render(
      <ProfileName
        profile={profile} // <-- this will throw TypeScript errors
      />
    )
 
    expect(screen.getByText('Person Name: Adam')).toBeInTheDocument()
  })
})

Since the component expects to receive “Masked data”, you will need to import the makeFragmentData() helper to “build” some masked data, as follow:

ProfileName.spec.ts
// ...
import { makeFragmentData } from '../gql'
 
describe('<ProfileName />', () => {
  it('renders correctly', () => {
    const profile = { name: 'Adam' }
    render(<ProfileName profile={makeFragmentData(profile, ProfileName_PersonFragmentDoc)} />)
 
    expect(screen.getByText('Person Name: Adam')).toBeInTheDocument()
  })
})

How to disable Fragment Masking

client-preset’s Fragment Masking can be disabled as follow:

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx', '!src/gql/**/*'],
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
        fragmentMasking: false
      }
    }
  }
}
 
export default config

Persisted Documents

Persisted documents (often also referred to as persisted queries or persisted operations) is a technique for reducing client to server upstream traffic by sending a unique identifier instead of the full GraphQL document. It is also commonly used to reduce the size of the client bundle as well as to improve security by preventing the client from sending and executing arbitrary GraphQL operations (and thus reducing attack surface).

Enable Persisted Documents

Persisted documents can be enabled by setting the persistedDocuments option to true:

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
        persistedDocuments: true
      }
    }
  }
}

By enabling this option GraphQL Code Generator will generate an additional file persisted-documents.json within your artifacts location.

    • fragment-masking.ts
    • gql.ts
    • graphql.ts
    • index.ts
    • persisted-documents.json

This file contains a mapping of the document’s hash to the document’s content.

persisted-documents.json
{
  "b2c3d4e5f6g7h8i9j0a1": "query Hello { hello }",
  "kae4fe7f6g7h8i9ej0ag": "mutation echo($msg: String!) { echo(message: $msg) }"
}

In addition the document hash will be added to the generated document node as a hash property.

app.ts
import { graphql } from './gql'
 
const HelloQuery = graphql(/* GraphQL */ `
  query Hello {
    hello
  }
`)
 
// logs "b2c3d4e5f6g7h8i9j0a1"
console.log(HelloQuery['__meta__']['hash'])

This hash can be used in the network layer of your GraphQL client to send the document hash instead of the document string.

Note that the server you sent the document hash to must be able to resolve the document hash to the document string.

Fetch example
import { graphql } from './gql'
 
const HelloQuery = graphql(/* GraphQL */ `
  query Hello {
    hello
  }
`)
 
const response = await fetch('http://yoga/graphql', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    accept: 'application/json'
  },
  body: JSON.stringify({
    extensions: {
      persistedQuery: {
        version: 1,
        sha256Hash: HelloQuery['__meta__']['hash']
      }
    }
  })
})
 
console.log(response.status)
console.log(await response.json())

Hashing algorithm

To override the default hash algorithm of sha1 set persistedDocuments.hashAlgorithm

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
        persistedDocuments: {
          hashAlgorithm: 'sha256'
        }
      }
    }
  }
}

Instead of using a preset algorithm, you can also provide your own hash function.

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      presetConfig: {
        persistedDocuments: {
          hashAlgorithm: operation => {
            const shasum = crypto.createHash('sha512')
            shasum.update(operation)
            return shasum.digest('hex')
          }
        }
      }
    }
  }
}

Normalized Caches (urql and Apollo Client)

Urql is a popular GraphQL client that utilizes a normalized cache. Because the client utilizes the __typename fields to normalize the cache, it is important that the __typename field is included in the persisted documents. The addTypenameSelectionDocumentTransform document transform can be used for achieving this.

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
import { addTypenameSelectionDocumentTransform } from '@graphql-codegen/client-preset'
 
const config: CodegenConfig = {
  schema: './**/*.graphqls',
  documents: ['./**/*.{ts,tsx}'],
  ignoreNoDocuments: true,
  generates: {
    './gql/': {
      preset: 'client',
      plugins: [],
      presetConfig: {
        persistedDocuments: true
      },
      documentTransforms: [addTypenameSelectionDocumentTransform]
    }
  }
}
 
export default config

Afterwards, you can send the hashes to the server.

Example with urql
import { createClient, cacheExchange } from '@urql/core'
import { persistedExchange } from '@urql/exchange-persisted'
 
const client = new createClient({
  url: 'YOUR_GRAPHQL_ENDPOINT',
  exchanges: [
    cacheExchange,
    persistedExchange({
      enforcePersistedQueries: true,
      enableForMutation: true,
      generateHash: (_, document) => Promise.resolve(document['__meta__']['hash'])
    })
  ]
})

Reducing Bundle Size

Large scale projects might want to enable code splitting or tree shaking on the client-preset generated files. This is because instead of using the map which contains all GraphQL operations in the project, we can use the specific generated document types.

The client-preset comes with a Babel and a swc plugin that enables it.

Babel Plugin

To configure the Babel plugin, update (or create) your .babelrc.js as follow:

.babelrc.js
const { babelOptimizerPlugin } = require('@graphql-codegen/client-preset')
 
module.exports = {
  presets: ['react-app'],
  plugins: [[babelOptimizerPlugin, { artifactDirectory: './src/gql', gqlTagName: 'graphql' }]]
}

SWC Plugin

⚠️

As of 2023/03/11, SWC’s custom plugins is still an experimental feature, that means unexpected breaking changes that makes specific nextjs versions or our plugin incompatible are possible, so please file an issue here if you faced any unexpected behavior/errors while using the plugin.

Pro tip: if you faced the following obscure error, or the nextjs dev script just instantly terminates the process without any errors, it most likely means that SWC is not happy with your nextjs version, so try upgrading it to latest if possible, then let us know of your current broken version to try fixing it or documenting its no longer supported.

thread '<unnamed>' panicked at 'failed to invoke plugin: failed to invoke plugin on 'Some("/app/node_modules/next/dist/client/router.js")'

The SWC plugin is not bundled in the client-preset package, so you will need to install it separately:

npm i -D @graphql-codegen/client-preset-swc-plugin

General

To use the SWC plugin without Next.js, update your .swcrc to add the following:

.swcrc
{
  // ...
  jsc: {
    // ...
    experimental: {
      plugins: [
        ['@graphql-codegen/client-preset-swc-plugin', { artifactDirectory: './src/gql', gqlTagName: 'graphql' }]
      ]
    }
  }
}

Vite React

To use the SWC plugin with Vite React, update your vite.config.ts to add the following:

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      plugins: [
        ['@graphql-codegen/client-preset-swc-plugin', { artifactDirectory: './src/gql', gqlTagName: 'graphql' }]
      ]
    })
  ]
})

Next.js

To use the SWC plugin with Next.js, update your next.config.js to add the following:

next.config.js
const nextConfig = {
  // ...
  experimental: {
    swcPlugins: [
      ['@graphql-codegen/client-preset-swc-plugin', { artifactDirectory: './src/gql', gqlTagName: 'graphql' }]
    ]
  }
}

Note that you will need to provide the artifactDirectory path that should be the same as the one configured in your codegen.ts

DocumentMode

The DocumentMode option can be used to control how the plugin will generate the document nodes.

By default, the generated documents are of type TypedDocumentNode which is a fully typed GraphQL operation AST. Example:

out/gql.ts
export type HelloQueryVariables = Exact<{ [key: string]: never }>
 
export type HelloQuery = { __typename?: 'Query'; hello: string }
 
export const HelloDocument = {
  kind: 'Document',
  definitions: [
    {
      kind: 'OperationDefinition',
      operation: 'query',
      name: { kind: 'Name', value: 'hello' },
      selectionSet: { kind: 'SelectionSet', selections: [{ kind: 'Field', name: { kind: 'Name', value: 'hello' } }] }
    }
  ]
} as unknown as DocumentNode<HelloQuery, HelloQueryVariables>

The documentMode option can be used to change the generated documents to string:

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      config: {
        documentMode: 'string'
      }
    }
  }
}

This will generate the following:

out/gql.ts
export type HelloQueryVariables = Exact<{ [key: string]: never }>
 
export type HelloQuery = { __typename?: 'Query'; hello: string }
 
export const HelloDocument = new TypedDocumentString(`
  query hello {
    hello
  }
`) as unknown as TypedDocumentString<HelloQuery, HelloQueryVariables>

It can then be used as follow:

Fetch example
import { graphql } from './gql'
 
const helloQuery = graphql(`
  query hello {
    hello
  }
`)
 
fetch('https:<your-graphql-api>', {
  method: 'POST',
  headers: {
    'content-type': 'application/json'
  },
  body: JSON.stringify({
    query: helloQuery.toString()
  })
})

When to use a string DocumentMode?

The string DocumentMode is useful when you want to reduce the bundle size of your application as you will get string literals instead of typed ASTs. This is useful when your GraphQL client allows you to send a string literal as the query and you don’t need to use the AST on the client, e.g. when using graphql-request, SWR, React Query, etc.