Comprehensive comparison of Orval, openapi-ts, Skmtc, and Kubb for generating Zod schemas from OpenAPI specifications.
Test Schema: GitHub REST API v3 OpenAPI spec (11.1MB JSON)
| Metric | Kubb | openapi-ts | Orval | Skmtc | Winner |
|---|---|---|---|---|---|
| Average Generation Time | 8.7s | 11.8s | 5.0s | 2.4s | 🏆 Skmtc (fastest) |
| Output File Size (Zod) | 3.4MB | 2.6MB | 4.5MB | 1.2MB | 🏆 Skmtc (smallest) |
| Total Output Size | 3.6MB | 2.6MB | 4.5MB | 1.2MB | 🏆 Skmtc (smallest) |
| Number of Files | 2 files | 1 file | 1 file | 1 file | openapi-ts/Orval/Skmtc |
| Output Path | src/gen/zod.ts |
src/client/zod.gen.ts |
src/gen/github-zod.ts |
skmtc-zod-output/types.generated.ts |
- |
| Feature | Kubb | openapi-ts | Orval | Skmtc |
|---|---|---|---|---|
| Config File | kubb.config.ts |
openapi-ts.config.ts |
orval.config.mjs |
.skmtc/<project>/deno.json |
| Package Name | @kubb/cli + plugins |
@hey-api/openapi-ts |
orval |
skmtc |
| Version Tested | 4.4.0 | 0.86.8 | 8.0.0-rc.0 | Latest (Deno) |
| Config Type | TypeScript | TypeScript | JavaScript/ESM | JSON |
| Installation | pnpm add -D @kubb/cli @kubb/core @kubb/plugin-oas @kubb/plugin-zod |
pnpm add -D -E @hey-api/openapi-ts |
pnpm add -D orval |
deno install -A jsr:@skmtc/cli |
| Runtime | Node.js | Node.js | Node.js | Deno |
| Zod Dependency | pnpm add zod |
pnpm add zod |
pnpm add zod |
Included (Deno) |
| Feature | Kubb | openapi-ts | Orval | Skmtc |
|---|---|---|---|---|
| Zod Import | import { z } from 'zod/v4' |
import { z } from 'zod' |
import * as zod from 'zod' |
import { z } from 'zod' |
| Namespace | Uses z.object() |
Uses z.object() |
Uses zod.object() |
Uses z.object() |
| Property Quotes | Quoted ("id") |
Unquoted (id:) |
Quoted ("id") |
Unquoted (id:) |
| Optional Syntax | z.optional(z.number()) |
z.optional(z.number()) |
zod.number().optional() |
z.number().optional() |
| Formatting | Multi-line, readable | Multi-line, readable | Multi-line, readable | Single-line, compact |
| TypeScript Integration | ❌ No | ❌ No | ❌ No | ❌ No |
| File Organization | Single file or separate files | Single file | Single file | Single file or separate files |
Orval:
export const updatePetBody = zod.object({
"id": zod.number().optional(),
"name": zod.string(),
"photoUrls": zod.array(zod.string()),
"status": zod.enum(['available', 'pending', 'sold']).optional()
})openapi-ts:
export const zRoot = z.object({
current_user_url: z.string(),
authorizations_url: z.string(),
hub_url: z.optional(z.string()),
});
export const zSecurityAdvisoryEcosystems = z.enum([
'rubygems',
'npm',
'pip',
]);Skmtc:
export const root = z.object({current_user_url: z.string(), current_user_authorizations_html_url: z.string(), authorizations_url: z.string(), code_search_url: z.string(), commit_search_url: z.string(), emails_url: z.string(), emojis_url: z.string(), events_url: z.string(), feeds_url: z.string(), followers_url: z.string(), following_url: z.string(), gists_url: z.string(), hub_url: z.string().optional(), issue_search_url: z.string(), issues_url: z.string(), keys_url: z.string(), label_search_url: z.string(), notifications_url: z.string(), organization_url: z.string(), organization_repositories_url: z.string(), organization_teams_url: z.string(), public_gists_url: z.string(), rate_limit_url: z.string(), repository_url: z.string(), repository_search_url: z.string(), current_user_repositories_url: z.string(), starred_url: z.string(), starred_gists_url: z.string(), topic_search_url: z.string().optional(), user_url: z.string(), user_organizations_url: z.string(), user_repositories_url: z.string(), user_search_url: z.string()});
export const securityAdvisoryEcosystems = z.enum(["rubygems", "npm", "pip", "maven", "nuget", "composer", "go", "rust", "erlang", "actions", "pub", "other", "swift"]);Kubb:
// File: src/gen/zod/rootSchema.ts
import { z } from "zod/v4";
export const rootSchema = z.object({
"current_user_url": z.string(),
"current_user_authorizations_html_url": z.string(),
"authorizations_url": z.string(),
"code_search_url": z.string(),
// ... more properties
"hub_url": z.optional(z.string()),
// ... more properties
})| Feature | Kubb | openapi-ts | Orval | Skmtc |
|---|---|---|---|---|
| Zod Schema Generation | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| TypeScript Types | ❌ No (Zod only mode) | ✅ Yes (optional) | ❌ No (Zod only mode) | ❌ No |
| SDK Generation | ❌ No | ✅ Yes (optional) | ❌ No | ❌ No |
| Client Generation | ❌ No | ✅ Yes (optional) | ❌ No | ❌ No |
| Request Validation | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Response Validation | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Definitions/Components | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Split Mode | ✅ Yes (separate files per schema) | ❌ No | ✅ Yes (tags-split, split) | ✅ Yes (separate files per schema) |
| Single File Mode | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Compact Output | ❌ No | ❌ No | ❌ No | ✅ Yes (single-line) |
| Type Integration | ❌ No | ❌ No | ❌ No | ❌ No |
| Tool | Run 1 | Run 2 | Run 3 | Average |
|---|---|---|---|---|
| Kubb | 8.661s | 8.499s | 8.909s | 8.7s |
| openapi-ts | 11.706s | 11.672s | 12.093s | 11.8s |
| Orval | 5.135s | 4.827s | 5.138s | 5.0s |
| Skmtc | 2.371s | 2.361s | 2.331s | 2.4s |
File: orval.config.mjs
import { defineConfig } from 'orval';
export default defineConfig({
github: {
input: {
target: './schemas/github-openapi.json',
},
output: {
client: 'zod',
mode: 'single',
target: './src/gen/github-zod.ts',
},
},
});File: openapi-ts.config.ts
import { defineConfig } from '@hey-api/openapi-ts';
export default defineConfig({
input: './schemas/github-openapi.json',
output: 'src/client',
plugins: [
{
name: 'zod',
requests: true,
responses: true,
definitions: true,
},
],
});File: .skmtc/skmtc-zod/deno.json
{
"imports": {
"@skmtc/gen-zod": "jsr:@skmtc/gen-zod@^0.1.0"
}
}Command:
skmtc generate skmtc-zod .skmtc/skmtc-zod/openapi.jsonFile: kubb.config.ts
import { defineConfig } from '@kubb/core'
import { pluginOas } from '@kubb/plugin-oas'
import { pluginZod } from '@kubb/plugin-zod'
export default defineConfig({
root: '.',
input: {
path: './schemas/github-openapi.json',
},
output: {
path: './src/gen',
},
plugins: [
pluginOas({
output: false, // Disable JSON schema output
}),
pluginZod({
output: {
path: 'zod.ts', // Single file output
},
typed: false,
dateType: 'stringOffset',
unknownType: 'unknown',
version: '4',
}),
],
})Command:
pnpm generate| Use Case | Recommended Tool | Reason |
|---|---|---|
| Fastest generation | 🏆 Skmtc | 2.4s - Fastest by far (52% faster than Orval) |
| Smallest bundle size | 🏆 Skmtc | 1.2MB - Most compact output (54% smaller than openapi-ts) |
| Large monorepos | 🏆 Kubb or Skmtc | Separate files per schema for better organization |
| Future extensibility | 🏆 openapi-ts | Can add SDK, types, and client later |
| Deno projects | 🏆 Skmtc | Native Deno support |
| Tree-shakeable imports | 🏆 Kubb or Skmtc | Individual files enable optimal tree-shaking (when using split mode) |
| Simple single-file output | 🏆 Skmtc or openapi-ts | Cleanest single-file output (Skmtc: 1.2MB, openapi-ts: 2.6MB) |
| Plugin-based extensibility | 🏆 Kubb | Modular plugin architecture for custom generators |
- 🚀 Speed is the top priority (2.4s - fastest by far, 52% faster than Orval)
- 📦 Smallest possible bundle size (1.2MB - 54% smaller than openapi-ts)
- 🦕 You're using Deno or want Deno support
- ✨ You prefer compact, minified-style output
- 🎯 Best all-around performance (fastest AND smallest)
- 📁 Flexibility: supports both single-file and multi-file modes
- 🎯 Simple single-file output is preferred (4.5MB)
- 📁 You need tag-based split mode for organization
- 🔧 You prefer JavaScript/ESM configuration
- ⚡ Fast generation with readable output (5.0s - second fastest)
- 🛠️ Node.js ecosystem with excellent npm/pnpm integration
- 🛠️ Future extensibility is important (SDK, types, client)
- 📝 You prefer TypeScript configuration
- 🔄 You might need SDK generation later
- 📦 Moderate bundle size is acceptable
- 🔧 You prefer plugin-based architecture for extensibility
- 🎯 You need flexible output modes (single file or split mode)
- 🌳 Tree-shaking is important (supports separate file per schema)
- 📦 Moderate file size is acceptable (3.4MB - larger than openapi-ts)
- ⚡ Generation time is acceptable (8.7s - third fastest)
- Speed: Fastest generation time (2.4s - 52% faster than Orval, 80% faster than openapi-ts)
- Size: Smallest output (1.2MB - 54% smaller than openapi-ts, 73% smaller than Orval)
- Flexibility: Supports both single-file (1.2MB) and multi-file (870 files) modes
- Deno-native: Built for Deno runtime with native TypeScript support
- Compact format: Uses single-line compact format for minimal file size
- Simplicity: Zero configuration needed for basic usage
- Best overall: Only tool that is BOTH fastest AND smallest
- Speed: Second fastest (5.0s)
- Simple output: Single-file output (4.5MB) for easy integration
- Flexibility: Multiple output modes (single, split, tags-split)
- Organization: Tag-based split mode for large monorepos
- Validation options: Can disable validation for broken schemas
- Node.js ecosystem: Excellent npm/pnpm integration
- Extensibility: Can enable additional features (SDK, types) when needed
- Modern tooling: TypeScript-first configuration
- Complete solution: Types + Schemas + SDK in one tool
- Moderate size: 2.6MB output balances size and features
- Speed: Third fastest (8.7s)
- Flexibility: Supports both single-file (3.4MB) and multi-file modes
- Plugin Architecture: Modular, extensible plugin system
- Configuration: Highly configurable with transformers and custom generators
- Output modes: Can disable JSON schema output for cleaner builds
- Barrel files: Automatic index.ts generation for convenient imports
- ✅ Generating high-quality Zod v4 schemas
- ✅ Handling large OpenAPI specifications (11MB+ JSON)
- ✅ Request/response/component schema generation
- ✅ Easy integration into existing projects
- ✅ Active development and community support
Speed vs Features:
- Skmtc is fastest (2.4s) with flexible single/multi-file modes
- Orval is second fastest (5.0s) with more flexibility options
- Kubb is third fastest (8.7s) with plugin-based architecture
- openapi-ts is slowest (11.8s) but offers SDK/client generation
Size vs Organization:
- Skmtc has smallest output (1.2MB single file) - best for bundle size
- openapi-ts has second smallest (2.6MB single file)
- Kubb has moderate output (3.4MB single file, 3.6MB total)
- Orval has larger output (4.5MB single file)
File Organization:
- Single-file: Skmtc (1.2MB), openapi-ts (2.6MB), Kubb (3.4MB), Orval (4.5MB)
- Multi-file: Skmtc (870 files, 4.4MB), Kubb (2014 files, 9.5MB) - better tree-shaking
- Both Skmtc and Kubb support both modes with configuration
Speed Comparison:
- Skmtc: 2.4s (baseline - fastest)
- Orval: 5.0s (2.08x slower than Skmtc)
- Kubb: 8.7s (3.63x slower than Skmtc)
- openapi-ts: 11.8s (4.92x slower than Skmtc)
Test Date: October 28, 2025 Test Environment: macOS, Node.js v22.16.0, Deno (latest), pnpm v10.15.1