OpenAPI to TypeScript Schema Generator with Runtime Validation
npm install dtoforgeā” Blazing Fast OpenAPI to TypeScript Generator with Runtime Validation
Transform your OpenAPI 3.0 specifications into type-safe TypeScript schemas with runtime validation in milliseconds. Built in Go for maximum performance, designed for modern TypeScript development.



---
``bashInstall globally (recommended)
npm install -g dtoforge
> ⨠New: NPM package now supports proper global installation and
npx usage across all platforms (Windows, macOS, Linux)!šÆ Quick Start
`bash
Generate TypeScript with io-ts validation (default)
dtoforge -openapi api.yaml -out ./generatedGenerate TypeScript with Zod validation
dtoforge -openapi api.yaml -lang typescript-zod -out ./generatedUse configuration file
dtoforge -openapi api.yaml -config dtoforge.config.yaml
`š Recent Improvements
$3
- Interface-based types - More intuitive interface User extends t.TypeOf (default)
- Cleaner type names - No more redundant "Codec" suffixes (User instead of UserCodec)
- Better field handling - Smart t.intersection pattern for required/optional field separation
- Simplified enums - Direct inline t.keyof generation for cleaner output$3
- Reduced bundle size - Schema registry and helpers disabled by default
- Faster builds - Only generate what you need
- Migration-friendly - Perfect for Swagger 2.0 ā OpenAPI 3.0 upgrades$3
- Proper global installation - npm install -g dtoforge works everywhere
- NPX compatibility - npx dtoforge works without installation
- Multi-architecture - Supports ARM64, x64 on Windows, macOS, Linux⨠What You Get
$3
`yaml
api.yaml
openapi: 3.0.0
components:
schemas:
User:
type: object
required: [id, email, name]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
age:
type: integer
createdAt:
type: string
format: date-time
`$3
With Zod:
`typescript
import { z } from 'zod';export const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
age: z.number().optional(),
createdAt: z.string().datetime().optional(),
});
export type User = z.infer;
// Runtime validation
const user = UserSchema.parse(apiResponse); // ā
Type-safe!
`With io-ts:
`typescript
import * as t from 'io-ts';
import { DateFromISOString } from 'io-ts-types';export const User = t.intersection([
t.type({
id: t.string,
email: t.string,
name: t.string,
}),
t.partial({
age: t.number,
createdAt: DateFromISOString,
})
]);
export interface User extends t.TypeOf {}
// Runtime validation with detailed errors
const result = User.decode(apiResponse);
if (isRight(result)) {
const user: User = result.right; // ā
Type-safe!
}
`šØ Real-World Usage
$3
`typescript
class ApiClient {
async fetchUser(id: string): Promise {
const response = await fetch( /api/users/${id});
const data = await response.json();
// Runtime validation ensures type safety
return UserSchema.parse(data);
}
}
`$3
`typescript
// Automatic partial schemas for forms
const UserFormSchema = UserSchema.partial();function validateForm(formData: unknown) {
const result = UserFormSchema.safeParse(formData);
return result.success ? result.data : result.error;
}
`$3
`json
{
"scripts": {
"generate-types": "dtoforge -openapi api.yaml -out src/types",
"prebuild": "npm run generate-types"
}
}
`āļø Configuration
Create
dtoforge.config.yaml:`yaml
Output configuration
output:
folder: "./src/types"
mode: "multiple" # or "single"Custom type mappings
customTypes:
uuid:
zodType: "z.string().uuid().brand('UUID')"
typeScriptType: "UUID"
import: "import { UUID } from './branded-types';"
date-time:
zodType: "DateTimeSchema"
typeScriptType: "DateTime"
import: "import { DateTimeSchema } from './datetime';"What to generate
generation:
generatePackageJson: true
generateHelpers: false # Generate validation helpers (default: false)
generatePartialCodecs: false # Generate partial type variants (default: false)
generateSchemaRegistry: false # Generate schema registry object (default: false)
generateSchemaNames: false # Generate schema names array (default: false)
useInterfaces: true # Use interfaces instead of type aliases (default: true)
defaultUnknownType: "t.unknown" # Default io-ts type for unknown schemas (default: "t.unknown")OpenAPI extensions support
extensions:
x-nullable:
enabled: true # Enable x-nullable extension support (default: true)
behavior: "null" # "null" | "undefined" | "nullish" (default: "null")Schema exclusion rules (optional)
exclude:
exact:
- "InternalSchema" # Exclude specific schema by exact name
startsWith:
- "Debug" # Exclude schemas starting with "Debug"
endsWith:
- "Internal" # Exclude schemas ending with "Internal"
contains:
- "Test" # Exclude schemas containing "Test"
`š§ Advanced Features
$3
DtoForge allows you to exclude specific schemas from generation using flexible matching rules. This is useful when your OpenAPI spec contains schemas that you don't want to generate (internal types, test schemas, deprecated types, etc.).
#### Configuration:
`yaml
exclude:
exact:
- "InternalSchema"
- "DeprecatedUser"
startsWith:
- "Debug"
- "_"
endsWith:
- "Internal"
- "Request"
contains:
- "Test"
- "Mock"
`#### Matching Rules:
| Rule | Description | Example Pattern | Matches |
|------|-------------|-----------------|---------|
|
exact | Exact name match | "InternalSchema" | InternalSchema only |
| startsWith | Prefix match | "Debug" | DebugUser, DebugConfig |
| endsWith | Suffix match | "Internal" | UserInternal, ConfigInternal |
| contains | Substring match | "Test" | TestUser, UserTest, MyTestData |#### Example Use Cases:
Exclude internal/private schemas:
`yaml
exclude:
endsWith:
- "Internal"
- "Private"
`Exclude test and mock schemas:
`yaml
exclude:
contains:
- "Test"
- "Mock"
- "Stub"
`Exclude specific legacy schemas:
`yaml
exclude:
exact:
- "LegacyUser"
- "DeprecatedOrder"
startsWith:
- "V1_" # Old API version prefixes
`When schemas are excluded, DtoForge will log them:
`
š Excluded 3 schemas: [DebugConfig, TestUser, InternalData]
`---
$3
When DtoForge encounters a property without a recognized type, it generates
t.unknown by default. You can customize this behavior using the defaultUnknownType configuration option.#### Configuration:
`yaml
generation:
defaultUnknownType: "t.unknownRecord" # Use t.unknownRecord instead of t.unknown
`#### Common Values:
| Value | Description | Use Case |
|-------|-------------|----------|
|
t.unknown | Any value (default) | General purpose, most flexible |
| t.unknownRecord | Object with unknown values | When you expect objects but don't know the shape |
| t.record(t.string, t.unknown) | String-keyed record | Explicit record type |#### Example:
OpenAPI Schema:
`yaml
components:
schemas:
Config:
type: object
properties:
settings:
description: Dynamic settings object
# No type specified
`With default (
t.unknown):
`typescript
export const Config = t.partial({
settings: t.unknown,
})
`With
defaultUnknownType: "t.unknownRecord":
`typescript
export const Config = t.partial({
settings: t.unknownRecord,
})
`---
$3
DtoForge supports the
x-nullable OpenAPI extension to explicitly mark fields as nullable, providing more precise type generation than the standard nullable property.Supported in both io-ts and Zod generators.
#### Usage in OpenAPI Schema:
`yaml
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
x-nullable: true # Email is required but can be null
phone:
type: string
x-nullable: false # Explicitly not nullable (optional)
address:
type: string # Regular optional field
`#### Configuration:
Configure x-nullable behavior in your config file:
`yaml
extensions:
x-nullable:
enabled: true # Enable/disable x-nullable processing (default: true)
behavior: "null" # "null" | "undefined" | "nullish"
`#### Behavior Options:
| Behavior | Description | io-ts Output | Zod Output |
|----------|-------------|--------------|------------|
|
"null" (default) | Field can be null | t.union([T, t.null]) | .nullable() |
| "undefined" | Field becomes optional | Moves to t.partial | .optional() |
| "nullish" | Both null and optional | In t.partial with t.union([T, t.null]) | .nullable().optional() |#### Generated Output Examples:
With behavior: "null" (default)
`typescript
// io-ts
export const User = t.intersection([
t.type({
id: t.string,
email: t.union([t.string, t.null]), // Required but nullable
}),
t.partial({ phone: t.string, address: t.string })
])// Zod
export const UserSchema = z.object({
id: z.string(),
email: z.string().nullable(), // Required but nullable
phone: z.string().optional(),
address: z.string().optional(),
});
`With behavior: "undefined"
`typescript
// io-ts
export const User = t.intersection([
t.type({ id: t.string }),
t.partial({
email: t.string, // Moved to partial (optional)
phone: t.string,
address: t.string
})
])// Zod
export const UserSchema = z.object({
id: z.string(),
email: z.string().optional(), // Made optional by x-nullable
phone: z.string().optional(),
address: z.string().optional(),
});
`With behavior: "nullish"
`typescript
// io-ts
export const User = t.intersection([
t.type({ id: t.string }),
t.partial({
email: t.union([t.string, t.null]), // Partial (optional) + nullable
phone: t.string,
address: t.string
})
])// Zod
export const UserSchema = z.object({
id: z.string(),
email: z.string().nullable().optional(), // Both nullable and optional
phone: z.string().optional(),
address: z.string().optional(),
});
`#### Key Differences:
- Standard
nullable: Makes a field accept null values
- x-nullable: true: Behavior depends on configuration (null, undefined, or both)
- x-nullable: false: Explicitly prevents the x-nullable effect$3
`typescript
// Define your branded types
export const UUID = z.string().uuid().brand('UUID');
export type UUID = z.infer;// Configure DtoForge to use them
customTypes:
uuid:
zodType: "UUID"
import: "import { UUID } from './types';"
`$3
`bash
Generate separate files (default)
dtoforge -openapi api.yaml -out ./typesGenerate single file
dtoforge -openapi api.yaml -out ./types -config single-file.yaml
`$3
`bash
Generate without overwriting package.json
dtoforge -openapi api.yaml -out ./existing-project
`š Performance Benchmarks
| Schema Size | DtoForge | Alternative Tools |
|------------|----------|-------------------|
| Small (10 schemas) | 5ms | 250ms |
| Medium (100 schemas) | 25ms | 2.1s |
| Large (1000 schemas) | 180ms | 18s |
Benchmarks run on MacBook Pro M1. Your results may vary.
š Validation Library Comparison
| Feature | io-ts | Zod |
|---------|-------|-----|
| Performance | ā” Fastest | š Fast |
| Bundle Size | š¦ Smaller | š¦ Larger |
| Error Messages | š§ Technical | š¬ User-friendly |
| API Style | š Functional | šÆ Modern |
| Ecosystem | fp-ts compatible | tRPC, Prisma compatible |
$3
- ā
Performance is critical
- ā
You use functional programming patterns
- ā
You need the smallest bundle size$3
- ā
You want the best developer experience
- ā
You use tRPC, Prisma, or similar tools
- ā
You prefer modern, intuitive APIsš CLI Reference
`bash
dtoforge [options]Options:
-openapi string Path to OpenAPI spec (JSON or YAML)
-out string Output directory (default: "./generated")
-lang string typescript | typescript-zod (default: "typescript")
-package string Package name for generated code
-config string Config file path
-no-config Disable config file discovery
-example-config Generate example config file
-version Print version and exit
Examples:
dtoforge -openapi api.yaml -out ./types
dtoforge -openapi api.yaml -lang typescript-zod
dtoforge -openapi api.yaml -config my-config.yaml
dtoforge -example-config
dtoforge -version
`š Troubleshooting
$3
Q: Generated schemas don't match my API
`bash
Ensure your OpenAPI spec is valid
dtoforge -openapi api.yaml --validate-spec
`Q: Import errors in generated code
`bash
Check your custom type imports
dtoforge -openapi api.yaml --debug
`Q: Performance issues
`bash
Use single file mode for faster builds
dtoforge -openapi api.yaml -config single-file.yaml
``DtoForge is open source! Contributions are welcome:
- š Report bugs - GitHub Issues
- š” Request features - GitHub Discussions
- š§ Submit PRs - Contributing Guide
If DtoForge saves you time and makes your development workflow better, consider supporting its development:

Every contribution helps keep this project alive and growing! š
- š ļø Active Development - Regular updates and new features
- š Bug Fixes - Quick response to issues and problems
- š Documentation - Comprehensive guides and examples
- š” Feature Requests - Your ideas shape the future of DtoForge
- ā” Performance - Continuous optimization and improvements
- More time for development and maintenance
- Better documentation and tutorials
- Additional language generators (C#, Java, Python)
- Community support and faster issue resolution
---
MIT License - see the LICENSE file for details.
- Zod - Modern TypeScript schema validation
- io-ts - Excellent runtime type validation library
- fp-ts - Functional programming utilities
- The OpenAPI community for the great specification
---
Made with ā¤ļø by developers, for developers
DtoForge helps you build type-safe applications by bridging the gap between API specifications and runtime validation. Whether you prefer functional programming with io-ts or modern validation with Zod, DtoForge adapts to your development style and project needs.