Seamlessly convert Zod schemas to GraphQL queries, mutations, and subscriptions
zod2gql
extends Zod's schema objects with GraphQL generation capabilities, allowing you to automatically convert your Zod schemas into GraphQL queries, mutations, and subscriptions.
- π Simple API: Extend Zod schemas with
.toGQL()
method - π All Operation Types: Support for queries, mutations, and subscriptions
- π Array Schema Support: Automatic field name pluralization for array schemas
- π§© Complex Schema Support: Handles nested objects, arrays, circular references, enums, and more
- π Fully Type-Safe: Written in TypeScript with complete type definitions
- π οΈ Customizable: Control operation names, variables, input types, and recursion depth
- π§ Intelligent: Can infer operation field names from schema descriptions
- β‘ Lightweight: Zero dependencies beyond Zod itself
# npm
npm install zod2gql
# yarn
yarn add zod2gql
# pnpm
pnpm add zod2gql
import { z } from 'zod';
import { GQLType } from 'zod2gql';
import 'zod2gql/query'; // Import the extension
// Define a Zod schema with a name using describe()
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
age: z.number(),
}).describe('User'); // Set schema name for field inference
// Generate a GraphQL query
const query = userSchema.toGQL(GQLType.Query, {
operationName: 'GetUser',
variables: { id: '123' }
});
console.log(query);
/*
query GetUser($id: String!) {
user(id: $id) {
id
name
email
age
}
}
*/
zod2gql
supports array schemas with automatic field name pluralization:
import { z } from 'zod';
import { GQLType } from 'zod2gql';
import 'zod2gql/query'; // Import the extension
// Define a Zod schema with a name using describe()
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}).describe('User');
// Generate a query for multiple users with automatic pluralization
const usersQuery = z.array(userSchema).toGQL(GQLType.Query, {
variables: { limit: 10, offset: 0 }
});
console.log(usersQuery);
/*
query($limit: Int!, $offset: Int!) {
users(limit: $limit, offset: $offset) {
id
name
email
}
}
*/
Notice how the field name is automatically pluralized from "user" to "users" when using an array schema.
Instead of using the method directly, you can use the helper functions for better readability:
import { z } from 'zod';
import { createQuery, createMutation, createSubscription } from 'zod2gql';
// Query
const query = createQuery(userSchema, {
operationName: 'GetUser',
variables: { id: '123' }
});
// Mutation
const mutation = createMutation(userSchema, {
operationName: 'UpdateUser',
variables: { id: '123', name: 'New Name' }
});
// Subscription
const subscription = createSubscription(userSchema, {
operationName: 'UserUpdated',
variables: { userId: '123' }
});
zod2gql can infer the operation field name from the schema's description:
// Add a name to your schema using describe()
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string()
}).describe('User');
// The operation field name "user" will be inferred
const query = createQuery(userSchema, {
variables: { id: '123' }
});
console.log(query);
/*
query($id: String!) {
user(id: $id) {
id
name
email
}
}
*/
// For array schemas, the field name is automatically pluralized
const usersQuery = z.array(userSchema).toGQL(GQLType.Query, {
variables: { limit: 10 }
});
console.log(usersQuery);
/*
query($limit: Int!) {
users(limit: $limit) {
id
name
email
}
}
*/
zod2gql handles nested schemas, arrays, and circular references:
const addressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string()
}).describe('Address');
const userSchema = z.object({
id: z.string(),
name: z.string(),
address: addressSchema,
friends: z.array(z.lazy(() => userSchema))
}).describe('User');
const query = createQuery(userSchema, {
operationName: 'GetUser',
variables: { id: '123' }
});
You can control the depth of the generated query to limit nesting:
const query = createQuery(complexSchema, {
operationName: 'GetComplex',
variables: { id: '123' },
maxDepth: 3 // Limit recursion depth
});
For mutations with complex input types, you can provide type mappings:
const mutation = createMutation(userSchema, {
operationName: 'CreateUser',
variables: {
userData: { name: 'John', email: '[email protected]' }
},
inputTypeMap: {
userData: 'UserInput' // Map 'userData' to 'UserInput!' in GraphQL
}
});
For bulk mutations, array schemas are particularly useful:
const bulkCreateMutation = z.array(userSchema).toGQL(GQLType.Mutation, {
variables: {
users: [
{ name: 'John', email: '[email protected]' },
{ name: 'Jane', email: '[email protected]' }
]
},
inputTypeMap: {
users: '[UserInput!]'
}
});
console.log(bulkCreateMutation);
/*
mutation($users: [UserInput!]!) {
createUsers(users: $users) {
id
name
email
}
}
*/
enum GQLType {
Query = 'query',
Mutation = 'mutation',
Subscription = 'subscription'
}
interface ToGQLOptions {
operationName?: string;
variables?: Record<string, any>;
maxDepth?: number;
inputTypeMap?: Record<string, string>;
}
// Extension method added to ZodObject
toGQL(
queryType?: GQLType,
options?: ToGQLOptions,
depth?: number
): string;
// Extension method added to ZodArray
toGQL(
queryType?: GQLType,
options?: ToGQLOptions,
depth?: number
): string;
// Helper functions
createQuery(schema: z.ZodObject<any>, options?: ToGQLOptions): string;
createMutation(schema: z.ZodObject<any>, options?: ToGQLOptions): string;
createSubscription(schema: z.ZodObject<any>, options?: ToGQLOptions): string;