GraphQL·React·Free

GraphQL to
React hooks.

Turn a GraphQL SDL schema into a typed React client. TanStack Query hooks for every query and mutation, subscriptions over graphql-ws, or plain async functions when you want to bring your own data layer. Unions keep __typename so narrowing works. One file per model, no unused-type bloat. No signup, no install.

GraphQL SDL·React·TanStack Query·Subscriptions·MIT
schema.graphqlInput
type Query {
  payment(id: ID!): Payment
  payments(limit: Int): [Payment!]!
}

type Mutation {
  createPayment(input: CreatePaymentInput!): Payment!
}

enum PaymentStatus {
  PENDING
  SETTLED
  FAILED
}

union PaymentMethod = CardPayment | BankPayment

type Payment {
  id: ID!
  amount: Float!
  currency: String
  status: PaymentStatus!
  method: PaymentMethod!
}
compile
src/services/query-api.ts tsc cleanOutput
// src/services/query-api.ts
import { ApiClient, executeGraphQL, useApiClient } from '../client';
import { useQuery } from '@tanstack/react-query';
import { Payment } from '../models/payment';

const PAYMENT_QUERY = `query Payment($id: ID!) {
  payment(id: $id) {
    id
    amount
    currency
    status
    method {
      __typename
      ... on CardPayment {
        kind
        last4
        brand
      }
      ... on BankPayment {
        kind
        iban
      }
    }
  }
}`;

export const queryApi = {
  payment: async (apiClient: ApiClient, id: string): Promise<Payment | null> => {
    return executeGraphQL<Payment | null>(
      apiClient, PAYMENT_QUERY, 'Payment', 'payment', { 'id': id });
  },
};

// TanStack Query Hooks

export function usePayment(id: string) {
  const apiClient = useApiClient();
  return useQuery({
    queryKey: ['payment', id],
    queryFn: () => queryApi.payment(apiClient, id),
  });
}
What gets generated

Idiomatic TypeScript. Idiomatic hooks.

Below: types emitted from a small payments schema with an enum, a union and an input. Each type lands in its own file; the operation keeps __typename on the union so the result type is straightforward to narrow.

// src/models/payment.ts
import { PaymentMethod } from './payment-method';
import { PaymentStatus } from './payment-status';

export interface Payment {
  id: string;
  amount: number;
  status: PaymentStatus;
  method: PaymentMethod;
  currency?: string | null;
}

// src/models/payment-status.ts
export type PaymentStatus = 'PENDING' | 'SETTLED' | 'FAILED';

// src/models/payment-method.ts
import { CardPayment } from './card-payment';
import { BankPayment } from './bank-payment';

export type PaymentMethod = CardPayment | BankPayment;

// src/models/card-payment.ts
export interface CardPayment {
  readonly __typename: 'CardPayment';
  kind: string;
  last4: string;
  brand: string;
}
Three things we get right

Grounded in how the schema reads.

Each claim here is something you can verify by running the converter on your own schema. Click a card to see the schema fragment that triggered it and the code that came out.

01ProofUnions keep their shape.Spec · InCode · Out
schema.graphql fragment
union PaymentMethod = CardPayment | BankPayment

type CardPayment {
  kind: String!
  last4: String!
  brand: String!
}

type BankPayment {
  kind: String!
  iban: String!
}
emitted .ts files
// models/payment-method.ts
import { CardPayment } from './card-payment';
import { BankPayment } from './bank-payment';

export type PaymentMethod = CardPayment | BankPayment;

// models/card-payment.ts
export interface CardPayment {
  readonly __typename: 'CardPayment';
  kind: string;
  last4: string;
  brand: string;
}

// emitted operation keeps the __typename selection
// so downstream narrowing works without casts:
//
// ... on CardPayment {
//   kind
//   last4
//   brand
// }
// ... on BankPayment {
//   kind
//   iban
// }
React · TanStack Query

Hooks, not just types.

Opt into TanStack Query and every query gets a useQuery hook, every mutation a useMutation. Query keys are stable per variable. The ApiClient is resolved from context, so tests and multi-tenant apps swap it without touching call sites.

src/services/query-api.ts generated
// src/services/query-api.ts
import { ApiClient, executeGraphQL, useApiClient } from '../client';
import { useQuery } from '@tanstack/react-query';
import { Payment } from '../models/payment';

