npm package: @softonus/graphql-codegen-zod
Description: GraphQL Code Generator plugin that generates Zod v4 validation schemas with full type inference. Modern replacement for typescript-validation-schema
Author: Ebo
Latest version: 1.1.0

# @softonus/graphql-codegen-zod

> **Generate Zod v4 validation schemas from your GraphQL Input types automatically**

[![npm version](https://img.shields.io/npm/v/@softonus/graphql-codegen-zod)](https://www.npmjs.com/package/@softonus/graphql-codegen-zod)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## What is this?

`@softonus/graphql-codegen-zod` is a GraphQL Code Generator plugin that **automatically generates Zod v4 validation schemas** from your GraphQL Input types.

It's the **modern replacement for `typescript-validation-schema`** which has compatibility issues with Zod v4.

## Why do I need this?

- Generate validation schemas directly from your GraphQL schema
- No manual synchronization between GraphQL types and Zod schemas
- Full type inference with Zod v4
- Works perfectly with React Hook Form + Zod Resolver

## Installation

```bash
# npm
npm install @softonus/graphql-codegen-zod

# yarn
yarn add @softonus/graphql-codegen-zod

# pnpm
pnpm add @softonus/graphql-codegen-zod

# bun
bun add @softonus/graphql-codegen-zod
```

## Quick Start

```yaml
# codegen.ts
import { CodegenConfig } from '@graphql-codegen/cli'
import { readdirSync } from 'fs'

const scalars = {
  ID: 'string', String: 'string', Int: 'number', Boolean: 'boolean',
  Float: 'number', JSON: '{ [key: string]: any }', Upload: 'File',
  DateTime: 'string', Email: 'string', PhoneNumber: 'string'
}

const scalarSchemas = {
  ID: 'z.string().min(1)', String: 'z.string().min(1)', Int: 'z.number().int()',
  Boolean: 'z.boolean()', Float: 'z.number()', JSON: 'z.record(z.string(), z.any())',
  Upload: 'z.instanceof(File)', DateTime: 'z.string()', Email: 'z.string().email()',
  PhoneNumber: 'z.string().min(8)'
}

const modules = readdirSync('gql', { withFileTypes: true })
  .filter(d => d.isDirectory())
  .map(d => d.name)

const config: CodegenConfig = {
  schema: 'http://localhost:4000/graphql',
  documents: 'gql/**/*.gql',
  generates: modules.reduce((acc, module) => ({
    ...acc,
    [`gql/${module}/graphql.ts`]: {
      schema: 'http://localhost:4000/graphql',
      documents: `gql/${module}/**/*.gql`,
      plugins: ['typescript', 'typescript-operations', 'typed-document-node'],
      config: { scalars, useTypeImports: true }
    },
    [`gql/${module}/zod.ts`]: {
      schema: 'http://localhost:4000/graphql',
      documents: `gql/${module}/**/*.gql`,
      plugins: ['@softonus/graphql-codegen-zod'],
      config: { scalarSchemas }
    }
  }), {})
}

export default config
```

## What it generates

Given a GraphQL schema with Input types:

```graphql
input CreateUserInput {
  """The user's full name"""
  name: String!
  """Valid email address"""
  email: String!
  age: Int
  status: UserStatus!
}

enum UserStatus {
  ACTIVE
  INACTIVE
  SUSPENDED
}
```

It generates `gql/auth/zod.ts` (grouped by module):

```typescript
/* eslint-disable */
/**
 * @softonus/graphql-codegen-zod
 * Generated by graphql-codegen
 */

import { z } from 'zod'

// ============ AUTH INPUTS ============

export function CreateUserInputSchema() {
  return z.object({
    name: z.string().min(1).describe("The user's full name"),
    email: z.string().email().describe("Valid email address"),
    age: z.number().int().optional(),
    status: z.enum(["ACTIVE", "INACTIVE", "SUSPENDED"])
  })
}
```

## Usage with React Hook Form

```tsx
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { CreateUserInputSchema } from '@/gql/auth/zod'

function CreateUserForm() {
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(CreateUserInputSchema())
  })

  // Form is automatically validated against GraphQL schema types! ✓
}
```

## Comparison

| Feature | typescript-validation-schema | @softonus/graphql-codegen-zod |
|---------|------------------------------|---------------------|
| Zod v4 support | ❌ Partial | ✅ Full |
| Type inference | ⚠️ Limited | ✅ Full |
| Per-module output | ❌ No | ✅ Yes |
| Custom scalars | ⚠️ Manual | ✅ Automatic |
| Schema grouping | ❌ Flat | ✅ By module |
| Enum support | ❌ No | ✅ Yes |
| Field descriptions | ❌ No | ✅ Yes (`.describe()`) |
| Discriminated unions | ❌ No | ✅ Yes |
| Field refinements | ❌ No | ✅ Yes |

## Configuration Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `importFrom` | `string` | `'@/gql/graphql'` | Import types from path |
| `groupByModule` | `boolean` | `true` | Group schemas by folder |
| `scalarSchemas` | `Record<string, string>` | See below | Custom scalar mappings |
| `module` | `string` | `undefined` | Only generate schemas for this specific module |
| `fieldRefinements` | `Record<string, Record<string, string>>` | `{}` | Field-level refinement chains |
| `generateObjectTypes` | `boolean` | `false` | Also generate output/object type schemas |

### Default Scalar Schemas

```typescript
{
  ID: 'z.string().min(1)',
  String: 'z.string()',
  Int: 'z.number().int()',
  Boolean: 'z.boolean()',
  Float: 'z.number()',
  DateTime: 'z.string()',
  Email: 'z.string().email()',
  PhoneNumber: 'z.string().min(8)',
  JSON: 'z.record(z.string(), z.any())',
  Upload: 'z.instanceof(File)'
}
```

## FAQ

**Q: Can I use this with Zod v3?**
A: This plugin is designed for Zod v4. For Zod v3, use `typescript-validation-schema`.

**Q: How is the module grouping determined?**
A: Based on Input type naming convention. For example: `CreateUserInput` → `auth` module, `CreatePostInput` → `posts` module.

**Q: How do I customize scalar mappings?**
A: Pass `scalarSchemas` in the config:
```yaml
config:
  scalarSchemas:
    DateTime: 'z.string().datetime()'
```

**Q: Can I generate schemas for all Input types or just specific ones?**
A: All Input types in your GraphQL schema are automatically converted to Zod schemas.

**Q: Does it extract default values from the schema?**
A: Yes. Fields with `= default` in the schema get `.default(value)` on the Zod schema.

**Q: How do I add custom validation to specific fields?**
A: Use `fieldRefinements` in your config:

```yaml
config:
  fieldRefinements:
    SignInInput:
      email: '.refine(v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), { message: "Invalid email" })'
```

**Q: Does it support GraphQL enums?**
A: Yes! Enum types are automatically converted to `z.enum([...])` with their values.

**Q: Can I generate schemas for output types (not just inputs)?**
A: Yes! Set `generateObjectTypes: true` in your config to also generate schemas for output/object types and unions (as `z.discriminatedUnion`). Field descriptions are automatically carried over as `.describe()`.

## Example Output Structure

```
gql/
├── auth/
│   ├── graphql.ts      # GraphQL types
│   └── zod.ts         # SignInInputSchema, SignUpInputSchema, etc.
├── posts/
│   ├── graphql.ts
│   └── zod.ts         # CreatePostInputSchema, etc.
└── memorial/
    ├── graphql.ts
    └── zod.ts         # CreateMemorialInputSchema, etc.
```

## License

MIT © [Ebo](https://github.com/ebokoo)