# genql: a GraphQL client generated from your schema

> genql reads a GraphQL schema at build time and generates a TypeScript client whose query shape is checked by the compiler, so a schema change breaks the build instead of the request. It ships no runtime GraphQL library, and the generated client can be driven through any transport you prefer.

**remorses/genql** — Type safe TypeScript client for any GraphQL API

- Repository: https://github.com/remorses/genql
- Website: https://genql.dev
- Stars: 980 · Forks: 44
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/remorses-genql

## The schema is the source of truth, the query is a function call

Writing GraphQL queries by hand means two representations of the same thing: a string of query text and a TypeScript type describing its result. Those two drift, and the compiler cannot see the drift because a string is just a string.

genql inverts that. You generate a client from your schema, and the generated code exposes the query as a typed object. Selecting fields becomes assigning them, and passing arguments becomes passing a typed structure. A field that does not exist in the schema is a compile error rather than a runtime failure.

The project's own list of reasons for doing this is worth reading closely. Queries are written faster because of auto completion. You can update your schema and stay confident the queries are still valid. A type can be asked for all of its scalar fields with a single flag instead of enumerating them. There is no graphql package dependency and no runtime parsing of queries. And you only have to regenerate the client after the schema changes, not after every query change, which is the detail that makes the approach bearable in a busy repository.

The client is generated by a CLI into a directory you choose, and the generated code imports from that directory, so the client is an ordinary module in your project rather than a runtime abstraction.

## Generating the client takes one install and one command

The quick start in the documentation is two steps. The first installs the CLI as a development dependency:

```sh
npm i -D @genql/cli # cli to generate the client code
```

The second runs it against your schema and writes the generated client to a folder:

```sh
genql --schema ./schema.graphql --output ./generated
```

The schema path and the output path are the two things you must supply. The schema file here is a local GraphQL SDL file, and the output folder receives the generated client. The documentation notes that other command line options exist and points to a CLI reference page, so the flag set is larger than this one example makes visible.

Once generated, the client is used as an ordinary module. The example imports the factory and a helper from the generated directory:

```js
import { createClient, everything } from './generated'
const client = createClient()
```

The import path matters for how you structure the repository. Because the generated code is imported by relative path, the output directory has to exist in the tree where your application expects it, and it has to be regenerated and committed whenever the schema moves under it.

The runtime requirements are light. The project root package.json declares node >=18.0.0 as the supported engine, and the workspace is managed with pnpm, with lerna used for publishing and changesets used for version bumps.

## Query fields as object keys, arguments under __args

The generated API is object shaped. A query call names the root field, and the fields you want become keys whose values are true:

```js
client
    .query({
        countries: {
            // pass arguments to the query
            __args: {
                filter: {
                    currency: {
                        eq: 'EUR',
                    },
                },
            },
            name: true,
            code: true,
            nestedField: {
                // fetch all scalar fields
                __scalar: true,
            },
        },
    })
    .then(console.log)
```

Two conventions carry the weight. The __args key holds the arguments for the field, and its shape mirrors the argument types in the schema, including nested input objects such as a filter. The __scalar flag is the shortcut for fetching all scalar fields of a type, which removes the maintenance problem of adding a new scalar to a type and forgetting to add it to every query.

That call produces a GraphQL query equivalent to this:

```graphql
query {
    countries(filter: { currency: { eq: "EUR" } }) {
        name
        code
        nestedField {
            scalarField1
            scalarField2
        }
    }
}
```

The comparison makes the trade-off visible. The generated query is ordinary GraphQL text, so anything that can send GraphQL can send it. What you gain is that the object form is checked by TypeScript, and what you give up is that you can no longer write arbitrary query syntax inline.

## No graphql package, and it runs on more than Node

The dependency claim is specific and unusual. The feature list states no dependencies, not even graphql, and the reasons section repeats it as no graphql package dependency and no runtime parsing of queries. The client produces a query document rather than an executable AST, which is why nothing from the graphql package is needed at runtime.

That has two consequences. The first is a smaller install and no version conflict with an application that already uses a different graphql release for a different purpose. The second is that genql is not in charge of sending the request. The documentation lists support for subscriptions, built in batching support, and the ability to work with any client such as Apollo or Relay, and a separate page covers integrating with other GraphQL clients.

