---
url: /get-started.md
---
# Introduction
`gql.tada` aims to tie GraphQL and TypeScript closer together and minimize friction,
by improving the experience of writing and using GraphQL with more editor feedback,
automatically derived types, and additional built-in tools that don't get in your
way.
Once `gql.tada` is set up, we write our GraphQL queries in pure TypeScript,
queries automatically infer their types, and editors provide immediate feedback,
auto-completion, diagnostics, and GraphQL type hints.
All on-the-fly in TypeScript with as few setup steps as possible.
### A demo in 128 seconds
---
---
url: /get-started/installation.md
description: How to get set up and ready
---
# Installation
We’ll go through the steps to get `gql.tada` set up properly.
A quick demo of what this looks like can be found [in an example project in the `gql.tada`
repository.](https://github.com/0no-co/gql.tada/blob/main/examples/example-pokemon-api/)
With `gql.tada`, you'll mainly interact with three different parts of the library:
* the library code you import from the `gql.tada` package
* the TypeScript plugin, `gql.tada/ts-plugin`
* and [the `gql.tada` CLI](/get-started/workflows)
## Step 1 — Installing packages
We’ll start by installing `gql.tada` as a dependency.
::: code-group
```sh [npm]
npm install gql.tada
```
```sh [pnpm]
pnpm add gql.tada
```
```sh [yarn]
yarn add gql.tada
```
```sh [bun]
bun add gql.tada
```
:::
Next, we’ll add the TypeScrpt plugin to our `tsconfig.json` to set it up in TypeScript’s
language server. This is the main configuration for both the TypeScript plugin and the
`gql.tada` CLI at the same time.
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"strict": true,
"plugins": [ // [!code ++]
{ // [!code ++]
"name": "gql.tada/ts-plugin", // [!code ++]
"schema": "./schema.graphql", // [!code ++]
"tadaOutputLocation": "./src/graphql-env.d.ts" // [!code ++]
} // [!code ++]
] // [!code ++]
}
}
```
:::
Setting up `gql.tada/ts-plugin` will start up a [“TypeScript Language Service Plugin”](https://github.com/microsoft/TypeScript/wiki/Writing-a-Language-Service-Plugin#whats-a-language-service-plugin) when TypeScript is analyzing a file in our IDE or editor. This provides editor hints, such as diagnostics,
auto-completions, and type hovers for GraphQL.
> \[!NOTE] VSCode Setup
> There may be extra steps you should take when you're using VSCode.
> [Read about these steps in the "VSCode Setup" section below.](#vscode-setup)
> \[!NOTE] Prior to TypeScript 5.5
> There are extra steps you must take when your TypeScript version is older than 5.5.
> [Read about these steps in the "Prior to TypeScript 5.5" section below.](#prior-to-typescript-5-5)
## Step 2 — Configuring a schema
We’ll need to set up a GraphQL schema for `gql.tada` to function correctly.
Without a schema, no typings and no editor hints will be available, since the
schema provides the GraphQL types, fields, and description information of
your GraphQL API.
To add a GraphQL schema to `gql.tada`, we'll be the `tsconfig.json`'s plugin
section we've just added and modify the `schema` option.
::: code-group
```json twoslash [tsconfig.json] {6}
{
"compilerOptions": {
"plugins": [
{
// @annotate: Configure your schema here
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
```
:::
The `schema` option currently allows for three different formats to load a schema. It accepts either:
* a path to a `.graphql` file containing a schema definition (in GraphQL SDL format)
* a path to a `.json` file containing a schema’s introspection query data
* a URL to a GraphQL API that can be introspected
::: code-group
```json [.graphql file] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql"
}
]
}
}
```
```json [.json file] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./introspection.json"
}
]
}
}
```
```json [URL] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "http://localhost:4321/graphql"
}
]
}
}
```
```json [URL with headers] {6-11}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": {
"url": "http://localhost:4321/graphql",
"headers": {
"Accept": "application/graphql-response+json"
}
}
}
]
}
}
```
:::
## Step 3 — Configuring typings
We're now ready to let `gql.tada` output a typings file.
This file is generated on the fly by the TypeScript plugin,
and can [also be generated using the CLI](/get-started/workflows#generating-the-output-file).
Where this file will be saved to is configured in the `tsconfig.json` file as
well using the `tadaOutputLocation` option.
::: code-group
```json twoslash [tsconfig.json] {7}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
// @annotate: Configure the output typings file location here
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
```
:::
Depending on the `tadaOutputLocation`'s configured file extension, there's
[two separate formats](/reference/config-format#tadaoutputlocation) this
file can be saved in. For most cases the `.d.ts` format is recommended
for best performance however.
Once we start up our editor, the TypeScript plugin will run and create
the output file. In this example, we’ve created a `src/graphql-env.d.ts` file.
When opening this file we should see code that looks like the following:
::: code-group
```ts [graphql-env.d.ts]
declare const introspection: {
__schema: { /*...*/ };
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: typeof introspection;
}
}
```
:::
The typings file is a representation of an introspected GraphQL schema
and allows types to be inferred for GraphQL documents in the
TypeScript type system. After this file is created by automatically,
`gql.tada` is set up project-wide and is **ready to be used.**
### Initializing `gql.tada` manually
With the prior instructions, we can import `graphql()` from `gql.tada` directly
and start writing GraphQL documents, but this default setup limits what we can do.
In a full setup, we want to customize scalars or pass further type configuration to
`gql.tada`.
To customize `gql.tada`, we’ll create a file that imports the output typings manually
and uses the `initGraphQLTada()` function to create our own `graphql()` function:
:::code-group
```ts twoslash [src/graphql.ts] {4-6}
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
This setup is also necessary if we're setting up [multiple schemas](/guides/multiple-schemas)
(for example, in a monorepo), since we'd have multiple output typings files if we're trying
to use `gql.tada` for multiple GraphQL schemas.
Instead of importing `graphql()` from `gql.tada`, we should now import it from our
custom `src/graphql.ts` file.
### Customizing scalar types
Now that we’ve set up a `src/graphql.ts` file, which uses `initGraphQLTada<>()` to create
a custom `graphql` function, we may also use this function to customize our scalars.
By default, `gql.tada` will have types defined for the [built-in scalars](https://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars)
in GraphQL. However, it won’t be able to know the serialized type of your custom scalars.
For instance, our schema may contain a `DateTime` scalar which, when queried, becomes
a string of `new Date().toISOString()`, however, `gql.tada` won’t know that this type
is a string.
:::code-group
```ts twoslash [src/graphql.ts] {3-6}
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
// ---cut-before---
export const graphql = initGraphQLTada<{
// @annotate: Define scalar types here
introspection: introspection;
scalars: {
DateTime: string;
JSON: any;
};
}>();
// ---cut-after---
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
When using these scalars, they’ll now be mapped to the types in the `scalars` object type.
***
## Extra Steps
A few extra steps may be necessary to install and use `gql.tada`.
These are called out, as needed, in the above sections, so you'll only
need to follow these steps depending on your workspace and setup.
### Prior to TypeScript 5.5
If you're using a TypeScript version that's **older** than [TypeScript 5.5](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/)
you will have to set up the TypeScript plugin differently.
Instead, of using `gql.tada/ts-plugin`, with older versions of TypeScript we'll
install `@0no-co/graphqlsp` directly. This is a package that contains the TypeScript
plugin that `gql.tada/ts-plugin` uses and aliases.
::: code-group
```sh [npm]
npm install --save-dev @0no-co/graphqlsp
```
```sh [pnpm]
pnpm add --save-dev @0no-co/graphqlsp
```
```sh [yarn]
yarn add --dev @0no-co/graphqlsp
```
```sh [bun]
bun add --dev @0no-co/graphqlsp
```
:::
Once `@0no-co/graphqlsp` is installed as a direct dependency, we'll update the `tsconfig.json`
to use it.
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"strict": true,
"plugins": [
{
"name": "gql.tada/ts-plugin", // [!code --]
"name": "@0no-co/graphqlsp", // [!code ++]
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
```
:::
***
### VSCode Setup
As shown above, `gql.tada` has a TypeScript plugin to provide
editor hints, such as diagnostics, auto-completions, and type hovers
for GraphQL. This plugin will load up when your workspace's
TypeScript installation is used by your editor's TypeScript server.
However, VSCode won't by default load up your workspace's TypeScript
installation and may instead load up a global installation, which
prevents the plugin from being loaded up.
To resolve this, you should create a `.vscode/settings.json` file to prompt you
[to use the workspace version of TypeScript](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript).
::: code-group
```js [.vscode/settings.json] {2-3}
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
```
:::
To enable syntax highlighting for GraphQL, you can install the official
[“GraphQL: Syntax Highlighting” VSCode extension.](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql-syntax)
***
### Vue and Svelte Support
If you're using Vue's `.vue` files and Svelte's `.svelte` files, the
TypeScript plugin won't be able to run in your editor under normal
circumstances.
While some implementations exist for TypeScript to run and type
check Vue and Svelte files, `gql.tada` won't have any output in
these cases.
However, while the TypeScript plugin may not support `.vue` and
`.svelte` files, [the `gql-tada check` command does.](/get-started/workflows#running-diagnostics)
To enable support for either files, you'll need to install the
corresponding support packages.
::: code-group
```sh [npm]
# for Vue
npm install -D @gql.tada/vue-support
# for Svelte
npm install -D @gql.tada/svelte-support
```
```sh [pnpm]
# for Vue
pnpm add -D @gql.tada/vue-support
# for Svelte
pnpm add -D @gql.tada/svelte-support
```
```sh [yarn]
# for Vue
yarn add -D @gql.tada/vue-support
# for Svelte
yarn add -D @gql.tada/svelte-support
```
```sh [bun]
# for Vue
bun add -d @gql.tada/vue-support
# for Svelte
bun add -d @gql.tada/svelte-support
```
:::
Once these are installed, the CLI's `check` and other commands will
be able to parse and check external files for `gql.tada` errors.
---
---
url: /get-started/writing-graphql.md
description: How to get set up and ready
---
# Writing GraphQL
In `gql.tada`, we write our GraphQL documents using the `graphql()`
function and receive the result and variables types inferred from
the document itself.
In this section, we'll see what operation types look like, how
we write fragments, and how to use scalar, enum, and input object
types.
::: info Imports
Some code examples may import `graphql()` from `gql.tada`.
However, if you’ve previously followed the steps on the “Installation” page
[to initialize `gql.tada` manually](./installation#initializing-gqltada-manually),
you’ll instead have to import your custom `graphql()` function, as
returned by `initGraphQLTada()`.
:::
## Queries
When passing a query to `graphql()`, it will be parsed in TypeScript’s type system
and the schema that’s set up is used to map this document over to a type.
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
const PokemonsQuery = graphql(`
query PokemonsList($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`);
```
The `PokemonsQuery` variable will have an inferred type that defines the
type of the data result of the query. When adding variables, the types of variables
are added to the inferred type as well.
The resulting type is known as a [`TypedDocumentNode`](https://github.com/dotansimha/graphql-typed-document-node)
and is supported by most GraphQL clients.
When passing a `gql.tada` query to a GraphQL client, the type
of input variables and result data are inferred automatically.
For example, with `urql` and React, this may look like the following:
```tsx twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { useQuery } from 'urql';
import { graphql } from 'gql.tada';
// @annotate: PokemonsQuery carries a type for data and variables:
const PokemonsQuery = graphql(`
query PokemonsList($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`);
const PokemonsListComponent = () => {
// @annotate: Types for data and variables are applied from PokemonsQuery:
const [result] = useQuery({
query: PokemonsQuery,
variables: { limit: 5 },
});
return (
{result.data?.pokemons?.map((pokemon) => (
- {pokemon?.name}
))}
);
};
```
The same applies to mutation operations, subscription operations, and fragment definitions.
The `graphql()` function will parse your GraphQL definitions, take the first definition it
finds and infers its type automatically.
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql, ResultOf, VariablesOf } from 'gql.tada';
// @annotate: The first definition’s types are inferred:
const MarkCollectedMutation = graphql(`
mutation MarkCollected($id: ID!) {
markCollected(id: $id) {
id
name
collected
}
}
`);
// @annotate: Inferring the definition’s variables…
type variables = VariablesOf;
// @annotate: …and the definition’s result type.
type result = ResultOf;
```
The above example uses the `ResultOf` and `VariablesOf` types for illustrative purposes.
These type utilities may be used to manually unwrap the types of a GraphQL `DocumentNode`
returned by `graphql()`.
***
## Fragments
The `graphql()` function allows for fragment composition, which means we’re able to create
a fragment and spread it into our definitions or other fragments.
Creating a fragment is the same as any other operation definition.
The type of the first definition, in this case a fragment, will be used
to infer the result type of the returned document:
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
```
Spreading this fragment into another fragment or operation definition requires us
to pass the fragment into a tuple array on the `graphql()` function’s second argument.
```ts twoslash
import './graphql/graphql-env.d.ts';
import { graphql } from 'gql.tada';
const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
// ---cut-before---
const PokemonsList = graphql(`
query PokemonsList {
pokemons(limit: 10) {
id
...Pokemon
}
}
`, [PokemonFragment]);
```
Here we spread our `PokemonFragment` into `PokemonsList` by passing it into
the `graphql()` function and then using its name in the GraphQL document.
### Fragment Masking
However, in `gql.tada` a pattern called **“Fragment Masking”** applies.
`PokemonsList`’s result type does not contain the `name` and `collected` field
from the spread fragment and instead contains a reference to the `PokemonFragment`.
This forces us to unwrap, or rather “unmask”, the fragment first.
```tsx twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { useQuery } from 'urql';
import { graphql, readFragment } from 'gql.tada';
const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
const PokemonsList = graphql(`
query PokemonsList {
pokemons(limit: 10) {
id
...Pokemon
}
}
`, [PokemonFragment]);
const PokemonsListComponent = () => {
const [result] = useQuery({ query: PokemonsList });
// @annotate: The data here does not contain our fragment’s fields:
const { pokemons } = result.data!;
return pokemons?.map((item) => {
// @annotate: Calling readFragment() unwraps the type of the fragment:
const pokemon = readFragment(PokemonFragment, item);
return pokemon?.name;
});
};
```
When spreading a fragment into a parent definition, the parent only contains a reference to the fragment.
This means that we’re isolating fragments. Any spread fragment data cannot be accessed directly until
the fragment is unmasked.
```ts twoslash
import './graphql/graphql-env.d.ts';
declare var client: import('@urql/core').Client;
// ---cut-before---
import { graphql, readFragment } from 'gql.tada';
const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
const PokemonQuery = graphql(`
query Pokemon($id: ID!) {
pokemon(id: $id) {
id
...Pokemon
}
}
`, [PokemonFragment]);
const result = await client.query(PokemonQuery, { id: '001' });
// @annotate: Pokemon’s data is only accessible once unmasked with readFragment()
const pokemon = readFragment(PokemonFragment, result.data?.pokemon);
```
`PokemonFragment`’s fragment mask in `PokemonQuery` is only unmasked and accessible as its plain result
type once we call `readFragment()` on the fragment mask.
In this case, we’re passing `data.pokemon`, which is an object containing the fragment
mask.
This all only happens and is enforced at a type level, meaning that we don’t incur any overhead
during runtime for masking our fragments.
### Fragment Composition
Fragment Masking is a concept that only exists to enforce proper **Fragment Composition**.
In a componentized app, fragments may be used to define the data requirements of UI components,
which means, we’ll define fragments, colocate them with our components, and compose them into
other fragments or our query.
Since all fragments are masked in our types, this colocation is enforced and we maintain our
data requirements to UI component relationship.
For example, our `PokemonFragment` may be associated with a `Pokemon` component rendering
individual items:
::: code-group
```tsx twoslash [components/Pokemon.tsx]
import './graphql/graphql-env.d.ts';
// ---cut-before---
// @filename: components/Pokemon.tsx
// ---cut---
import { graphql, readFragment, FragmentOf } from 'gql.tada';
export const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
interface Props {
// @annotate: The component accepts a fragment mask of PokemonFragment:
data: FragmentOf;
}
export const PokemonComponent = ({ data }: Props) => {
// @annotate: In the component body we unwrap the fragment mask:
const pokemon = readFragment(PokemonFragment, data);
return {pokemon.name};
};
```
:::
The `FragmentOf` type is used as an input type above. This type accepts our fragment document
and creates the fragment mask that a fragment spread would create as well.
We can then use our new `PokemonComponent` in our `PokemonsListComponent` and compose its `PokemonFragment`
into our query:
::: code-group
```tsx twoslash [components/PokemonsList.tsx]
import './graphql/graphql-env.d.ts';
// ---cut-before---
// @filename: components/Pokemon.tsx
import { graphql, readFragment, FragmentOf } from 'gql.tada';
export const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
collected
}
`);
interface Props {
// @annotate: The component accepts a fragment mask of PokemonFragment:
data: FragmentOf;
}
export const PokemonComponent = ({ data }: Props) => {
// @annotate: In the component body we unwrap the fragment mask:
const pokemon = readFragment(PokemonFragment, data);
return {pokemon.name};
};
// @filename: components/PokemonsList.tsx
// ---cut---
import { graphql } from 'gql.tada';
import { useQuery } from 'urql';
import { PokemonFragment, PokemonComponent } from './Pokemon';
const PokemonsListQuery = graphql(`
query PokemonsList {
pokemons(limit: 10) {
id
...Pokemon
}
}
`, [PokemonFragment]);
export const PokemonsListComponent = () => {
const [result] = useQuery({ query: PokemonsListQuery });
return (
{result.data?.pokemons?.map((pokemon) => (
// @annotate: The masked fragment data is accepted as defined by FragmentOf:
))}
);
};
```
:::
Meaning, while we can unmask and use the `PokemonFragment`’s data in the `PokemonComponent`,
the `PokemonsListComponent` cannot access any of the data requirements defined by and meant for the
`PokemonComponent`.
## Scalars
As we've seen in prior examples, when selection fields, `gql.tada` infers the type
of fields from the given schema automatically. Fields will be nullable if the schema
doesn't mark them as non-nullable. The default scalars will be typed by their
[JSON serialization](https://spec.graphql.org/draft/#sec-JSON-Serialization)
value.
| Scalar | Type |
| --| -- |
| `String` | `string` |
| `Boolean` | `boolean` |
| `Int` | `number` |
| `Float` | `number` |
When customizing a scalar, the inferred value type of a field will change according
to the type we pass in the `scalars` mapping type however, as seen in the
["Customizing scalar types" section](./installation#customizing-scalar-types).
:::code-group
```ts twoslash [src/graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
// ---cut-before---
export const graphql = initGraphQLTada<{
introspection: introspection;
// @annotate: The ID type now takes on a special type
scalars: {
ID: `${number}`;
};
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
Now, creating the `PokemonFragment` with our custom `graphql()` function,
the `id` field changes to the specified `ID` type.
::: code-group
```tsx twoslash [components/Pokemon.tsx]
// @filename: src/graphql.ts
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from '../graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
ID: `${number}`;
};
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
// ---cut---
// @filename: components/Pokemon.tsx
// ---cut---
import { graphql, readFragment, FragmentOf } from '../src/graphql';
export const PokemonFragment = graphql(`
fragment Pokemon on Pokemon {
id
name
}
`);
interface Props {
data: FragmentOf;
}
export const PokemonComponent = ({ data }: Props) => {
const pokemon = readFragment(PokemonFragment, data);
return {pokemon.name};
};
```
:::
In this case, we've defined all `ID` types to instead use a more specific
type to say that they're stringified numbers, to demonstrate how IDs are
structured in this example API.
### Reusing Scalar Types
Scalar types are often reused in utility functions and it isn't
always possible to [write a fragment](../guides/fragment-colocation)
for all of our utility functions, as some of them may not be
consuming more than a single scalar value.
Following from our last code example, we'd like to now reuse
the `ID` type in a small function that accepts the type and parses
it further.
To do this, we can use the [`graphql.scalar()` helper
function](../reference/gql-tada-api#graphql-scalar) to retrieve
the type of the scalar.
::: code-group
```tsx twoslash [utils/parseId.ts]
// @filename: src/graphql.ts
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from '../graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
ID: `${number}`;
};
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
// ---cut---
// @filename: utils/parseId.ts
// ---cut---
import { graphql } from '../src/graphql';
export type ID = ReturnType>;
export const parseId = (id: ID): number => {
return Number(id);
};
```
:::
In the above example, we've used `ReturnType>`
to retrieve the type of our scalar directly from our configuration.
But `graphql.scalar` is also useful when we wish to repeat the type across
the codebase instead. If we want to passively check that the GraphQL
scalar type is compatible to a local type, we can also call `graphql.scalar()`
directly and let TypeScript check for our value to be compatible with
the configured type instead.
::: code-group
```tsx twoslash [utils/parseId.ts]
// @filename: src/graphql.ts
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from '../graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
ID: `${number}`;
};
}>();
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
// ---cut---
// @filename: utils/parseId.ts
// ---cut---
import { graphql } from '../src/graphql';
export type ID = `${number}`;
export const parseId = (id: ID): number => {
const value = graphql.scalar('ID', id);
return Number(value);
};
```
:::
### Enum Types
When `gql.tada` infers the type of an enum, the output type becomes
a union of all possible literal values.
```graphql
enum PokemonType {
Bug
Dark
Dragon
# ...
}
```
```ts
type PokemonType =
| 'Bug'
| 'Dark'
| 'Dragon';
/* ...*/
```
And similarly to scalars, we can retrieve the type of enums with the
`graphql.scalar()` helper and reuse them.
```tsx twoslash
// @filename: src/graphql.ts
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from '../graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
// ---cut---
// @filename: utils/pokemonType.ts
// ---cut---
import { graphql } from '../src/graphql';
export type PokemonType = ReturnType>;
export const isBugType = (input: 'Bug') => {
const pokemonType = graphql.scalar('PokemonType', input);
return input === 'Bug';
};
```
Calling `graphql.scalar()` like in the above example also allows us to check
a hardcoded subset of values against our scalar while implementing other
utility functions.
Since `graphql.scalar()` will enforce the second argument to be typed as
the scalar itself, we can also use it to enforce compatibility of local
types to GraphQL types.
::: info TypeScript Enums
Enum types can only be output as unions of string literals in `gql.tada`, but
if you're migrating from a different code generator you may instead be using
and importing generated TypeScript `enum`s or `const enum`s.
You can still use your own values for enum types and configure them using the
`scalars` option to replace their inferred values. But if you do this, `gql.tada`
won't be able to keep them up-to-date for you.
:::
### Input Objects
Lastly, the most complex types that `graphql.scalar()` can return for us
are `input` types.
```tsx twoslash
// @filename: src/graphql.ts
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from '../graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
// ---cut---
// @filename: utils/searchPokemon.ts
// ---cut---
import { graphql } from '../src/graphql';
export type SearchPokemon = ReturnType>;
```
Reusing input types is common when we create local state that isn't immediately
used as operation variables, or doesn't match the variables types of some operations.
## Abstract Types
When a field returns an abstract type (a union or interface) we select its
possible types using inline fragments (`... on Type`). Selecting `__typename`
alongside them gives TypeScript a discriminant it can use to narrow the result
to a single variant.
```ts twoslash
import { initGraphQLTada, type ResultOf } from 'gql.tada';
type introspection = {
name: 'sample';
query: 'Query';
mutation: never;
subscription: never;
types: {
String: { kind: 'SCALAR'; name: 'String' };
Query: {
kind: 'OBJECT';
name: 'Query';
fields: {
search: { name: 'search'; type: { kind: 'UNION'; name: 'SearchResult'; ofType: null } };
};
};
SearchResult: { kind: 'UNION'; name: 'SearchResult'; fields: {}; possibleTypes: 'Article' | 'Photo' };
Article: {
kind: 'OBJECT';
name: 'Article';
fields: {
title: { name: 'title'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null } } };
};
};
Photo: {
kind: 'OBJECT';
name: 'Photo';
fields: {
url: { name: 'url'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null } } };
};
};
};
};
const graphql = initGraphQLTada<{ introspection: introspection }>();
// ---cut-before---
const SearchQuery = graphql(`
query Search {
search {
__typename
... on Article { title }
... on Photo { url }
}
}
`);
declare const data: ResultOf;
if (data.search?.__typename === 'Article') {
// @annotate: data.search is narrowed to Article here, so title is available
data.search.title;
}
```
Without `__typename` in the selection set, we have no discriminant to let TypeScript
tell the variants apart. In some cases `gql.tada` may automatically add optionally-typed
`__typename` fields, so the result types are still readable. But, select `__typename`
manually when you encounter unions or interfaces, so your code can switch between
them directly.
---
---
url: /get-started/workflows.md
description: How to use and adapt the CLI in your workflows
---
# Essential Workflows
## 1. Setup
### Downloading Schemas
The [`schema` setting](/reference/config-format#schema) supports
loading your GraphQL schema from an SDL or introspection file,
as well as making a GraphQL introspection request to a URL.
While this is convenient you may not want your schema to be
introspected from an API running locally or remotely indefinitely.
However, as you may not be maintaining your GraphQL API in the
same repository or your GraphQL server may not output an SDL
file itself, the `generate schema` CLI command exists to close
this gap.
To introspect an API and download your schema, run the `generate schema`
command while passing your API's URL.
```sh
gql-tada generate schema 'http://api.test/graphql' --output './schema.graphql'
```
When no `--output` argument is passed, the command will attempt to
use your configuration's `schema` setting, provided it's a file path.
You may also pass `--header` arguments, which define headers sent
during the GraphQL introspection request. If a GraphQL endpoint
requires authentication headers, you may use this to pass in
tokens or authorization headers.
```sh
gql-tada generate schema 'http://api.test/graphql' --header "Authorization: $ENV_TOKEN"
```
***
### The `doctor` command
Since you've run through the steps on the [Installation
page](/get-started/installation), you may have seen that there
are several moving parts to `gql.tada`, including needing
an output file to be generated and relying on the TypeScript
plugin to display diagnostics.
To prevent any of these parts working improperly, and to detect
whether there are any issues in your configuration or with
your setup, the `doctor` command exists.
```sh
gql-tada doctor
```
The `doctor` command runs through several environment checks,
loads your configuration, and checks your schema to make sure
you don't run into any unexpected issues.
While it's entirely optional, it doesn't hurt to run it before
you get started or when onboarding a new team member onto
`gql.tada`.
## 2. Editing
Usually while editing your code, the TypeScript plugin
takes care of several things automatically:
* it generates the output typings file
* it provides type hints and suggestions
* it displays diagnostics when it detects a problem
However, you can also generate the output typings file
or get diagnostics outside of your editor. This is especially
important if you need diagnostics or the output file before
or without opening your editor, or if your editor does not
support TypeScript plugins.
### Generating the output file
As we've learned on the [Installation page](/get-started/installation#step-3-—-configuring-typings),
the output typings file is necessary for `gql.tada` to infer types
of GraphQL documents as it contains an introspection type of
your schema.
To generate the output typings file, use the `gql-tada` CLI's
`generate turbo` command.
```sh
gql-tada generate output
```
The `generate output` command loads your schema, generates
introspection output and finally saves the output typings file.
Just like the TypeScript plugin, the command will
use the `tadaOutputLocation` setting to determine where to
write the output file to, and will load your schema
using the `schema` setting:
::: code-group
```json [tsconfig.json] {7}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql"
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
```
:::
The output typings file essentially contains the regular introspection
data of your schema. If the format is changed from a `.d.ts` to a
`.ts` file, the introspection data is even reusable for runtime
code.
However, the `d.ts` file type is recommended as it's more efficient
for the TypeScript type checker for larger schemas. This format is
also preprocessed into an intermediary format.
::: info Should the output tyings file be committed?
You can decide yourself whether you want to check the output typings file
into version control.
Without the typings file, GraphQL types can't be inferred and you'll
get errors when running type checks, for example with the `tsc`
command.
Committing the output file to your repository has the advantage
that you'll always be in a state to run type checks.
:::
***
### Running diagnostics
The TypeScript plugin runs several checks on your code, providing
diagnostics releant to your GraphQL schema right in your editor.
But to run `gql.tada`'s diagnostics as a standalone process we can
use the CLI's `check` command instead. The command runs all
diagnostics and gives us an idea of GraphQL-related issues across
a whole workspace.
```sh
gql-tada check
```
The `check` command loads your schema then runs diagnostics
on your code. This includes both diagnostics specific to
`gql.tada` as well as GraphQL validation, and
checks against your GraphQL schema.
The GraphQL checks that are run are basically the same that
your GraphQL server would run during
[GraphQL Validation](https://graphql.org/learn/validation/).
For example, when you write a query that selects fields that
don't exist on your schema, or you pass invalid arguments
to a field an error will be displayed.
::: info Why doesn't `tsc` show me diagnostics?
TypeScript plugins are hook into the TypeScript language
server API, which is specific to editor and IDE features.
During other tasks, like when you run `tsc` or other TypeScript
compiler tools, TypeScript plugins aren't loaded.
The `gql-tada check` command exists to be a standalone
version of GraphQL-related diagnostics instead and itself
loads the TypeScript plugin's diagnostics code.
:::
Two diagnostics that feature in `gql.tada` output opinionated
warnings that may not be relevant to your codebase:
* [`trackFieldUsage`](/reference/config-format#trackfieldusage)
* [`shouldCheckForColocatedFragments`](/reference/config-format#shouldcheckforcolocatedfragments)
## 3. Committing
`gql.tada` usually infers the types of GraphQL
documents entirely in TypeScript's type system.
It has types to parses documents and convert them
to types. If you worked with TypeScript before
you may have already spotted a problem here.
For each GraphQL document you add more, TypeScript's
type checker has more work to do, and over time
type checking will get slower and slower as your
codebase grows.
### Turbo Mode
To help with this and prevent performance issues,
the `gql-tada` CLI has a `turbo` command that
pre-processes types and outputs a type cache.
```sh
gql-tada turbo
```
The `turbo` command scans your codebase for GraphQL
documents and evaluates their TypeScript types ahead
of time. It will then write these types to a type cache,
which contains all your documents' pre-evaluated types.
You can update your configuration to change where this
type cache gets written to with the `tadaTurboLocation`
setting:
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql"
"tadaOutputLocation": "./src/graphql-env.d.ts",
"tadaTurboLocation": "./src/graphql-cache.d.ts" // [!code ++]
}
]
}
}
```
:::
If you inspect the written file, you'll see that it's a
regular `d.ts` typings file which contains a map of GraphQL
document strings, as they appear in your code, to TypeScript
type literals.
If you're familiar with GraphQL type generation tools, you
may recognize that this is essentially a compromise between
"codegen tools",
which [are explained further on the page on "Typed Documents"](/guides/typed-documents#type-generation),
and the pure type inference approach that
`gql.tada` takes out of the box.
::: info Should the type cache be committed?
You can decide yourself whether you want to check the type cache file
into version control.
Committing it to your repository has the advantage that when you
or someone else starts working on a new set of changes,
TypeScript's type checks will be as fast as they can be, which
can improve the Developer Experience on larger codebases.
:::
## 4. CI Checks
Integrating the `gql-tada` CLI into your continuous integration
pipeline takes just adding a few commands and should effectively
replicate the errors you may see in an editor when using `gql.tada`.
At the very least, you'll likely want to run the `check` command in your
CI environment.
```sh
gql-tada generate output
gql-tada check
```
::: details GitHub Actions Example
If you're using GitHub Actions, you can run the commands in a simple step
in your workflow's jobs.
```yaml
- name: "gql.tada Checks"
run: |
gql-tada generate output
gql-tada check
```
On GitHub Actions, the `check` command will also integrate with GitHub's,
and annotate errors and warnings inside the GitHub UI on pull requests,
for instance.
:::
The `generate output` command,
as [previously mentioned](#generating-the-output-file),
generates the typings output file and since this file is necessary for
type inference, if it's not generated and missing, running type
checks (for instance, with `tsc`) will likely fail with type errors.
### Verifying committed output files
As you've seen on this page, there are two different output files we're
concerned with when running inside an continuous integration environment.
* the output typings file (via [the `generate output` command](#generating-the-output-file))
* the type cache file (via [the `turbo` command](#turbo-mode))
Checking these files into your repository makes sure that your
codebase is less reliant on running `gql.tada`, and that anyone
who clones your code does not have to even know how to use
`gql.tada`.
This may mean however that you want to keep these files up-to-date
and check for them in your CI's checks as well.
```sh
gql-tada generate-output
gql-tada turbo
git diff --name-status --exit-code .
```
::: details GitHub Actions Example
If you're using GitHub Actions, you can run the commands in a simple step
in your workflow's jobs.
```yaml
- name: "gql.tada Checks"
run: |
gql-tada generate output
gql-tada generate turbo
git diff --name-status --exit-code .
```
:::
The `git diff` command added at the end will fail if any unstaged changes are
present. Adding this can help fail your CI step, if any of `gql.tada`'s
files need to be updated.
---
---
url: /guides/typed-documents.md
description: How GraphQL documents and TypeScript come together
---
# Typed Documents
Although GraphQL defines conventions and guarantees for the
client-side GraphQL query language and the server-side
GraphQL type system, it’s still ultimately an API specifaction
for client-server applications.
In GraphQL, we create schemas that describe the type system
that queries are executed against, and said schema describes
a shape of GraphQL types and scalars.
As such, even if we use strong types on the client-side and
strong types on the server-side, we still have to bridge the
gap between both ends.
## Schemas and Queries
Given a GraphQL schema, expressed here in the Schema Definition
Language (“SDL”), in its simplest form, we define fields on
objects that, when queried, may resolve to scalar values.
```graphql
type Query {
helloWorld: String
numberOfRequests: Int!
}
```
When querying this schema we may write a query that requests
our defined fields:
```graphql
query {
helloWorld
numberOfRequests
}
```
Simplifying this, a GraphQL query, which has been validated against
a GraphQL schema, matches a subset of the structure our GraphQL
schema defines. However, while it “selects” fields and defines
how to execute their resolvers, type information is only present
on the schema.
As such, as per the specification, a GraphQL API with the above
SDL may only return data matching our query, such as:
```json
{
"helloWorld": "Hello!",
"numberOfRequests: 1
}
```
It’s the server-side type system’s responsibility to ensure
that when this schema is executed against a valid query, that
the execution result matches the types defined.
As such, while the server-side, written in any library for any
language implementing the GraphQL specification, has complete
knowledge of the schemas types and structure, queries are
subject to these types *implicitly*.
## Type Generation
If we’re using TypeScript on the client-side and have a set of
GraphQL documents that we may execute against our schema, we
can only know the documents’ result types by looking comparing
them to the schema.
In GraphQL, this is often done ahead of time in with “Type Generation”.
This means that we input our schema and our queries into a process
at compile-time and convert the shape of the query to TypeScript’s
type system.
As such, the shape of data in the queries above would match
a TypeScript type looking like the following:
```ts
interface Result {
helloWorld: string | null;
numberOfRequests: number;
}
```
In many tools this is done using code generation, a process that,
like many concepts in GraphQL, has already been established with
JSON Schemas, or other API-shape specifications.
In code generation, we would have to ensure that a compile-time
tool generates or connects our TypeScript type to what we use
at runtime — a query string or AST.
With `gql.tada`, this all happens in the TypeScript type system
and is more invisible since no files are automatically
generated per GraphQL document.
## Integration with Clients
If we don’t integrate GraphQL on the client-side with Type Generation,
then a minimal example using GraphQL is quite simple.
In this example, we’ll have a GraphQL query sent to an API using a simple
`fetch` call:
::: code-group
```ts twoslash [query.ts]
import { DocumentNode, parse, print } from 'graphql';
const query = parse(/* GraphQL */ `
{
helloWorld
numberOfRequests
}
`);
async function execute(query: DocumentNode, variables?: any): Promise {
const response = await fetch('/graphql', {
method: 'POST',
body: JSON.stringify({
query: print(query),
variables,
}),
});
return (await response.json()).data;
}
const data = await execute(query);
```
:::
In TypeScript however, we’re lacking two vital integration points here.
Without Type Generations, neither `data` nor `variables` are typed
according to our GraphQL schema, although with GraphQL’s guarantees these
types should be unambiguous.
### Manual Type Generation
With manual code generation tools, a separate tool would output a file
containing our query’s types. For instance, it may output a separate file
that contains the `Result` and `Variables` types we need:
::: code-group
```ts twoslash [query.generated.ts]
export type Result = {
helloWorld: string | null;
numberOfRequests: number;
};
export type Variables = {};
```
:::
However, this now requires us to make an effort to include these types
manually in our `execute` function:
::: code-group
```ts twoslash [query.ts]
// @filename: query.generated.ts
export type Result = {
helloWorld: string | null;
numberOfRequests: number;
};
export type Variables = {};
// @filename: query.ts
// ---cut---
import { DocumentNode, parse, print } from 'graphql';
import type { Result, Variables } from './query.generated';
const query = parse(/* GraphQL */ `
{
helloWorld
numberOfRequests
}
`);
// @annotate: We add generics to our function:
async function execute(
query: DocumentNode,
variables: Variables
): Promise {
const response = await fetch('/graphql', {
method: 'POST',
body: JSON.stringify({
query: print(query),
variables,
}),
});
return (await response.json()).data;
}
// @annotate: We pass our generated types to these generics
const data = await execute(query, {});
```
:::
This is a very manual process though that doesn’t match our expectation that **GraphQL
is strongly typed and types should hence be inferred implicitly.**
### `TypedDocumentNode` types
What we really wish to do with client-side GraphQL is to attach our generated types
to the `DocumentNode` type directly. This would mean that our `query` above can only
lead to the correct types being used. Furthermore, by using said query, its types
could be inferred automatically.
Instead of having a `DocumentNode` type, ideally, we’d want the query to be typed
as `TypedDocumentNode`.
As a result, GraphQL in TypeScript has two ways of attaching types to GraphQL documents:
* the [`@graphql-typed-document-node/core`](https://github.com/dotansimha/graphql-typed-document-node) type
* the [`graphql` package’s](https://github.com/graphql/graphql-js/blob/2aedf25/src/utilities/typedQueryDocumentNode.ts) type
Our type generation tools can now output a `TypedDocumentNode` that has types attached
to it directly:
::: code-group
```ts twoslash [query.generated.ts]
import { parse } from 'graphql';
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
type Result = {
helloWorld: string | null;
numberOfRequests: number;
};
type Variables = {};
export const query: TypedDocumentNode = parse(
'{ helloWorld, numberOfRequests }'
);
```
:::
Which for clients executing a GraphQL query means, that they can infer the types
of a given GraphQL query by matching it against `TypedDocumentNode` instead.
In our example, we can now infer the generic types from the `query` instead:
::: code-group
```ts twoslash [query.ts]
// @filename: query.generated.ts
import { parse } from 'graphql';
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
interface Result {
helloWorld: string | null;
numberOfRequests: number;
}
interface Variables {}
export const query: TypedDocumentNode = parse(
'{ helloWorld, numberOfRequests }'
);
// @filename: query.ts
// ---cut---
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
import { DocumentNode, parse, print } from 'graphql';
import { query } from './query.generated';
async function execute(
query: TypedDocumentNode,
variables: Variables
): Promise {
const response = await fetch('/graphql', {
method: 'POST',
body: JSON.stringify({
query: print(query),
variables,
}),
});
return (await response.json()).data;
}
// @annotate: Types are now inferred from the query argument
const data = await execute(query, {});
```
:::
### `gql.tada` type inference
Having types output by a separate code generation tool again doesn’t quite match our
expectation that **GraphQL is strongly typed and types should hence be inferred implicitly.**
In `gql.tada` however, the idea is that we get from writing a query to having a
`TypedDocumentNode` type just via TypeScript inference, without running a separate
tool or having files be generated for each query.
```ts twoslash
// @filename: graphql-env.d.ts
export type introspection = {
"__schema": {
"queryType": {
"name": "Query"
},
"mutationType": null,
"subscriptionType": null,
"types": [
{
"kind": "OBJECT",
"name": "Query",
"fields": [
{
"name": "helloWorld",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"args": []
},
{
"name": "numberOfRequests",
"type": {
"kind": "NON_NULL",
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"args": []
}
],
"interfaces": []
},
{
"kind": "SCALAR",
"name": "Int"
},
{
"kind": "SCALAR",
"name": "String"
}
],
"directives": []
}
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection
}
}
// @filename: index.ts
import './graphql-env.d.ts';
// ---cut---
import { graphql } from 'gql.tada';
// @annotate: We get a TypedDocumentNode without extra imports
const query = graphql(`
{
helloWorld
numberOfRequests
}
`);
```
In essence, what `gql.tada` does is give you a fully typed GraphQL document that
tells TypeScript what the `Result` and `Variables` types are just via inference.
## Client Support
Today, supporting typed documents in GraphQL is an accepted and de-facto standard,
and below you can find a non-exhaustive list of GraphQL clients that support
typed documents and will hence also work well with `gql.tada`.
::: code-group
```ts twoslash [@apollo/client]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { useQuery } from '@apollo/client/react';
const getPokemonsQuery = graphql(`
query GetPokemons {
pokemons {
id
name
}
}
`);
function Pokemons() {
const { loading, error, data } = useQuery(getPokemonsQuery);
}
```
```ts twoslash [@urql/core]
import './graphql/graphql-env.d.ts';
declare var client: import('@urql/core').Client;
// ---cut-before---
import { graphql } from 'gql.tada';
const getPokemonsQuery = graphql(`
query GetPokemons {
pokemons {
id
name
}
}
`);
async function getPokemons() {
const { data } = await client.query(getPokemonsQuery, {});
}
```
```ts twoslash [urql]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { useQuery } from 'urql';
const getPokemonsQuery = graphql(`
query GetPokemons {
pokemons {
id
name
}
}
`);
function Pokemons() {
const [{ fetching, error, data }] = useQuery({ query: getPokemonsQuery });
}
```
```ts twoslash [graphql-request]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import request from 'graphql-request';
const getPokemonsQuery = graphql(`
query GetPokemons {
pokemons {
id
name
}
}
`);
async function getPokemons() {
const data = await request('/graphql', getPokemonsQuery);
}
```
:::
---
---
url: /guides/fragment-colocation.md
description: How GraphQL fragments are effectively used in componentized apps.
---
# Fragment Colocation
When presenting GraphQL, its features often turn into a
box-ticking exercise of comparing it to alternative solutions of
server-client API design, until we may ask ourselves whether
GraphQL’s strengths mostly lie in bringing a community together
with clever decisions we can now all agree and rely on…
However, while some of what makes GraphQL great is that many
of its core principles aren’t new ideas, its less talked about
strength lies in fragment composition and hierarchical schema design,
which matches our data needs for componentized apps.
## Introduction to Fragments
In GraphQL, fragments have many uses, and the uses of
“Fragment Colocation” are basically a combination of many
of the other uses for fragments.
### Reusing Selection Sets
At their most fundamental, fragments allow us to define a
selection set and reuse this set in multiple places of our
GraphQL document.
```ts
import { graphql } from 'gql.tada';
const query = graphql(`
query PostsOverview {
latestPosts {
...PostCard
}
trendingPosts {
...PostCard
}
}
fragment PostCard on Post {
id
text
createdAt
}
`);
```
In the prior example, we’ve extracted two selection sets
into a `PostCard` fragment. When a query we’re writing
uses the same data in multiple code paths, we may use
fragments to only write a re-used selection set once.
### Type Conditions
Fragments are also used whenever we’re trying to specify
that a certain selection set only applies to one possible
type of an abstract type, like a `union` or `interface`.
```ts
import { graphql } from 'gql.tada';
const query = graphql(`
query PostsOverview {
latestPosts {
id
...MediaCard
... on MediaPost {
videoUrl
}
...TextCard
... on TextPost {
text
}
}
}
`);
```
The above example shows a query for a schema where `latestPosts`
exposes an interface that is implemented by two types;
`MediaPost` and `TextPost`.
We may use fragments to conditionally apply a selection set
to either of these types, which is like “Type Narrowing” in
GraphQL.
> \[!TIP]
> The above example uses an inline fragment spread, however, the
> same principle of type conditions applies to regular fragments
> and fragment spreads.
### `@include` & `@skip` Conditions
GraphQL also features two built-in directives, `@include` and
`@skip`, which we can use to conditionally include a fragment,
based on a variable we pass to our query.
```ts
import { graphql } from 'gql.tada';
const query = graphql(`
query PostsOverview($showDetails: Boolean!) {
latestPosts {
id
text
...PostDetails @include(if: $showDetails)
}
}
fragment PostDetails on Post {
id
author {
name
}
location {
city
}
}
`);
```
Here, we only include a `PostDetails` fragment if `$showDetails`
is set, which means, fragments also allow us to alter the query
based on some input variables.
We can use this to slightly alter the result shape based on what
components we know we’ll render, while keeping the query itself
the same.
## Fragment Colocation
All the above examples of how we can use fragments may feel vaguely
familiar to us, even if this is the first time we’re seeing fragments
in action. That might be because fragments are structured very
similarly to how components in componentized apps work.
While querying fields is similar to how we *access* data in front-end
code, and hence map the hierarchy of data we need; Fragments are similar
to how we may structure components.
::: code-group
```tsx twoslash [PokemonTypes.tsx]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { FragmentOf, graphql } from 'gql.tada';
export const pokemonTypesFragment = graphql(`
fragment PokemonTypes on Pokemon @_unmask {
types
}
`);
export const PokemonTypes = (props: {
data: FragmentOf
}) => {
const { data } = props;
return (
Types
{data.types?.map((typing) => - {typing}
)}
);
};
```
:::
With fragments, like our `pokemonTypesFragment` above, we can define the data a
component *requires to render* right next to the component itself, which
keeps concerns on how to fetch this data away from our presentational
components, while still defining what data the component requires.
### Nested Fragment Composition
While colocating fragments is interesting on its own, it really becomes
useful once we define more nested components, and compose their fragments.
Let’s create a `Pokemon` component that renders the `PokemonTypes`
component we’ve already defined above:
::: code-group
```tsx twoslash [Pokemon.tsx]
// @filename: PokemonTypes.tsx
import './graphql/graphql-env.d.ts';
import { FragmentOf, graphql } from 'gql.tada';
export const pokemonTypesFragment = graphql(`
fragment PokemonTypes on Pokemon @_unmask {
types
}
`);
export const PokemonTypes = (props: {
data: FragmentOf
}) => null;
// @filename: Pokemon.tsx
// ---cut---
import { FragmentOf, graphql } from 'gql.tada';
import { pokemonTypesFragment, PokemonTypes } from './PokemonTypes';
export const pokemonFragment = graphql(`
fragment Pokemon on Pokemon @_unmask {
id
name
...PokemonTypes
}
`, [pokemonTypesFragment]);
export const Pokemon = (props: {
data: FragmentOf
}) => {
const { data } = props;
return (
);
};
```
:::
As we can see, defining reusing and composing fragments, is just as easy
as reusing and composing components.
No matter whether where we’re using the `Pokemon` or `PokemonTypes`
components, as long as we compose fragments upwards, we’ll eventually
be able to compose them into a query, at the level of our screen’s code,
and hence combine the data requirements of all of our components.
### Fragment Masking
In the previous examples, you may have noticed the `@_unmask` directive.
In `gql.tada`, a technique called “Fragment Masking” is applied to the
generated types of your fragments, and `@_unmask` disables this for the
purpose of our example code. Fragment Masking hides the types of a
fragment on the fragment’s derived type. This prevents leaking data
when composing fragments.
Let’s consider what happens if the `Pokemon` component started to accidentally
depend on data that only the `PokemonTypes`’s fragment defines.
::: code-group
```tsx twoslash [Pokemon.tsx]
// @filename: PokemonTypes.tsx
import './graphql/graphql-env.d.ts';
import { FragmentOf, graphql } from 'gql.tada';
export const pokemonTypesFragment = graphql(`
fragment PokemonTypes on Pokemon @_unmask {
types
}
`);
export const PokemonTypes = (props: {
data: FragmentOf
}) => null;
// @filename: Pokemon.tsx
import { FragmentOf, graphql } from 'gql.tada';
import { pokemonTypesFragment, PokemonTypes } from './PokemonTypes';
export const pokemonFragment = graphql(`
fragment Pokemon on Pokemon @_unmask {
id
name
...PokemonTypes
}
`, [pokemonTypesFragment]);
// ---cut-before---
export const Pokemon = (props: {
data: FragmentOf
}) => {
const { data } = props;
return (
{data.name}
// @error: Pokemon now accidentally depends on PokemonTypes’s data:
{data.types?.length}
);
};
```
:::
We can fix this by removing `@_unmask` on the `PokemonTypes` component’s fragment
to re-enable fragment masking. This will effectively “hide” the `pokemonTypesFragment`’s data
from the `Pokemon` component to keep the fragments isolated from one another
on a type-level.
::: code-group
```tsx twoslash [PokemonTypes.tsx]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { FragmentOf, graphql, readFragment } from 'gql.tada';
// @annotate: Removing @_unmask isolates this fragment’s data.
export const pokemonTypesFragment = graphql(`
fragment PokemonTypes on Pokemon {
types
}
`);
export const PokemonTypes = (props: {
data: FragmentOf
}) => {
// @annotate: We now have to add readFragment() to unwrap the masked fragment:
const pokemon = readFragment(pokemonTypesFragment, props.data);
return (
Types
{pokemon.types?.map((typing) => - {typing}
)}
);
};
```
:::
Inside the inferred TypeScript types, when fragment masking *isn’t disabled* using
`@_unmask`, then `gql.tada` will infer masked types. In TypeScript, the type that
`FragmentOf<>` returns may look like the following:
```ts
// FragmentType with @_unmask:
type unmaskedPokemonTypes = {
types: ("Bug" | "Dark" | /*...*/ null)[] | null;
};
// FragmentType without @_unmask:
type maskedPokemonTypes = {
[$tada.fragmentRefs]: {
PokemonTypes: 'Pokemon';
};
};
```
The `$tada.fragmentRefs` property above is just a stand-in for the fragment that we've
used in our GraphQL document and all selections inside that fragment are not present
and hidden.
> \[!TIP] Why is this the default behaviour?
> This is the default behaviour in `gql.tada`, and happens unless you add `@_unmask`
> to a fragment. Not only is this a great pattern to prevent mistakes, it also improves
> TypeScript's inference performance!
>
> We recommend you not to disable Fragment Masking unless you absolutely have to,
> to enforce fragment composition safety.
::: details Disabling Fragment Masking globally
While fragment masking is the default, you can also switch it off globally, which
is equivalent to adding `@_unmask` to every fragment.
We don't necessarily recommend starting out with this, since it makes it harder to
switch and migrate to fragment masking incrementally, if you decide to do so in the
future.
However, if this isn't a concern to you, you can pass a `disableMasking` flag
to the `initGraphQLTada` call:
```ts twoslash [src/graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
// ---cut-before---
export const graphql = initGraphQLTada<{
disableMasking: true; // [!code ++]
introspection: introspection;
scalars: {
DateTime: string;
};
}>();
```
:::
### Import Diagnostic
Fragment colocation and masking helps us manage large amounts of GraphQL documents
when creating and composing queries, while keeping data usage isolated and minimal,
right next to our UI components.
However, a common mistake with this method is that sometimes we may forget to import
and use a fragment.
To prevent us from leaving out fragments, the TypeScript plugin has a diagnostic
called `shouldCheckForColocatedFragments`. This diagnostic will issue a warning
if any imports in your documents don't include an exported fragment.
::: code-group
```tsx twoslash {4} [Without importing a fragment]
import './graphql/graphql-env.d.ts';
// ---cut-before---
// @filename: ./src/PokemonsList.tsx
// ---cut---
// @warn: GraphQLSP: Unused co-located fragment definition(s)
import { PokemonItem } from './PokemonItem';
```
```tsx twoslash {4} [With importing a fragment]
import './graphql/graphql-env.d.ts';
// ---cut-before---
// @filename: ./src/PokemonsList.tsx
// ---cut---
import { PokemonItem, PokemonItemFragment } from './PokemonItem';
```
:::
This ties together the last loose end for the fragment colocation and masking
patterns.
---
---
url: /guides/persisted-documents.md
description: How to integrate with and generate persisted documents
---
# Persisted Documents
APIs that support Persisted Documents use identifiers that are sent
to the GraphQL API instead of the complete GraphQL documents.
This requires that the identifiers are embedded in our code and
that the documents are known to our GraphQL API.
::: details What are Persisted Documents?
We call a document a "persisted document" if it has an ID that
identifies it. A GraphQL API that implements persisted documents
will typically accept an ID instead of a full `query` parameter,
containing the full GraphQL document.
When used for CDN caching, a GraphQL API may accept a request with
a document ID as a `GET` HTTP request, making CDN caching trivial,
since it turns GraphQL requests into CDN-cacheable RPC calls.
Some GraphQL API frameworks may implement optimizations for persisted
documents. Since the documents are known ahead of time, they can parse
and validate the document just once. When the API is running and
receives a persisted document ID, it may already assume that the
document is valid.
When used as a security measure, a GraphQL API may reject unknown
queries by checking the ID against a list of allowed documents.
This effectively limits the GraphQL queries your API accepts to
just documents you've written yourself.
:::
::: details How do they differ from Automatic Persisted Queries?
**Persisted Documents** are not the same as **Automatic Persisted Queries**.
Automatic Persisted Queries are a protocol extension for which the ID
for documents are hashed and generated on the client-side, during runtime,
automatically, and registered with the API if it does not recognize an ID.
If your API supports them, you won't need to modify your `gql.tada` code
to make use of this feature. However, you also won't be able to implement
any of the security benefits of Persisted Documents, as documents are
registered dynamically with the API, instead of ahead of time.
:::
***
## Defining Persisted Documents
We may define persisted documents by using the `graphql.persisted()` API.
This call wraps around a GraphQL document and annotates it with a document
ID that we pass to the call.
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
const pokemonsQuery = graphql(`
query PokemonsList($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`);
const persistedQuery = graphql.persisted("POKEMONS_LIST_ID", pokemonsQuery);
```
The replacement document - `persistedQuery` in our example - copies
the type of the document it receives, so type inference will still work as
usual when we use it instead of the original query.
However, the returned document will also carry a `documentId` property with
it, which is set to the ID we passed to `graphql.persisted`.
In this case, it'll be set to `"POKEMONS_LIST_ID"`.
### Compiling away GraphQL documents
When using **Persisted Documents** as a security measure, the API enforces
them and only accepts known document IDs.
You may wish to combine this with a technique to obscure GraphQL documents,
by omitting them from your client-side output bundles entirely. Compiling
GraphQL documents is often done to completely obscure the arguments and
types shape of your GraphQL schema.
::: tip Compatibility with GraphQL clients
Check whether your GraphQL clients supports omitting the original GraphQL
document.
Many GraphQL client caches rely on the original document and its `definitions`
to be available to them, either to provide normalized caching, or to identify
the document uniquely.
:::
This can be achieved by passing the original GraphQL document as a type
to a `graphql.persisted()` call.
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
const pokemonsQuery = graphql(`
query PokemonsList($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`);
const persistedQuery = graphql.persisted(
"POKEMONS_LIST_ID"
);
```
When passing the original document as a generic, the return type of
`graphql.persisted()` remains identical, but the document string
itself will be omitted from your compiled output bundle, provided
the original document - `pokemonsQuery` in our example - isn't
referenced anywhere else in your code.
::: details When and why does this work?
When we refer to the document using `typeof`, this refers to a
value by type instead of by value.
Since a `graphql()` call is side effectless and `typeof` only
refers to it by type, no reference to the original value remain
in TypeScript's transpiled output code.
This lets **tree-shaking and minification** remove the original value,
which effectively removes the original GraphQL document definition
from your compiled output bundle, as long as either of these
mechanisms work properly in your bundles or app framework.
:::
***
### Using generated IDs
Many GraphQL APIs choose to use hashes as GraphQL document IDs, since the
IDs for documents don't necessarily have to be human-readable, and often
need to change when the document changes.
Since it's tedious to manually generate hashes for a GraphQL document and
to keep track of when it changes, the TypeScript plugin has to mechanisms to
help us with hashed document IDs:
* it provides a **code action** that generates a SHA256 hash of your document
* a **diagnostic** warns you if this SHA256 hash needs to be updated
The code action will be reported to your editor once you have defined
a `graphql.persisted()` call. When activated, it will replace the current
document ID passed to the call with a new hash.
In our example above, we'd end up with the following code after:
```ts
const persistedQuery = graphql.persisted(
"sha256:89e47d4f32b4ff76296844ff260d2878bf1829d30706fc7fc92de0fc66c2a4cf",
pokemonsQuery
);
```
## Generating Persisted Manifests
Embedding document IDs with our `gql.tada` documents allows us to send
them to our GraphQL API. However, the other half of making Persisted Documents
work is extracting and registering GraphQL documents from our codebase.
To generate a persisted JSON manifest file, use the `gql.tada` CLI's
`generate persisted` command.
```sh
gql-tada generate persisted --output persisted.json
```
The `generate persisted` command scans your codebase for persisted
GraphQL documents by looking for `graphql.persisted()` calls, and
evaluates and extracts them into a JSON file.
To omit the `--output` argument, you can update your configuration
to change where this persisted manifest file gets written to with
the `tadaPersistedLocation` setting:
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts",
"tadaPersistedLocation": "./persisted.json" // [!code ++]
}
]
}
}
```
:::
### Using the persisted manifest file
The persisted manifest file is a JSON file that contains document
entries. Each entry is keyed by a document ID and has a value of the
GraphQL detected document.
In our example code this would result in a file containing a
`"POKEMONS_LIST_ID"` with our document as a GraphQL document value:
```json
{
"POKEMONS_LIST_ID": "\n query Pokemons ($limit: Int = 10) {\n pokemons(limit: $limit) {\n id\n name\n }\n }\n\n\nfragment PokemonItem on Pokemon {\n id\n name\n}"
}
```
The document string is a combination of the original string
that your `graphql()` call receives with all fragments it
references appended to it.
::: tip Formatting Documents
The persisted manifest file may not contain the documents exactly
how your GraphQL client would format it.
GraphQL clients often format documents to add introspection fields
to them, most commonly adding `__typename` fields to selection sets.
As such, you may want to format and modify the GraphQL document strings
before registering them with your GraphQL API.
:::
***
## Integration with GraphQL Clients
The ["GraphQL over HTTP" specification](https://github.com/graphql/graphql-over-http/blob/persisted-documents/spec/Appendix%20A%20--%20Persisted%20Documents.md)
is looking to standardize how persisted documents are sent to GraphQL APIs via HTTP.
If your GraphQL client supports this specification, you likely won't have to do
anything else to send persisted documents to your API, as long as your API
supports them.
> \[!NOTE]
> "GraphQL over HTTP" is currently a *Stage 2* proposal and is not fully implemented
> by all GraphQL clients and servers yet. The Persisted Documents appendix of the
> specification is an early RFC and not implemented by most servers yet.
***
### `urql` Client
By default, `@urql/core` will omit the `query` property and send a `documentId`
property containing the document ID instead when you're using persisted documents.
If your API supports this request format, there's nothing else you have to do.
#### Formatting Persisted Documents
Before you can register the documents in your persisted manifest file
with your GraphQL API, you should format the documents the same way
`@urql/core` does, if you're using a `cacheExchange`.
```ts twoslash
import { print, parse } from '@0no-co/graphql.web';
import { formatDocument } from '@urql/core';
export function formatClientDocument(document: string) {
return print(formatDocument(parse(document)));
}
```
Before `urql` sends a GraphQL document to your API, it formats
the document to add `__typename` fields to the selection set. Applying
the above transform to your persisted JSON manifest file's documents
ensures that your API will process the same GraphQL operation that
`urql` expects to receive a result for.
#### `@urql/exchange-persisted`
If your API supports the unofficial
[Apollo Automatic Persisted Queries protocol](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine) instead, you'll have to use the `@urql/exchange-persisted`
exchange.
::: details Automatic Persisted Queries protocol
The Automatic Persisted Queries protocol sends omits the `query` property
from requests, and sends the document ID under the
`extensions.persistedQuery.sha256Hash` property.
```json
{
"variables": null,
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "DOCUMENT_ID"
}
}
}
```
:::
First, install the `@urql/exchange-persisted` package:
::: code-group
```sh [npm]
npm install @urql/exchange-persisted
```
```sh [pnpm]
pnpm add @urql/exchange-persisted
```
```sh [yarn]
yarn add @urql/exchange-persisted
```
```sh [bun]
bun add @urql/exchange-persisted
```
:::
You'll then need to add the `persistedExchange` to your exchanges, in front of the `fetchExchange`.
```ts twoslash
import type { TadaPersistedDocumentNode } from 'gql.tada';
import { Client, fetchExchange, cacheExchange } from 'urql';
import { persistedExchange } from '@urql/exchange-persisted';
export const client = new Client({
url: '/graphql',
exchanges: [
cacheExchange,
persistedExchange({
async generateHash(_, document) {
return (document as TadaPersistedDocumentNode).documentId;
},
preferGetForPersistedQueries: true,
enforcePersistedQueries: true,
enableForMutation: true,
enableForSubscriptions: true,
}),
fetchExchange,
],
});
```
When `preferGetForPersistedQueries` is enabled, query operations will be
sent as `GET` HTTP requests instead of `POST` requests, which makes
CDN caching simpler to enable.
***
### Apollo Client
You'll have to use the built-in `createPersistedQueryLink` function
and add the link in front of your HTTP link.
```ts twoslash
import type { TadaPersistedDocumentNode } from 'gql.tada';
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';
const link = createPersistedQueryLink({
generateHash(document) {
return (document as TadaPersistedDocumentNode).documentId;
},
useGETForHashedQueries: true,
}).concat(new HttpLink({ uri: '/graphql' }));
export const client = new ApolloClient({
cache: new InMemoryCache(),
link,
});
```
This will send your persisted documents using the unofficial
[Apollo Automatic Persisted Queries protocol](https://github.com/apollographql/apollo-link-persisted-queries#apollo-engine).
::: details Automatic Persisted Queries protocol
The Automatic Persisted Queries protocol sends omits the `query` property
from requests, and sends the document ID under the
`extensions.persistedQuery.sha256Hash` property.
```json
{
"variables": null,
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "DOCUMENT_ID"
}
}
}
```
:::
When `useGETForHashedQueries` is enabled, query operations will be
sent as `GET` HTTP requests instead of `POST` requests, which makes
CDN caching simpler to enable.
#### Formatting Persisted Documents
Before you can register the documents in your persisted manifest file
with your GraphQL API, you should format the documents the same way
the Apollo Client does.
```ts twoslash
import { print, parse } from 'graphql';
import { addTypenameToDocument } from '@apollo/client/utilities';
export function formatClientDocument(document: string) {
return print(addTypenameToDocument(parse(document)));
}
```
Before Apollo Client sends a GraphQL document to your API, it formats
the document to add `__typename` fields to the selection set. Applying
the above transform to your persisted JSON manifest file's documents
ensures that your API will process the same GraphQL operation that
Apollo Client expects to receive a result for.
---
---
url: /guides/multiple-schemas.md
description: How to set up multiple schemas and GraphQL APIs
---
# Multiple Schemas
When first getting started you're probably setting up `gql.tada`
for a single schema - your own GraphQL API's schema. However,
once you're interacting with another GraphQL API you can set up
`gql.tada` for it as well.
::: details When would you setup multiple schemas?
Interacting with multiple GraphQL APIs isn't uncommon anymore.
* Maybe you have a public GraphQL API and a private, admin GraphQL API?
* Maybe you're in a monorepo and your GraphQL API is calling another
third-party GraphQL API?
While consuming multiple GraphQL APIs on your front-end
simultaneously makes you lose out of a lot of GraphQL's benefits,
there are still many reasons for a your codebase to communicate
with multiple GraphQL APIs.
Whenever you're writing GraphQL documents for different schemas,
you'll probably want to set them up with `gql.tada` to rely
on its types and diagnostics.
:::
***
## 1. Configuring Multiple Schemas
If you've followed the instructions on the [Installation page](/get-started/installation#step-2-—-configuring-a-schema),
your configuration contains only one set of [schema options](/reference/config-format#schema-options)
in the plugin configuration.
To add multiple schemas, you'll have to update your configuration and move
all schema options onto a `schemas[]` array.
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"plugins": [
{ // [!code focus:15]
"name": "gql.tada/ts-plugin",
"schemas": [
{
"name": "pokemon",
"schema": "./graphql/pokemon.graphql",
"tadaOutputLocation": "./src/graphql/pokemon-env.d.ts"
},
{
"name": "simple",
"schema": "./graphql/simple.graphql",
"tadaOutputLocation": "./src/graphql/simple-env.d.ts"
}
]
}
]
}
}
```
:::
All entries in the `schemas[]` list are entirely separate and load their
own GraphQL schemas and have their own file locations.
You'll have to add a unique `name` to each entry. These names are only
used in `gql.tada`'s internal tooling, and in errors and
diagnostics messages, so they're entirely arbitrary.
After you've updated your configuration you can use the `doctor`
command to make sure everything's working properly.
```sh
gql-tada doctor
```
## 2. Initializing `gql.tada` per schema
Once multiple schemas are configured, we cannot import and use the `graphql()`
function from `gql.tada` anymore and have to instead create a `graphql()` function
per schema manually.
This is because the GraphQL schema types are different
for each schema we've set up, and `gql.tada`'s tooling also needs to be able
to identify each schema per document.
::: info Installation Steps
For this section, you'll basically repeating the steps from
[the Installation page's "Intializing `gql.tada` manually" section](/get-started/installation#initializing-gql-tada-manually)
for each schema you're setting up.
:::
To do this, we'll call `initGraphQLTada()` to create a new `graphql()` function
for each schema we're setting up.
:::code-group
```ts twoslash [src/graphql/pokemon.ts] {2}
// @filename: pokemon-env.d.ts
export type introspection = import('./graphql/graphql-env.d.ts').introspection;
// @filename: index.ts
// ---cut---
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './pokemon-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
```
:::
We'll have to repeat this for each schema we're setting up. For our prior example,
we'd be repeating this for our `simple.graphql` schema.
:::code-group
```ts twoslash [src/graphql/simple.ts] {2}
// @filename: simple-env.d.ts
export type introspection = {
name: 'simple';
query: 'Query';
types: {
String: unknown;
Query: {
kind: 'OBJECT';
name: 'Query';
fields: {
helloWorld: {
name: 'helloWorld';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
};
};
};
};
};
// @filename: index.ts
// ---cut---
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './simple-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
```
:::
After setting up each schema, each individual `graphql()` function imported
from these new files will point to a different schema and infer to the
corresponding types.
```ts twoslash [src/graphql/simple.ts]
// @filename: src/graphql/simple-env.d.ts
export type introspection = {
name: 'simple';
query: 'Query';
types: {
String: unknown;
Query: {
kind: 'OBJECT';
name: 'Query';
fields: {
helloWorld: {
name: 'helloWorld';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
};
};
};
};
};
// @filename: src/graphql/simple.ts
// ---cut---
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './simple-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
}>();
// @filename: src/index.ts
// ---cut---
import { graphql } from './graphql/simple';
const query = graphql(`
{ helloWorld }
`);
```
***
### CLI Commands
```sh
gql-tada check
gql-tada generate output
gql-tada generate turbo
gql-tada generate persisted
```
All of the `gql-tada` CLI commands still work the exact same
when multiple schemas are set up.
However, while all `generate` commands accept an `--output` argument when
only one schema is configured, with multiple schemas, you'll have
to configure their output file paths in your schema options instead.
* [`tadaOutputLocation`](/reference/config-format#tadaoutputlocation) for
the [`generate output`](/reference/gql-tada-cli#generate-output) command
* [`tadaTurboLocation`](/reference/config-format#tadaturbolocation) for
the [`generate turbo`](/reference/gql-tada-cli#generate-turbo) command
* [`tadaPersistedLocation`](/reference/config-format#tadapersistedlocation) for
the [`generate persisted`](/reference/gql-tada-cli#generate-persisted) command
::: code-group
```json [tsconfig.json] {10-12}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schemas": [
{
"name": "pokemon",
"schema": "./graphql/pokemon.graphql",
"tadaOutputLocation": "./src/graphql/pokemon-env.d.ts",
"tadaTurboLocation": "./src/graphql/pokemon-cache.d.ts",
"tadaPersistedLocation": "./graphql/pokemon-persisted.json"
},
/*...*/
]
}
]
}
}
```
:::
---
---
url: /guides/testing.md
description: How to write type-safe test fixtures and fake data with fragment masking.
---
# Testing
With [fragment masking](/guides/fragment-colocation#fragment-masking), fragments
are opaque and isolated at the type-level, which enforces that a parent never
sees the data its children selected. This helps in app code, but complicates tests.
Mock data written by hand has the shape of the *unmasked* fragment result, which
won't match the masked types the document expects.
The `gql.tada/testing` entrypoint exports three helpers to bridge this gap. They
are meant for tests, stories, fixtures, and cache updaters.
::: tip
Reach for these only at the boundary where you construct mock data.
Inside the component or function under test, keep using
[`readFragment()`](/reference/gql-tada-api#readfragment)
as you normally would.
:::
***
## Masking a fragment's data
[`maskFragments()`](/reference/gql-tada-api#maskfragments) takes a list of
fragments and the (unmasked) data for them, and returns that data typed as a
masked fragment reference. This is the helper you want when a component under test
accepts a `FragmentOf` prop and you need to hand it a fixture:
```ts twoslash [pokemon.test.ts]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { maskFragments } from 'gql.tada/testing';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
// Ready to pass as a prop:
const data = maskFragments([pokemonItemFragment], {
id: '001',
name: 'Bulbasaur',
});
```
`maskFragments()` also accepts `null`, `undefined`, and arrays of data, so you can
build fixtures for nullable or list-typed fragment props directly.
***
## Building a document's result
[`readResult()`](/reference/gql-tada-api#readresult) builds a type-safe
fixture for a whole document. You pass it the document, the data with fragment
fields inlined, and the list of fragments the document uses. It resolves the
references so your data is fully type checked, including fragments that
themselves spread other fragments.
```ts twoslash [pokemon.test.ts]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { readResult } from 'gql.tada/testing';
const pokemonNameFragment = graphql(`
fragment PokemonName on Pokemon {
name
}
`);
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
...PokemonName
}
`, [pokemonNameFragment]);
const query = graphql(`
query {
pokemon(id: "001") {
...PokemonItem
}
}
`, [pokemonItemFragment]);
// Fully type-checked, including nested fragments:
const data = readResult(
query,
{ pokemon: { id: '001', name: 'Bulbasaur' } },
[pokemonItemFragment, pokemonNameFragment]
);
```
Pass **every** fragment you want inlined, including ones spread transitively by
other fragments. Any fragment you leave out of the list stays masked: instead of
inlined fields, it shows up as a still-masked reference in the expected data.
This is handy when you'd rather build part of the result with
[`maskFragments()`](#masking-a-fragment-s-data). Leave that fragment out, and
slot the masked value in instead, if you already have mocked data for an individual
fragment.
### Casting results unsafely
[`unsafe_readResult()`](/reference/gql-tada-api#unsafe_readresult) casts data to a
document's result type **without type checking** the data nested inside fragment
references. It's a slightly safer alternative to `as any as ResultOf`.
```ts twoslash [pokemon.test.ts]
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { unsafe_readResult } from 'gql.tada/testing';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
const query = graphql(
`
query {
pokemon(id: "001") {
...PokemonItem
}
}
`,
[pokemonItemFragment]
);
// ⚠️ data is cast to the result type WITHOUT checking the fragment fields:
const data = unsafe_readResult(query, {
pokemon: { id: '001', name: 'Bulbasaur' },
});
```
> \[!CAUTION]
> Because `unsafe_readResult()` doesn't check the data inside fragment masks, a
> typo or missing field won't be caught. Prefer
> [`readResult()`](#building-a-document-s-result) whenever you can, and reach for
> this only when listing every fragment is impractical.
---
---
url: /guides/recipebook.md
description: 'A collection of tips, tricks, and patterns for common gql.tada use-cases.'
---
# Recipebook
## Customizing Scalars
By default, `gql.tada` maps the [built-in GraphQL scalars](https://spec.graphql.org/October2021/#sec-Scalars.Built-in-Scalars)
to their TypeScript equivalents, and maps any custom scalars it doesn't recognize
to `unknown`. The `scalars` option on `initGraphQLTada<>()` overrides either of
these.
### Overriding scalar types
The same `scalars` option maps both built-in and custom scalars to whatever
TypeScript type matches what your API serializes.
A common adjustment is the built-in `ID` scalar, which `gql.tada` types as
`string | number`. The [GraphQL specification](https://spec.graphql.org/draft/#sec-ID)
allows an `ID` to be serialized from either, but most APIs only return strings.
Custom scalars like `DateTime` or `JSON` default to `unknown`, since their type
isn't automatically known, and usually we'd want to map these too.
::: code-group
```ts twoslash [src/graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
ID: string; // [!code ++]
DateTime: string; // [!code ++]
JSON: unknown; // [!code ++]
JSONObject: Record; // [!code ++]
};
}>();
// ---cut-after---
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
Since these are only type-level modifications, they don't change any runtime values.
Make sure each matches what your GraphQL API actually returns.
### Opaque (branded) scalar types
Mapping a scalar to a primitive like `string` makes it indistinguishable from any
other string. For scalars that need (de)serialization you could consider using
an **opaque type** (also known as "branded types").
For example, `DateTime` is a common type in GraphQL that usually is serialized
as an ISO string. A branded type lets us annotate these strings with a unique symbol,
which TypeScript treats as distinct from plain `string`s. This allows us to enforce
serialization and deserialization.
::: code-group
```ts twoslash [src/graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
declare const tag: unique symbol;
export type DateTime = string & { readonly [tag]: 'DateTime' };
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
DateTime: DateTime; // [!code ++]
};
}>();
// ---cut-after---
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
The `DateTime` example type above makes this type distinct from just a `string`
which means we can create utilities to deserialize and serialize this scalar.
```ts twoslash
declare const tag: unique symbol;
type DateTime = string & { readonly [tag]: 'DateTime' };
// ---cut-before---
export const fromDateTime = (value: DateTime) => new Date(value);
export const toDateTime = (date: Date) => date.toISOString() as DateTime;
```
This keeps all marshalling logic in a single place and naturally enforces this
as a zero-cost typesystem abstraction, without any additional conversion overhead.
Without branding, a raw `string` may flow straight into `new Date(...)` or can
be missed at various code sites, scattering (and maybe diverging) its parsing
logic across a large codebase. With branding, we enforce that specific utility
functions are used with these scalars. The same can be useful for other unique
types that serialize to `string`s or `number`s, such as `URL`, `UUID`, or
`EmailAddress`.
> \[!TIP]
> The cast inside `toDateTime()` is the one place the brand is asserted. Keeping
> that assertion isolated in a utility is the point. The rest of your code never
> casts.
***
## Working with Enums
By default, `gql.tada` infers GraphQL enums as a union of string literals, e.g.
`'Bug' | 'Dark' | 'Dragon'`. This is the right representation for new code: string
literal unions are forward-compatible and compile away to nothing.
`initGraphQLTada<>()` however allows remapping enum types to another TypeScript type.
This is most useful when migrating an existing codebase onto `gql.tada` — for example
from [GraphQL Code Generator](https://the-guild.dev/graphql/codegen), which by default
may emit a large amount of TypeScript `enum`s.
The `scalars` option can remap enum types by name, so you can slot your existing enum
back in and `gql.tada` will infer the exact type your code expects. This lets you
adopt it without rewriting every reference up front.
::: code-group
```ts twoslash [src/graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
// The enum your existing code already imports:
export enum PokemonType {
Fire = 'Fire',
Water = 'Water',
Grass = 'Grass',
}
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
PokemonType: PokemonType; // [!code ++]
};
}>();
// ---cut-after---
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada';
export { readFragment } from 'gql.tada';
```
:::
Wherever this enum's GraphQL type is selected, `gql.tada` now infers your
`PokemonType` enum instead of the default string literal union, so
existing call sites keep type checking unchanged.
> \[!WARNING]
> Treat this as a migration aid, not a default. TypeScript `enum`s have
> [well-documented drawbacks](https://www.totaltypescript.com/why-i-dont-like-typescript-enums).
> They emit runtime code and are reference-incompatible with their own values, since
> each enum value is an independent symbol in the TypeScript type checker.
> Once a part of your codebase is migrated, drop the override and use the default
> string literal unions, which align with GraphQL's backwards-compatibility
> guarantees.
***
## TypeScript Performance
`gql.tada` infers everything in TypeScript's type system, so type-checking speed
scales with how much GraphQL you write. As a project grows, the editor or `tsc`
can slow down, and very large documents can hit
`Type instantiation is excessively deep and possibly infinite.ts(2589)`.
A few habits keep things fast:
* **Keep `gql.tada` up to date.** Inference performance and document-size limits
improve across releases, and upgrading is the most common fix for `ts(2589)`.
* **Enable Turbo Mode.** [`gql-tada turbo`](/get-started/workflows#turbo-mode)
pre-computes a cache of all document types. Checking it into your repository
lets the plugin and CLI reuse the snapshot instead of re-inferring every type.
* **Use the `.d.ts` output format.**
The [`tadaOutputLocation`](/reference/config-format#tadaoutputlocation) `.d.ts`
format is much more efficient for the type-checker than `.ts`. Only use `.ts` if
another tool needs the introspection data at runtime.
* **Compose with fragments.** Splitting a large query into
[colocated fragments](/guides/fragment-colocation) keeps each selection set
small, which is easier on inference.
---
---
url: /reference/gql-tada-api.md
---
# `gql.tada` API
## Functions
### `graphql()`
| | Description |
| -------------------- | ----------------------------------------------------------------------- |
| `input` argument | A string of a GraphQL document. |
| `fragments` argument | An optional list of other GraphQL fragments created with this function. |
| returns | A GraphQL `DocumentNode` with result and variables types. |
Creates a `DocumentNode` with result and variables types.
You can compose fragments into this function by passing them and a fragment
mask will be created for them.
When creating queries, the returned document of queries can be passed into GraphQL clients
which will then automatically infer the result and variables types.
It is used with your schema in `setupSchema` to create a result type
of your queries, fragments, and variables.
If you instead would like to manually create a `graphql` function with an explicit schema type,
[use `initGraphQLTada` instead.](#initgraphqltada)
#### Example
```ts twoslash
// @filename: graphq-env.d.ts
export type introspection = {
__schema: {
queryType: {
name: 'Query';
};
mutationType: null;
subscriptionType: null;
types: [
{
kind: 'OBJECT';
name: 'Query';
fields: [
{
name: 'hello';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
args: [];
},
{
name: 'world';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
args: [];
},
];
interfaces: [];
},
{
kind: 'SCALAR';
name: 'String';
},
];
directives: [];
};
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection;
}
}
// @filename: index.ts
import './graphql-env.d.ts';
// ---cut---
import { graphql } from 'gql.tada';
const fragment = graphql(`
fragment HelloWorld on Query {
hello
world
}
`);
const query = graphql(
`
query HelloQuery {
hello
...HelloWorld
}
`,
[fragment]
);
```
***
### `graphql.scalar()`
| | Description |
| ---------------- | ---------------------------------------------- |
| `name` argument | A name of a GraphQL scalar or enum. |
| `value` argument | The value to be type-checked against the type. |
| returns | The `value` will be returned directly. |
Type checks a given input value to be of a scalar or enum type and
returns the value directly.
You can use this utility to add a type check for a scalar or enum value,
or to retrieve the type of a scalar or enum.
This is useful if you’re writing a function or component that only accepts
a scalar or enum, but not a full fragment.
> \[!NOTE]
> It’s not recommended to use this utiliy to replace fragments, i.e. to
> create your own object types. Try to use fragments where appropriate
> instead.
#### Example
```ts twoslash
// @filename: graphq-env.d.ts
export type introspection = {
__schema: {
queryType: {
name: 'Query';
};
mutationType: null;
subscriptionType: null;
types: [
{
kind: 'OBJECT';
name: 'Query';
fields: [];
interfaces: [];
},
{
kind: 'ENUM';
name: 'Media';
enumValues: [{ name: 'Book' }, { name: 'Song' }, { name: 'Video' }];
},
];
directives: [];
};
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection;
}
}
// @filename: index.ts
// ---cut-before---
import { graphql } from 'gql.tada';
function validateMediaEnum(value: 'Book' | 'Song' | 'Video') {
const media = graphql.scalar('Media', value);
}
type Media = ReturnType>;
```
***
### `graphql.persisted()`
| | Description |
| ---------------------------- | --------------------------------------------------------------------------- |
| `hash` argument | A hash associated with this query. |
| `document` optional argument | Optionally, the document, if it's supposed to be accessible during runtime. |
Generates a faux-document containing a property called `documentId` which
can be used to send off queries as [Persisted Operations](https://github.com/graphql/graphql-over-http/blob/main/rfcs/PersistedOperations.md).
We must either pass the document as a generic type argument or as the second argument:
* `graphql.persisted("abc...")`
* `graphql.persisted("abc...", document)`
The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check)
run a diagnostic which can check that the document is passed into `graphql.persisted()`
correctly. Furthermore, the TypeScript plugin offers a code action to automatically update
the `hash` argument to a SHA256-hash computed from the document.
This is useful to implement and extract persisted operations using the CLI. Additionally,
when the document is passed as a generic — as long as our GraphQL cache supports this — it
can be fully omitted during runtime from the client-side bundle.
> \[!WARNING] Client Compatibility
> When passing a document by type as a generic to `graphql.persisted("...")`, your runtime code
> won’t see any `definitions` on the AST.
>
> This may cause problems with GraphQL clients (especially normalized caches) that rely on the AST to be available,
> since the full document will be transpile away.
> For such clients, you may want to preserve the document by passing it as a second argument instead.
#### Example
```ts twoslash
// @filename: graphq-env.d.ts
export type introspection = {
__schema: {
queryType: {
name: 'Query';
};
mutationType: null;
subscriptionType: null;
types: [
{
kind: 'OBJECT';
name: 'Query';
fields: [
{
name: 'hello';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
args: [];
},
{
name: 'world';
type: {
kind: 'SCALAR';
name: 'String';
ofType: null;
};
args: [];
},
];
interfaces: [];
},
{
kind: 'SCALAR';
name: 'String';
},
];
directives: [];
};
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection;
}
}
// @filename: index.ts
// ---cut-before---
import { graphql } from 'gql.tada';
const query = graphql(`
query Hello {
hello
}
`);
// You can now use this in your `useQuery` calls.
const persistedOperation = graphql.persisted('sha256:x');
```
***
### `readFragment()`
| | Description |
| ----------------------------- | ------------------------------------------------------------------------ |
| `_document` optional argument | A GraphQL document of a fragment, created using [`graphql()`](#graphql). |
| `fragment` argument | A mask of the fragment, which can be wrapped in arrays, or nullable. |
| returns | The unmasked data of the fragment. |
When [`graphql()`](#graphql) is used to create a fragment and is spread into another
fragment or query, their result types will only contain a “reference” to the
fragment. This encourages isolation and is known as “fragment masking.”
This means that you must use `readFragment()` to unmask these fragment masks
and get to the data. This encourages isolation and only using the data you define
a part of your codebase to require:
```ts
const unmaskedData = readFragment(Fragment, maskedData);
```
When passing `fragment` masks to `readFragment()`, you may also pass nullable, optional data, or data
wrapped in arrays to `readFragment()` and the result type will be unwrapped and inferred accordingly.
Instead of passing the fragment document as the first argument, you may also pass it as a generic,
since it's not used as a runtime value anyway:
```ts
const unmaskedData = readFragment(maskedData);
```
#### Example
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { FragmentOf, ResultOf, graphql, readFragment } from 'gql.tada';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
const getPokemonItem = (data: FragmentOf | null) => {
// @annotate: Unmasks the fragment and casts to the result type:
const pokemon = readFragment(pokemonItemFragment, data);
};
const pokemonQuery = graphql(
`
query Pokemon($id: ID!) {
pokemon(id: $id) {
id
...PokemonItem
}
}
`,
[pokemonItemFragment]
);
const getQuery = (data: ResultOf) => {
getPokemonItem(data.pokemon);
};
```
***
### `initGraphQLTada()`
| | Description |
| --------------- | --------------------------------------------------------------------- |
| `Setup` generic | An [`AbstractSetupSchema` configuration object](#abstractsetupschema) |
| returns | A typed [`graphql()`](#graphql) function. |
`initGraphQLTada` accepts an [`AbstractSetupSchema` configuration object](#abstractsetupschema) as a generic
and returns [a `graphql()` function](#graphql) that may be used to create documents typed using your
GraphQL schema.
You should use and re-export the resulting function named as `graphql` or `gql` for your
editor and the TypeScript language server to recognize your GraphQL documents correctly.
#### Example
```ts twoslash
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql/graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
DateTime: string;
Json: any;
};
}>();
const query = graphql(`
{
__typename
}
`);
```
## Types
### `ResultOf`
| | Description |
| ------------------ | --------------------------------------------------------------- |
| `Document` generic | The document type of a `DocumentNode` carrying the result type. |
This accepts a [`TadaDocumentNode`](#tadadocumentnode) and returns the attached `Result` type
of GraphQL documents.
***
### `VariablesOf`
| | Description |
| ------------------ | ------------------------------------------------------------------ |
| `Document` generic | The document type of a `DocumentNode` carrying the variables type. |
This accepts a [`TadaDocumentNode`](#tadadocumentnode) and returns the attached `Variables` type
of GraphQL documents.
***
### `FragmentOf`
| | Description |
| ------------------ | -------------------------------------------------- |
| `Document` generic | A `DocumentNode` containing a fragment definition. |
Creates a fragment mask for a given fragment document.
When [`graphql()`](#graphql) is used to create a fragment and is spread into another
fragment or query, their result types will only contain a “reference” to the
fragment. This encourages isolation and is known as “fragment masking.”
While [`readFragment()`](#readfragment) is used to unmask these fragment masks, this utility
creates a fragment mask, so you can accept the masked data in the part of your
codebase that defines a fragment.
#### Example
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { FragmentOf, graphql, readFragment } from 'gql.tada';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
// May be called with any data that contains the mask
const getPokemonItem = (data: FragmentOf) => {
// Unmasks the fragment and casts to the result type
const pokemon = readFragment(pokemonItemFragment, data);
};
```
***
### `TadaDocumentNode`
| | Description |
| ------------------- | --------------------------------------------------------------------------- |
| `Result` generic | The type of GraphQL results, as returned by GraphQL APIs for a given query. |
| `Variables` generic | The type of variables, as accepted by GraphQL APIs for a given query. |
A GraphQL `DocumentNode` with attached types for results and variables.
This is a GraphQL `DocumentNode` with attached types for results and variables.
This is used by GraphQL clients to infer the types of results and variables and provide
type-safety in GraphQL documents.
You can create typed GraphQL documents using the [`graphql()` function.](#graphql)
***
### `setupSchema`
You may extend this interface via declaration merging with your `IntrospectionQuery`
data and optionally your scalars to get proper type inference.
This is done by declaring a declaration for it as per the following example.
Configuring scalars is optional and by default the standard scalrs are already
defined.
This will configure the default `graphql()` export to infer types from your schema.
Alternatively, if you don’t want to define your schema project-wide,
you may call [`initGraphQLTada()`](#initgraphqltada) instead.
[Read more about setting up your schema on the “Installation” page.](../get-started/installation#step-3-configuring-typings)
#### Example
```ts twoslash
import type { introspection } from './graphql/graphql-env.d.ts';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection;
scalars: {
DateTime: string;
Json: any;
};
}
}
```
***
### `AbstractSetupSchema`
| | Description |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| `introspection` property | Introspection of your schema in the `IntrospectionQuery` format. |
| `scalars` property | An optional object type with scalar names as keys and the corresponding scalar types as values. |
| `disableMasking` flag | This may be set to `true` to disable fragment masking globally. |
This is used either via [`setupSchema`](#setupschema) or [`initGraphQLTada()`](#initgraphqltada) to set
up your schema and scalars. Your configuration objects must match the shape of this type.
The `scalars` option is optional and can be used to set up custom scalar and enum types.
It must be an object map of scalar names to their desired TypeScript types.
When a scalar or enum is missing in your custom `scalars` object, a fallback will be
used for the built-in scalars (`Int`, `Float`, `String`, `Boolean`, and `ID`) and for
enums, the `enumValues` defined by the schema will be used.
The `disableMasking` flag may be set to `true` instead of using `@_unmask` on individual fragments
and allows fragment masking to be disabled globally.
## Testing Functions
These functions are all exported from `gql.tada/testing`, and are meant for tests,
stories, fixtures, or cache updaters, rather than your regular app code.
### `maskFragments()`
| | Description |
| --------------------- | -------------------------------------------------------------------------------- |
| `_fragments` argument | A list of GraphQL documents of fragments, created using [`graphql()`](#graphql). |
| `data` argument | The combined result data of the fragments, which can be wrapped in arrays. |
| returns | The masked data of the fragments. |
> \[!NOTE]
> While useful, `maskFragments()` is mostly meant to be used in tests or as
> an escape hatch to convert data to masked fragments.
>
> You shouldn’t have to use it in your regular component code.
When [`graphql()`](#graphql) is used to compose fragments into another fragment or
operation, the resulting type will by default be masked, [unless the `@_unmask`
directive is used.](../guides/fragment-colocation#fragment-masking)
This means that when we’re writing tests or are creating “fake data” without
inferring types from a full document, the types in TypeScript may not match,
since our testing data will not be masked and will be equal to [the result type](#resultof)
of the fragments.
To address this, the `maskFragments` utility takes a list of fragments and masks data (or an array of data)
to match the masked fragment types of the fragments.
* [Read more about fragment masking on the “Writing GraphQL” page.](../get-started/writing-graphql#fragment-masking)
* [For the reverse operation, see `readFragment()`.](#readfragment)
#### Example
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql, maskFragments } from 'gql.tada';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
const data = maskFragments([pokemonItemFragment], {
id: '001',
name: 'Bulbasaur',
});
```
***
### `unsafe_readResult()`
| | Description |
| -------------------- | -------------------------------------------------------------------- |
| `_document` argument | A GraphQL document, created using [`graphql()`](#graphql). |
| `data` argument | The result data of the GraphQL document with optional fragment refs. |
| returns | The masked result data of the document. |
> \[!CAUTION]
> Unlike, [`maskFragments()`](#maskfragments), this utility is unsafe, and
> should only be used when you know that data matches the expected shape
> of a GraphQL query you created.
>
> While useful, this utility is only a slightly safer alternative to `as any`
> and doesn’t type check the result shape against the masked fragments in your
> document.
>
> You shouldn’t have to use it in your regular app code.
When [`graphql()`](#graphql) is used to compose fragments into a document,
the resulting type will by default be masked, [unless the `@_unmask`
directive is used.](../guides/fragment-colocation#fragment-masking)
This means that when we’re writing tests and are creating “fake data”,
for instance for a query, that we cannot convert this data to the query’s
result type, if it contains masked fragment refs.
To address this, the `unsafe_readResult` utility accepts the document and
converts a query’s data to masked data.
#### Example
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql, unsafe_readResult } from 'gql.tada';
const pokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
}
`);
const query = graphql(
`
query {
pokemon(id: "001") {
...PokemonItem
}
}
`,
[pokemonItemFragment]
);
// @warn: data will be cast (unsafely!) to the result type
const data = unsafe_readResult(query, {
pokemon: { id: '001', name: 'Bulbasaur' },
});
```
***
### `readResult()`
| | Description |
| -------------------- | ------------------------------------------------------------------ |
| `document` argument | A GraphQL document, created using [`graphql()`](#graphql). |
| `data` argument | The result data of the document, with fragment data inlined. |
| `fragments` argument | A list of every fragment used in the document, transitively. |
| returns | The result data, typed as the document’s [result type](#resultof). |
When [`graphql()`](#graphql) composes fragments into a document, the result type
only contains opaque references to those fragments, rather than the fragments’
fields. This makes it hard to assemble a full result as “fake data”.
Unlike [`unsafe_readResult()`](#unsafe_readresult), which discards all fragment
references and doesn’t type check the data nested inside them, `readResult()` is
type-safe. You pass it the fragments used in the document, and it recursively
resolves their references, so the data you write is fully type checked — including
data for nested fragments and fragments that themselves spread other fragments.
Pass every fragment you’d like to inline — including fragments spread by other
fragments — to the `fragments` argument. Any fragment you leave out stays masked
in the expected data, merged into its surrounding object as a fragment reference.
This keeps the result’s shape intact and makes it easy to spot which fragments
are still missing, and lets you fill them with [`maskFragments()`](#maskfragments)
instead.
* [For masking a single level of fragment data, see `maskFragments()`.](#maskfragments)
* [For the unsafe variant that performs no checks, see `unsafe_readResult()`.](#unsafe_readresult)
#### Example
```ts twoslash
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { graphql } from 'gql.tada';
import { readResult } from 'gql.tada/testing';
const pokemonNameFragment = graphql(`
fragment PokemonName on Pokemon {
name
}
`);
const pokemonItemFragment = graphql(
`
fragment PokemonItem on Pokemon {
id
...PokemonName
}
`,
[pokemonNameFragment]
);
const query = graphql(
`
query {
pokemon(id: "001") {
...PokemonItem
}
}
`,
[pokemonItemFragment]
);
// @log: data is fully type-checked, including the nested fragment fields
const data = readResult(
query,
{ pokemon: { id: '001', name: 'Bulbasaur' } },
[pokemonItemFragment, pokemonNameFragment]
);
```
---
---
url: /reference/gql-tada-cli.md
---
# `gql-tada` CLI
## Commands
### `init`
> \[!NOTE]
>
> The `gql-tada init` command is still a work in progress.
> If you run into any trouble, feel free to let us know what you’d like to see added or changed.
| Option | Description |
| ------ | ------------------------------------------------------------------------------------------------ |
| `dir` | A relative location from your current working directory where the project should be initialized. |
The `init` command takes care of everything required to setup a `gql-tada`
project. The main tasks involved here are:
* Locating the schema
* Locating where `gql.tada`’s `graphql-env.d.ts` shall be placed
* Configuring the `tsconfig.json`
* Installing required dependencies
You can run this command with your preferred package manager:
::: code-group
```sh [npm]
npx gql-tada init ./my-project
```
```sh [pnpm]
pnpx gql-tada init ./my-project
```
```sh [bun]
bunx gql-tada init ./my-project
```
:::
### `doctor`
> \[!NOTE]
>
> The `gql-tada doctor` command is still a work in progress.
> If you run into any trouble, feel free to let us know what you’d like to see added or changed.
The `doctor` command will check for common mistakes in the `gql-tada`’s setup and configuration. It will check installed versions of packages, check the configuration, and check the schema.
### `check`
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported (default: `false`). |
| `--level,-l` | The minimum severity of diagnostics to display: `info`, `warn` or `error` (default: `info`). |
Usually, the TypeScript plugin will run inside your editor's TypeScript language server process and will report warnings
and errors. However, these diagnostics aren't run when `tsc` or other TypeScript compiler processes are used, since those
neither load plugins nor are part of the TypeScript language service API.
The `gql-tada check` command exists to run these diagnostics in a standalone command, outside of editing the relevant
files and reports these errors to the console.
When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI.
### `generate-schema`
| Option | Description |
| --------------- | ---------------------------------------------------------------------------------------------------- |
| `schema` | URL to a GraphQL API or a path to a `.graphql` SDL file or introspection JSON. |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--output,-o` | An output location to write the `.graphql` SDL file to. (Default: The `schema` configuration option) |
| `--header,` | A `key:value` header entry to use when retrieving the introspection from a GraphQL API. |
Oftentimes, an API may not be running in development, is maintained in a separate repository, or requires authorization headers, and specifying a URL in the `schema` configuration can slow down development.
The `gql-tada generate-schema` command introspects a targeted GraphQL API by URL, a `.graphql` SDL
or introspection JSON file, and outputs a `.graphql` SDL file. Generating a `.graphql` SDL file is
useful if we're trying to avoid adding a URL as the `schema` configuration option.
The SDL file will be written to the location specified by the `schema` configuration option,
which can be overridden using the `--output` argument.
### `generate-output`
| Option | Description |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| `--disable-preprocessing` | Whether to use the less efficient `.d.ts` introspection format. (Default: false) |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--output,-o` | Specify where to output the file to. (Default: The `tadaOutputLocation` configuration option) |
The `gql-tada generate-output` command programmatically outputs the `gql.tada` output typings file.
It will load the schema from the specified `schema` configuration option first then write the typings file to the specified
location.
The output file will be written to the location specified by the `tadaOutputLocation` configuration
option, which can be overridden using the `--output` argument.
### `turbo`
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------------- |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. |
| `--output,-o` | Specify where to output the file to. (Default: The `tadaTurboLocation` configuration option) |
The `turbo` command generates a cache for all GraphQL document types ahead of time.
This cache speeds up type evaluation and is especially useful when it's checked into the
repository after making changes to GraphQL documents, which speeds up all further type
checks and evaluation.
The cache is a snapshot of all current `gql.tada` types. As you edit GraphQL documents,
`gql.tada` will still infer types dynamically until a new cache file is generated.
The cache file will be written to the location specified by the `tadaTurboLocation` configuration
option, which can be overridden using the `--output` argument.
When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI.
### `generate-persisted`
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------ |
| `--disable-normalization` | Whether to disable normalizing the GraphQL document. (Default: false) |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. |
| `--output,-o` | Specify where to output the file to. (Default: The `tadaPersistedLocation` configuration option) |
The `gql-tada generate-persisted` command will scan your code for `graphql.persisted()` calls and generate
a JSON manifest file containing a mapping of document IDs to the GraphQL document strings.
These can then be used to register known and accepted documents (known as “persisted operations”) with your GraphQL API to lock down accepted documents that are allowed to be sent.
The manifest file will be written to the location specified by the `tadaPersistedLocation` configuration
option, which can be overridden using the `--output` argument.
When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate errors within the GitHub UI.
### `scan`
> \[!NOTE]
>
> The `gql-tada scan` command is experimental, and its rules and output may still change.
> If you run into any trouble or have ideas for insights you’d like to see, feel free to let us know.
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------- |
| `--tsconfig,-c` | Optionally, a `tsconfig.json` file to use instead of an automatically discovered one. |
| `--format,-f` | Emit the machine-readable `json` report instead of the terminal report. |
| `--graph` | Emit only the relationship graph as JSON. Implies machine output. |
| `--output,-o` | Specify where to write machine output to. (Default: standard output) |
| `--fail-on-warn,-w` | Triggers an error and a non-zero exit code if any warnings have been reported. |
The `gql-tada scan` command analyzes all GraphQL documents and fragments across your project and
keys every field selection back to the schema, producing project-level insights into how your
schema is used.
By default, it prints a human-readable report to the terminal. Each insight is produced by a rule,
covering field usage and reach, input (enum value and input-object field) usage, deprecated-field
usage, orphan and cross-feature fragments, operation complexity, fetch depth, and directive usage.
Passing `--format json` writes a machine-readable report — a project overview, operation and
fragment identities, and every rule’s datapoints — to standard output, or to the file given by
`--output`. Passing `--graph` instead emits the module ↔ document ↔ fragment ↔ schema relationship
graph on its own, for feeding into other tooling or visualizations.
When this command is run inside a GitHub Action, [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) are used to annotate warnings within the GitHub UI.
## Functions
The CLI is packaged as a module that `gql.tada` depends on published as `@gql.tada/cli-utils`.
If you're looking to generate the file that the CLI generates in your own scripts, you can
use the functions it exports directly.
### `generateOutput()`
| | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `output` option | The filename to write the output file to (Default: the `tadaOutputLocation` configuration option) |
| `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. |
| `disablePreprocessing` option | Whether to disable the optimized output format for `.d.ts` files. |
| `silent` option | Whether to disable terminal output when using the programmatic API. |
| returns | A `Promise` that resolves when the task completes. |
The `generateOutput()` function outputs the `gql.tada` output file manually. It will load the schema from the specified `schema` configuration option and write the output file.
The output file will be written to the location specified by the `tadaOutputLocation` configuration
option, which can be overridden using the `output` option.
```ts twoslash
import { generateOutput } from '@gql.tada/cli-utils';
await generateOutput({
output: './src/graphql-env.d.ts',
disablePreprocessing: false,
tsconfig: undefined,
silent: false,
});
```
***
### `generatePersisted()`
| | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `disableNormalization` | Disables normalizing the GraphQL document |
| `output` option | The filename to write the persisted JSON manifest file to (Default: the `tadaPersistedLocation` configuration option) |
| `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. |
| `failOnWarn` option | Whether to throw an error instead of logging warnings. |
| returns | A `Promise` that resolves when the task completes. |
The `generatePersisted()` function will scan your code for `graphql.persisted()` calls and generate
a JSON manifest file containing a mapping of document IDs to the GraphQL document strings.
These can then be used to register known and accepted documents (known as “persisted operations”) with your GraphQL API to lock down accepted documents that are allowed to be sent.
The manifest file will be written to the location specified by the `tadaPersistedLocation` configuration
option, which can be overridden using the `output` option.
```ts twoslash
import { generatePersisted } from '@gql.tada/cli-utils';
await generatePersisted({
output: './persisted.json',
failOnWarn: false,
tsconfig: undefined,
});
```
***
### `generateSchema()`
| | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| `input` option | The filename to a `.graphql` SDL file, introspection JSON, or URL to a GraphQL API to introspect. |
| `headers` option | Optionally, an object of headers to send when introspecting a GraphQL API. |
| `output` option | The filename to write the persisted JSON manifest file to (Default: the `schema` configuration option) |
| `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. |
| `silent` option | Whether to disable terminal output when using the programmatic API. |
| returns | A `Promise` that resolves when the task completes. |
The `generateSchema()` function introspects a targeted GraphQL API by URL, a `.graphql` SDL
or introspection JSON file, and outputs a `.graphql` SDL file. Generating a `.graphql` SDL file is
useful if we're trying to avoid adding a URL as the `schema` configuration option.
The SDL file will be written to the location specified by the `schema` configuration option,
which can be overridden using the `output` option.
```ts twoslash
import { generateSchema } from '@gql.tada/cli-utils';
await generateSchema({
input: 'https://trygql.formidable.dev/graphql/basic-pokedex',
output: './schema.graphql',
headers: undefined,
tsconfig: undefined,
silent: false,
});
```
***
### `generateTurbo()`
| | Description |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| `output` option | The filename to write the cache file to (Default: the `tadaTurboLocation` configuration option) |
| `tsconfig` option | The `tsconfig.json` to use instead of an automatically discovered one. |
| `failOnWarn` option | Whether to throw an error instead of logging warnings. |
| returns | A `Promise` that resolves when the task completes. |
The `generateTurbo()` function generates a cache for all GraphQL document types ahead of time.
This cache speeds up type evaluation and is especially useful when it's checked into the
repository after making changes to GraphQL documents, which speeds up all further type
checks and evaluation.
The cache is a snapshot of all current `gql.tada` types. As you edit GraphQL documents,
`gql.tada` will still infer types dynamically until a new cache file is generated.
The cache file will be written to the location specified by the `tadaTurboLocation` configuration
option, which can be overridden using the `output` option.
```ts twoslash
import { generateTurbo } from '@gql.tada/cli-utils';
await generateTurbo({
output: './src/graphql-cache.d.ts',
failOnWarn: false,
tsconfig: undefined,
});
```
---
---
url: /reference/config-format.md
---
# Configuration Format
Both `gql.tada`'s [CLI](/reference/gql-tada-cli) as well as the TypeScript
plugin are configured using an entry in your `tsconfig.json` file.
Their configurations are part of the TypeScript plugin entry:
::: code-group
```json [tsconfig.json] {4-10}
{
"compilerOptions": {
"strict": true,
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts"
}
]
}
}
```
:::
The section marked containing `schema` is what you can populate with [**Schema Options**](#schema-options),
as described in the next section. This will set up a default schema for `gql.tada`
to use and the only required options are [`schema`](#schema) and [`tadaOutputLocation`](#tadaoutputlocation).
If you have multiple schemas you'd like to use with `gql.tada`, then you'll instead want
to create a `schemas` array.
::: code-group
```json [tsconfig.json] {7-12}
{
"compilerOptions": {
"strict": true,
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schemas": [
{
"name": "your-schema-1",
"schema": "./schema-1.graphql",
"tadaOutputLocation": "./src/graphql-env-1.d.ts"
},
{
"name": "your-schema-2",
"schema": "./schema-2.graphql",
"tadaOutputLocation": "./src/graphql-env-2.d.ts"
}
]
}
]
}
}
```
:::
The `name` property in each `schemas[]` entry is arbitrary. It's important that you give each of your
schemas a name here, but this can be any name you want and is only used to identify the schema internally
and to you in error messages.
::: info Optional Schema Options
Don't worry about setting up more than the required `schema` and `tadaOutputLocation` configuration option.
All optional schema options are mostly used by the `gql.tada` CLI, which will tell you if you're missing
any of the extra configuration options. Additionally, to validate your configuration, you can always
run the [`gql-tada doctor` CLI command](/reference/gql-tada-cli#doctor).
:::
## Schema Options
This section documents all of the schema-specific configuration options.
These options are specific to a single schema and are added either under
the main plugin config or inside the `schemas[]` array items.
### `schema`
The `schema` option specifies how to load your GraphQL schema and currently allows
for three different schema formats. It accepts either:
* a path to a `.graphql` file containing a schema definition (in GraphQL SDL format)
* a path to a `.json` file containing a schema’s introspection query data
* a URL to a GraphQL API that can be introspected
::: code-group
```json [.graphql file] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql"
}
]
}
}
```
```json [.json file] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./introspection.json"
}
]
}
}
```
```json [URL] {6}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "http://localhost:4321/graphql"
}
]
}
}
```
```json [URL with headers] {6-11}
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": {
"url": "http://localhost:4321/graphql",
"headers": {
"Accept": "application/graphql-response+json"
}
}
}
]
}
}
```
:::
Since this option defines which GraphQL schema is used, it's required
and both the CLI and the TypeScript plugin will not function without it.
***
### `tadaOutputLocation`
The `tadaOutputLocation` specifies the output path to write a typings
output file to, which `gql.tada` uses to infer GraphQL types within
the TypeScript type system.
The `tadaOutputLocation` option supports two different formats dependent on the
file path you pass: the `.d.ts` format, and the `.ts` format.
Depending on the file path's extension, either of these output
formats are used.
When the option only specifies a directory, a `introspection.d.ts` file
will automatically be written to the output directory.
#### Format 1 — `.d.ts` file
The `.d.ts` output format is only a declaration file, which will also contain a
declaration that automatically declares [a `setupSchema` interface on `gql.tada`](./gql-tada-api#setupschema).
When this format is used, [declaration merging in TypeScript](https://www.typescriptlang.org/docs/handbook/declaration-merging.html),
kicks in, which means that - without any additional configuration - we can then start
importing `graphql()` from `gql.tada` and use it.
The resulting file will have the following shape:
::: code-group
```ts [graphql-env.d.ts]
export type introspection = {
__schema: { /*...*/ };
};
import * as gqlTada from 'gql.tada';
declare module 'gql.tada' {
interface setupSchema {
introspection: introspection;
}
}
```
:::
If we want to now customize `gql.tada`, for instance to set up our scalar types, we’ll need to
create our own `graphql()` function by importing the output typings manually and passing it
to [`gql.tada`’s `initGraphQLTada<>()` function](./gql-tada-api#initgraphqltada):
::: code-group
```ts [graphql.ts]
import { initGraphQLTada } from 'gql.tada';
import type { introspection } from './graphql-env.d.ts';
export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
DateTime: string;
JSON: any;
};
}>();
```
:::
Since this is just a declaration file, the easiest way to indicate this in our code is to
use a `import type` statement and to refer to the file using its full file extension.
#### Format 2 — `.ts` file
> \[!WARNING] A note on performance
>
> We strongly recommend you to use the `.d.ts` format instead. While it's less reusable, the format will
> be more efficient and increase TypeScript inference performance.
When writing a `.ts` file instead, a regular TypeScript file will be created exporting
an `introspection` object. This format is supported because, while this object may cause
a large increase in bundlesize, occasionally other tools may also depend on the raw GraphQL
introspection output during runtime.
The resulting file will have the following shape:
::: code-group
```ts [introspection.ts]
const introspection = {
__schema: { /*...*/ },
} as const;
export { introspection };
```
:::
Because this file doesn't include a `declare module` typings directive, with this format
we're always required to set up `gql.tada` manually using the
[`initGraphQLTada<>()` function](./gql-tada-api#initgraphqltada).
***
### `tadaTurboLocation`
The `tadaOutputLocation` specifies the output path that the
[`gql-tada turbo`](/reference/gql-tada-cli#turbo) command will write
the type cache output file to.
Type cache files are `.d.ts` files that cache `gql.tada`'s inferred types
This means that when you run `gql-tada turbo` after making your changes,
TypeScript will be able to start up and type check your GraphQL documents
much more quickly than without the type cache.
***
### `tadaPersistedLocation`
The `tadaPersistedLocation` specifies the output path that the
[`gql-tada generate persisted` command](/reference/gql-tada-cli#generate-persisted)
will write the persisted JSON manifest file to.
Persisted manifest files are `.json` files that contain all GraphQL
documents referenced using a [`graphql.persisted` call](/reference/gql-tada-api#graphql-persisted).
This is useful to implement persisted operations, as all documents will
be extracted into the manifest file at compile-time.
***
## Global Options
This section documents all of the plugin-wide configuration options.
These options aren't specific to a single schema and configure both
global features for the `gql.tada` CLI and the TypeScript plugin.
### `trackFieldUsage`
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts",
"trackFieldUsage": true // [!code ++]
}
]
}
}
```
:::
By default, this option is enabled. When enabled, your usage of
fields will be tracked as you consume data typed using a GraphQL document.
The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check)
will run a diagnostic that issues warnings when any fields in your selection
sets aren't used in your TypeScript code.
```tsx twoslash {8}
import './graphql/graphql-env.d.ts';
// ---cut-before---
import { FragmentOf, graphql, readFragment } from 'gql.tada';
// @warn: GraphQLSP: Field 'maxHP is not used.
export const PokemonItemFragment = graphql(`
fragment PokemonItem on Pokemon {
id
name
maxHP
}
`);
interface Props {
data: FragmentOf;
}
export const PokemonItem = ({ data }: Props) => {
const pokemon = readFragment(PokemonItemFragment, data);
return ;
};
```
In the above example, we add a `maxHP` field to a fragment that the component’s
code does not actually access, which causes a warning to be displayed.
::: info When should `trackFieldUsage` be disabled?
Usage of any fields is based on heuristics. As such, depending on your coding
patterns this warning can sometimes be triggered erroneously and support of
more coding patterns is still being expanded.
If you see any false-positive warnings, feel free to disable `trackFieldUsage`
or report the problematic code pattern to us in an issue.
:::
***
### `shouldCheckForColocatedFragments`
::: code-group
```json [tsconfig.json]
{
"compilerOptions": {
"plugins": [
{
"name": "gql.tada/ts-plugin",
"schema": "./schema.graphql",
"tadaOutputLocation": "./src/graphql-env.d.ts",
"shouldCheckForColocatedFragments": true // [!code ++]
}
]
}
}
```
:::
By default, this option is enabled. When enabled, your imports will be scanned
for exported fragments.
The TypeScript plugin and the [`gql-tada check` command](/reference/gql-tada-cli#check)
will issue warnings when you're missing imports to a GraphQL fragment exported by
will run a diagnostic that issues warnings when any imports statements don't import
a GraphQL fragment exported by another module.
This is important to help with [fragment co-location](/guides/fragment-colocation)
as many component modules may export fragments that you should be importing and
use in the importer's GraphQL documents.
```tsx twoslash {4}
import './graphql/graphql-env.d.ts';
// ---cut-before---
// @filename: ./src/PokemonItem.tsx
export const PokemonItem = () => null;
// @filename: ./src/PokemonsList.tsx
// ---cut---
import { useQuery } from 'urql';
import { graphql } from 'gql.tada';
// @warn: GraphQLSP: Unused co-located fragment definition(s)
import { PokemonItem } from './PokemonItem';
const PokemonsQuery = graphql(`
query Pokemons($limit: Int = 10) {
pokemons(limit: $limit) {
id
name
}
}
`, []);
export const PokemonList = () => {
const [result] = useQuery({ query: PokemonsQuery });
return null; // ...
};
```
In the above example, we add an import to a `PokemonItem` component.
If the file contains a fragment that we have to use in our query a warning is displayed.
::: info When should `shouldCheckForColocatedFragments` be disabled?
This warning is context-sensitive. It can be very helpful when you're
following our recommended [fragment co-location patterns](/guides/fragment-colocation).
However, if you're not using co-located fragments, or if you have many "mixed"
files that contain both components with fragments and other code, this warning
can get confusing and annoying.
We recommend you to disable this check if you know that you're not
going to follow [fragment co-location](/guides/fragment-colocation).
:::