const PAYMENT_QUERY = `query Payment($id: ID!) {
  payment(id: $id) {
    id
    amount
    currency
    status
    // ... union selection trimmed for brevity
  }
}`;

export const queryApi = {
  payment: async (apiClient: ApiClient, id: string): Promise<Payment | null> => {
    return executeGraphQL<Payment | null>(
      apiClient, PAYMENT_QUERY, 'Payment', 'payment', { 'id': id });
  },
};

// TanStack Query Hooks

export function usePayment(id: string) {
  const apiClient = useApiClient();
  return useQuery({
    queryKey: ['payment', id],
    queryFn: () => queryApi.payment(apiClient, id),
  });
}

// src/services/mutation-api.ts
import { useMutation } from '@tanstack/react-query';

export function useCreatePaymentMutation() {
  const apiClient = useApiClient();
  return useMutation({
    mutationFn: (input: CreatePaymentInput) =>
      mutationApi.createPayment(apiClient, input),
  });
}
Also available via MCP

Or ask Claude to do it.

The same generator ships as a Model Context Protocol server. It calls the same web API this converter uses, so the output is identical. Point Claude Desktop, Cursor, or any MCP-aware agent at a GraphQL schema and it produces the React client as a diff — free, no token, no leaving the chat.

// claude-desktop config · .mcp.json
{
  "mcpServers": {
    "metaengine": {
      "command": "npx",
      "args": ["-y", "@metaengine/mcp-server"]
    }
  }
}

// Then in Claude:
// "Read schema.graphql and generate a React client
//  with TanStack Query hooks into ./src."
See the MCP page
Prefer it in your build?

Same generator, as a package.

The converter above is the fastest way to try the output. For CI and repeatable builds, the identical generator ships as a package — pin a version, wire it into your pipeline, and review diffs like any other code.

Questions

Things people ask.

The same questions we keep getting. If yours is not here, run the converter — the answer is usually in the output.

A tree of .ts files: one interface per object type, one string-literal union per enum, and either TanStack Query hooks or plain async functions per operation. Subscriptions land in their own subscription-api.ts with a React hook each. Plus a small client.ts with an ApiClient, a context provider, and a useApiClient hook. No runtime generator dependency.
More converters

Same engine, other stacks.

Every converter runs the same spec-to-IR pipeline. Pick another source format or target stack.

OpenAPITypeScript
Angular httpResource, React TanStack Query, or framework-agnostic fetch.
Open converter
OpenAPIAngular
Typed services with httpResource, Signals, inject() DI, and interceptors.
Open converter
OpenAPIReact
TanStack Query hooks with stable query keys, or plain async functions.
Open converter
OpenAPIFetch
Framework-agnostic fetch client with ApiResult, middleware, and retries.
Open converter
OpenAPIJava Spring
RestClient services, Java records, sealed interfaces, Spring Boot 3.
Open converter
OpenAPIPython
Async httpx clients paired with Pydantic v2 models.
Open converter
OpenAPIGo
Idiomatic net/http client, context-aware, pointer-nullable fields.
Open converter
OpenAPIKotlin
Ktor client, kotlinx.serialization, sealed interfaces for oneOf.
Open converter
OpenAPIC#
Records, nullable reference types, JsonPolymorphic, HttpClient.
Open converter
OpenAPIRust
reqwest client with serde-tagged enums for oneOf and Option<T>.
Open converter
GraphQLAngular
Observable services with typed queries, mutations, and graphql-ws subscriptions.
Open converter
GraphQLKotlin
Ktor, kotlinx.serialization, Flow-based subscriptions.
Open converter
GraphQLC#
Records, nullable types, [JsonPolymorphic] unions, IAsyncEnumerable subscriptions.
Open converter
ProtobufKotlin
Ktor, coroutines, and sealed classes straight from .proto.
Open converter
ProtobufC#
Records, nullable types, Connect RPC over HttpClient.
Open converter
ProtobufGo
Plain structs with json tags, pointer presence, stdlib-only Connect client.
Open converter
SQLTypeScript
Postgres DDL to typed interfaces with foreign-key navigation.
Open converter
SQLKotlin
CREATE TABLE scripts to Kotlin @Serializable data classes.
Open converter

Try it on your own schema.

The converter runs in the browser. Your schema never leaves the page.

Open the converter