The runtime portability claim is equally broad. The feature list says it works in the browser, Node, Deno, Cloudflare workers, Bun and more. A library that carries no Node-specific dependency is the reason that list is plausible rather than aspirational.

There is also a migration path for code that already exists. The feature list points to a converter for migrating existing GraphQL queries to Genql code, and the reasons section claims you can safely update your schema and be sure your queries are still valid. A converter for hand written queries is what makes the tool an option for a codebase with history rather than only for a new project.

## The repository is a monorepo with its own build machinery

The layout shows a pnpm workspace with several distinct pieces. The cli directory holds the command line tool, website holds the documentation site, integration-tests holds a separate test project, and two directories named scraper and beskar sit at the root alongside the shared packages.

The root package.json shows how the pieces are driven. The genall script runs the generate step across every package. Build and test run recursively with a filter for the @genql scope, and the integration script changes into the integration-tests directory, runs generation, compiles with tsc and then runs the tests. That last detail matters: the integration path regenerates the client before compiling, which is the only way to know that the generator and the compiler agree.

Development runs the CLI straight from source rather than from a built artifact, using node with sucrase/register to load the TypeScript entry point cli/src/cli.ts. The same mechanism runs the example, and there is a script that points the CLI at a live endpoint and writes to an example/generated directory.

Publishing is handled by lerna with a minor version bump, and the changelog is kept with changesets. The generated client is versioned alongside the rest of the workspace, so a schema change and the client regeneration land in one commit.

## Where the generated client is the wrong answer

The case against genql is a small project with a stable schema. If you have three queries against a schema that changes twice a year, generating a client, committing the output and wiring up a build step is more machinery than the problem deserves, and hand written query strings with a codegen or linting step elsewhere would be less to maintain.

The second limitation is that the query surface is bounded by the generator. The documented object form covers fields, arguments and a scalar shortcut, but it is a query builder, not a query language. When you need a fragment defined once and reused, a directive from a custom schema extension, or an aliased field, the question becomes whether the generator models that, and the documentation does not enumerate it. The stated migration path is a converter for existing queries, which is the right place to check before committing.

The third limitation is workflow. The client does not exist until the CLI runs, so a fresh clone does not type check until someone generates. The documentation is explicit that you only need to regenerate after a schema change, which is a real reduction in friction, but it also means the generated directory has to be committed and kept in step.

On the project itself, the licence is MIT and the last push was on 2026-10-06. The published releases are the CLI packages: @genql/cli 6.3.4 on 2026-06-11, and 6.3.3 on 2024-03-30. The gap between those two versions is long enough that you should read the changelog the project points to before upgrading, and the project is also building a cloud platform for SDK generation in multiple languages, which is a direction worth watching but not something to depend on.

## Conclusion

genql suits teams with a schema that changes often and a TypeScript codebase that would rather catch query mistakes at compile time than at runtime, especially where adding the graphql runtime package is not an option. It does not suit a small project with one stable schema and a handful of hand written queries, where the generation step is more machinery than the problem needs. Before adopting it, run the CLI against your real schema and open the generated files, because the client only exists after that step and the compiler cannot help you until the generation output is committed to your repository.

## FAQ

### How do I generate a genql client?

Install the CLI with npm i -D @genql/cli, then run genql --schema ./schema.graphql --output ./generated. The generated client is written into the output directory and imported from there.

### Does genql require the graphql package at runtime?

No. The documentation states there are no dependencies, not even graphql, and that there is no runtime parsing of queries. The generated code produces a query document instead.

### How do I fetch every scalar field of a type with genql?

Set the __scalar flag on that field in the query object. The example uses it on nestedField to pull in all of its scalar fields without listing them.

### Can genql be used with Apollo or Relay?

Yes. The feature list states that it works with any client, including Apollo and Relay, and the documentation has a page on integrating with other GraphQL clients.

### When does the genql client need to be regenerated?

Only after the schema changes, not after every query change. The generated output is imported by relative path, so it has to exist in the repository and be kept in step with the schema.

## Sources

- [License: MIT](https://github.com/remorses/genql/blob/master/LICENSE)
- [Project website](https://genql.dev)
- [README](https://github.com/remorses/genql/blob/master/README.md)
- [Releases](https://github.com/remorses/genql/releases)
- [remorses/genql on GitHub](https://github.com/remorses/genql)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/remorses-genql
