Dorval brings the power of Orval to the Dart/Flutter ecosystem, automatically generating:
- 🎯 Type-safe API clients - Never worry about API contracts again
- 🔒 Freezed models - Immutable data classes with JSON serialization
- 🌐 HTTP client support - Dio client with ApiClient wrapper (more coming soon)
- ✅ Full null safety - Leveraging Dart's sound null safety
- 🔀 Smart header consolidation - Automatically reduces duplicate header classes
dorval/
├── packages/
│ ├── core/ # Core generation logic (@dorval/core)
│ │ ├── src/
│ │ │ ├── __tests__/ # Test files
│ │ │ ├── generators/ # Dart code generators
│ │ │ ├── getters/ # Schema getters
│ │ │ ├── parser/ # OpenAPI parsing
│ │ │ ├── resolvers/ # Reference resolvers
│ │ │ ├── templates/ # Handlebars templates
│ │ │ ├── utils/ # Utilities
│ │ │ └── writers/ # File writers
│ │ ├── dist/ # Compiled output
│ │ ├── package.json
│ │ └── tsconfig.json
│ │
│ ├── dorval/ # CLI tool (dorval)
│ │ ├── src/
│ │ │ ├── bin/ # CLI entry point
│ │ │ ├── commands/ # CLI commands
│ │ │ └── cli.ts # CLI configuration
│ │ ├── dist/ # Compiled output
│ │ └── package.json
│ │
│ ├── dio/ # Dio client templates (@dorval/dio)
│ │ ├── src/
│ │ │ └── builders/ # Dio-specific builders
│ │ └── package.json
│ │
│ └── custom/ # Custom client templates (@dorval/custom)
│ ├── src/
│ │ └── builders/ # Custom client builders
│ └── package.json
│
├── .github/
│ └── workflows/ # GitHub Actions CI/CD
│ ├── ci.yml # Continuous Integration
│ ├── release.yml # Auto release (semantic-release)
│ └── manual-publish.yml # Manual backup publish
│
└── docs/ # Documentation
├── PUBLISHING.md # Publishing guide
└── RELEASE.md # Release process
# Install globally
npm install -g dorval
# Or use with npx
npx dorval generate -i ./openapi.yaml -o ./lib/api
# Or add as a dev dependency
npm install --save-dev dorval# Clone the repository
git clone https://github.com/qwlong/dorval.git
cd dorval
# Install dependencies
yarn install
# Build the project
yarn build
# Link CLI globally (optional)
cd packages/dorval
npm link- Create a configuration file:
You can use either CommonJS or ES Modules format:
ES Modules (dorval.config.js or dorval.config.mjs):
export default {
petstore: {
input: './petstore.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio',
override: {
generator: {
freezed: true,
jsonSerializable: true,
nullSafety: true
},
methodNaming: 'methodPath' // 'operationId' | 'methodPath'
}
}
}
}CommonJS (dorval.config.cjs):
module.exports = {
petstore: {
input: './petstore.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio',
override: {
generator: {
freezed: true,
jsonSerializable: true,
nullSafety: true
},
methodNaming: 'methodPath' // 'operationId' | 'methodPath'
}
}
}
}TypeScript (dorval.config.ts):
export default {
petstore: {
input: './petstore.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio',
override: {
generator: {
freezed: true,
jsonSerializable: true,
nullSafety: true
},
methodNaming: 'methodPath' // 'operationId' | 'methodPath'
}
}
}
}- Run the generator:
# Using config file
dorval generate
# Using command line options
dorval generate -i ./petstore.yaml -o ./lib/api
# Watch mode (coming soon)
# dorval watch- Use the generated code in your Flutter app:
import 'package:dio/dio.dart';
import 'api/api_client.dart';
import 'api/services/pets_service.dart';
import 'api/models/index.dart';
void main() async {
// Initialize the API client
final apiClient = ApiClient(
baseUrl: 'https://petstore.swagger.io/v2',
);
// Create service instance
final petsService = PetsService(apiClient);
// Type-safe API calls with generated models!
try {
// List all pets with optional query parameters
final pets = await petsService.listPets(
params: ListPetsParams(limit: 10),
);
// Create a new pet
final newPet = NewPet(
name: 'Fluffy',
tag: 'cat',
);
final createdPet = await petsService.createPets(newPet);
// Get pet by ID
final pet = await petsService.showPetById('123');
// Update a pet
final updatedPet = await petsService.updatePet(
'123',
NewPet(name: 'Fluffy Updated'),
);
// Delete a pet
await petsService.deletePet('123');
} catch (e) {
if (e is ApiException) {
print('API Error: ${e.statusCode} - ${e.message}');
}
}
// Don't forget to dispose when done
apiClient.dispose();
}Dorval can be used programmatically in your Node.js scripts. Both CommonJS and ES Modules are supported:
import { generateDartCode } from '@dorval/core';
// Using async/await
const files = await generateDartCode({
input: './openapi.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio'
}
});
console.log(`Generated ${files.length} files`);const { generateDartCode } = require('@dorval/core');
// Using promises
generateDartCode({
input: './openapi.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio'
}
}).then(files => {
console.log(`Generated ${files.length} files`);
});
// Or with async/await in CommonJS
(async () => {
const files = await generateDartCode({
input: './openapi.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio'
}
});
console.log(`Generated ${files.length} files`);
})();import { generateDartCode } from '@dorval/core';
import type { DorvalConfig } from '@dorval/core';
const config: DorvalConfig = {
input: './openapi.yaml',
output: {
target: './lib/api',
mode: 'split',
client: 'dio'
}
};
const files = await generateDartCode(config);
console.log(`Generated ${files.length} files`);# Install dependencies
yarn install
# Build all packages
yarn build
# Run in development mode
yarn dev
# Run tests
yarn testThis project uses Conventional Commits for automatic versioning and changelog generation.
<type>(<scope>): <subject>
<body>
<footer>
| Type | Version | Description | Example |
|---|---|---|---|
fix |
Patch (0.2.0 → 0.2.1) | Bug fixes | fix: resolve null reference in parser |
feat |
Minor (0.2.0 → 0.3.0) | New features | feat: add support for oneOf schemas |
feat! or BREAKING CHANGE |
Major (0.2.0 → 1.0.0) | Breaking changes | feat!: change API structure |
perf |
Patch | Performance improvements | perf: optimize model generation |
docs |
No release | Documentation only | docs: update configuration guide |
style |
No release | Code style changes | style: format code |
refactor |
No release | Code refactoring | refactor: simplify parser logic |
test |
No release | Test changes | test: add parser tests |
chore |
No release | Maintenance tasks | chore: update dependencies |
chore(deps) |
Patch | Dependency updates | chore(deps): update dio to v5.4 |
# Bug fix (patch release)
git commit -m "fix: handle nullable array items correctly"
# New feature (minor release)
git commit -m "feat: add Retrofit client support"
# Breaking change (major release)
git commit -m "feat!: rename generateDartCode to generate
BREAKING CHANGE: The main API function has been renamed for consistency"
# Multiple changes in one commit
git commit -m "feat(models): add union type support
- Add oneOf schema handling
- Generate Freezed union types
- Support discriminator property
Closes #123"core: Core generation logiccli: CLI tool changesmodels: Model generationservices: Service generationparser: OpenAPI parsertemplates: Template changesdeps: Dependencies
This project reuses the OpenAPI parsing infrastructure from Orval while implementing Dart-specific code generation:
- Parse: OpenAPI spec → normalized schema (using @apidevtools/swagger-parser)
- Transform: Schema → Dart AST representation
- Generate: AST → Dart code using templates
- Format: Run
dart formaton generated files
- OpenAPI 3.0 and Swagger 2.0 support - Full spec parsing and validation
- Dio HTTP client generation - Complete with ApiClient wrapper
- Freezed model generation - Immutable models with JSON serialization
- Full null safety support - Leveraging Dart's sound null safety
- Complex type handling - Nested objects, arrays, enums, oneOf/discriminators
- All parameter types - Path, query, header, and body parameters
- All HTTP methods - GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
- Reference resolution - Complete
$refand component resolution - Smart header consolidation - Automatic deduplication of header classes
- Query parameter flattening - Complex objects serialized to query strings
- Inline object extraction - Generates proper classes for inline schemas
- Custom file naming -
.f.dartextension for Freezed models - Automated CI/CD - GitHub Actions with semantic-release
- Dual package support - Both CommonJS and ES Modules
- Comprehensive test suite - 68 tests with 100% pass rate
- Watch mode - Auto-regeneration on spec changes
- Additional HTTP clients - Retrofit, Chopper, HTTP package support
- Mock data generation - Test fixtures from schemas
- Performance optimization - For large OpenAPI specs
- State management integrations - Riverpod, GetX, Bloc
- GraphQL support - Generate GraphQL clients
- Custom interceptors - User-defined request/response handlers
- Interactive CLI wizard - Guided configuration setup
- VS Code extension - IDE integration
- API documentation generation - From OpenAPI descriptions
The generated ApiClient class provides:
- Automatic base URL configuration from OpenAPI spec
- Built-in error handling with status code interceptors
- Debug logging enabled via environment variable
- Request/response timeout configuration
- Default headers management
- Custom interceptors support
- Automatic retry logic for failed requests (configurable)
Each service class includes:
- Type-safe methods for all endpoints
- Automatic parameter handling (path, query, header, body)
- Proper null safety with nullable parameters
- Exception handling with custom
ApiException - Response type mapping to generated models
- Request cancellation support via
CancelToken
Generated with Freezed for:
- Immutability by default
- JSON serialization/deserialization
- CopyWith methods for easy updates
- Equality operators and hashCode
- toString implementations
- Union types for oneOf/discriminated unions
- Nested object support
Separate parameter classes for:
- Query parameters with automatic null filtering
- Header parameters with consolidation support
- Type-safe parameter passing
- Optional vs required distinction
lib/api/
├── api_client.dart # Dio client wrapper with configuration
├── models/ # Data models
│ ├── pet.f.dart # Freezed model definition
│ ├── pet.f.freezed.dart # Generated Freezed code
│ ├── pet.f.g.dart # Generated JSON serialization
│ ├── params/ # Parameter classes
│ │ ├── list_pets_params.f.dart
│ │ └── index.dart
│ ├── headers/ # Header classes (if custom headers)
│ │ ├── api_key_header.f.dart
│ │ └── index.dart
│ └── index.dart # Barrel export file
└── services/ # API services
├── pets_service.dart # Service with typed methods
├── api_exception.dart # Custom exception handling
└── index.dart # Barrel export file
export default {
apiName: { // Name your API (can have multiple APIs in one config)
input: './openapi.yaml', // Path to OpenAPI spec file or URL
output: {
target: './lib/api', // Output directory path
mode: 'split', // File organization mode
// Options: 'single' | 'split' | 'tags'
// - 'single': All code in one file
// - 'split': Separate files for models and services (default)
// - 'tags': Group by OpenAPI tags
client: 'dio', // HTTP client library (currently only 'dio' is supported)
// Future options planned: 'http' | 'chopper' | 'retrofit'
override: {
generator: {
freezed: true, // Use Freezed for immutable models
jsonSerializable: true, // Add JSON serialization
nullSafety: true, // Enable null safety (default: true)
partFiles: true, // Generate part files (default: true)
equatable: false // Add Equatable support (default: false)
},
methodNaming: 'methodPath', // Method naming strategy
// Options: 'operationId' | 'methodPath'
// - 'operationId': Use OpenAPI operationId
// - 'methodPath': Generate from HTTP method + path (e.g., getUsersById)
dio: {
baseUrl: 'https://api.example.com', // Override base URL
interceptors: ['AuthInterceptor'] // Custom interceptors
},
// Custom header consolidation (new feature!)
headers: {
// Define reusable header classes
definitions: {
ApiKeyHeader: {
fields: ['x-api-key'],
required: ['x-api-key'],
description: 'API key authentication'
},
CompanyHeaders: {
fields: ['x-api-key', 'x-company-id', 'x-user-id'],
required: ['x-api-key', 'x-company-id'], // x-user-id is optional
description: 'Company context headers'
}
},
// Optional: Map specific endpoints to header classes
mapping: {
'/v1/health/*': 'ApiKeyHeader',
'/v1/companies/**': 'CompanyHeaders'
},
// Enable smart header matching
customMatch: true, // Auto-match endpoints to header definitions
matchStrategy: 'exact', // 'exact' | 'subset' | 'fuzzy'
customConsolidate: true, // Auto-create shared classes for common patterns
consolidationThreshold: 3 // Min endpoints to trigger consolidation
}
}
},
hooks: {
afterAllFilesWrite: 'dart format .' // Commands to run after generation
}
}
}export default {
petstore: {
input: './petstore.yaml',
output: {
target: './lib/api'
}
}
}export default {
myApi: {
input: './tests/openapi.json',
output: {
target: './generated-api',
mode: 'split',
client: 'dio',
override: {
generator: {
freezed: true,
jsonSerializable: true,
nullSafety: true
},
methodNaming: 'methodPath' // getUsersById instead of getUserById
}
},
hooks: {
afterAllFilesWrite: [
'dart format ./generated-api',
'flutter pub run build_runner build'
]
}
}
}Dorval can intelligently consolidate duplicate header classes across your API:
export default {
api: {
input: './openapi.json',
output: {
target: './lib/api',
override: {
headers: {
// Define common header patterns
definitions: {
BasicAuth: {
fields: ['x-api-key'],
required: ['x-api-key']
},
UserContext: {
fields: ['x-api-key', 'x-user-id', 'x-session-id'],
required: ['x-api-key', 'x-user-id'] // x-session-id is optional
},
AdminContext: {
fields: ['x-api-key', 'x-admin-id', 'x-permission-level'],
required: ['x-api-key', 'x-admin-id', 'x-permission-level']
}
},
// Enable automatic matching
customMatch: true,
matchStrategy: 'exact', // Matches must have exact same fields and required status
// Auto-consolidate common patterns
customConsolidate: true,
consolidationThreshold: 3 // Create shared class if 3+ endpoints use same headers
}
}
}
}
}Benefits:
- Reduces duplication: Instead of 85 header classes, you might only need 5-10
- Order-independent: Headers with same fields but different order are recognized as identical
- Required-aware: Distinguishes between required and optional fields
- Smart naming: Auto-generates meaningful names for consolidated classes
Match Strategies:
exact: Fields and required status must match exactly (recommended)subset: Endpoint headers can be a subset of the definitionfuzzy: Best-effort matching using similarity scoring
Sample projects are coming soon. For now, refer to the configuration examples above.
# Generate from OpenAPI spec
dorval generate [options]
Options:
-c, --config <path> Path to config file (orval.config.ts)
-i, --input <path> OpenAPI spec file or URL
-o, --output <path> Output directory
--client <type> Client type (currently only 'dio')
--help Display help
# Watch mode (coming soon)
# dorval watch [options]-
"Cannot find module '@dorval/core'" error
- Ensure you've installed dorval:
npm install -g dorval - Or use npx:
npx dorval generate -i spec.yaml -o ./lib
- Ensure you've installed dorval:
-
Generated methods return correct model types
- ✅ Fixed: Response types are now properly mapped to generated models
- Models are generated with proper Freezed annotations
-
Import errors in generated Dart code
- Run
flutter pub getafter generation - Run
flutter pub run build_runner build --delete-conflicting-outputs - Ensure all dependencies are in
pubspec.yaml
- Run
-
Duplicate header classes
- ✅ Fixed: Smart header consolidation automatically reduces duplicates
- Configure consolidation threshold in config file if needed
- ✅ Comprehensive test suite - 68 tests, 100% passing
- ✅ CI/CD pipeline - GitHub Actions with automated testing
- ✅ Semantic release - Automated versioning and npm publishing
- ✅ Smart header consolidation - Reduces duplicate header classes
- ✅ Proper response type mapping - Models correctly typed
- ✅ OneOf with discriminator - Generates proper Freezed union types
- ✅ Inline object handling - Generates nested classes instead of Map
- ✅ Query parameter flattening - Complex objects properly serialized
- Expand test coverage
- Unit tests for generators (✅ Done)
- Integration tests (✅ Done)
- More E2E tests with real-world OpenAPI specs
- Performance benchmarks
- Test coverage badges
-
Custom client support
- Allow users to provide custom HTTP client implementations
- Support for custom request/response interceptors
- Custom error handling strategies
- Request retry logic configuration
-
Additional HTTP clients
- Complete Retrofit implementation
- Chopper client support
- Built-in HTTP package support
- GraphQL client generation
-
Enhanced type handling
- Better support for oneOf/anyOf/allOf
- Discriminated unions with proper type checking
- Recursive type definitions
- Better handling of nullable types
-
Configuration improvements
- Support for .dorvalrc configuration files
- Environment-specific configurations
- Global vs project-specific settings
- Configuration validation and error messages
-
CLI enhancements
- Interactive configuration wizard
- Watch mode with hot reload
- Diff view for regenerated files
- Dry-run mode to preview changes
- Progress indicators for large specs
-
IDE integrations
- VS Code extension
- IntelliJ/Android Studio plugin
- Code completion for generated APIs
- Inline documentation from OpenAPI descriptions
-
State management integrations
- Riverpod providers generation
- Bloc/Cubit pattern support
- GetX controllers generation
- Provider package integration
-
Testing utilities
- Mock server generation
- Fake data generators from schemas
- Test fixtures from OpenAPI examples
- Integration with mockito
-
Code optimization
- Tree-shaking unused models
- Lazy loading for large APIs
- Code splitting by feature/module
- Minification options
-
Documentation
- Generate API documentation website
- Dartdoc comments from OpenAPI descriptions
- Usage examples generation
- Postman collection export
-
Plugin system
- Custom template support
- Hook system for pre/post generation
- Custom validators
- Community plugin marketplace
-
Multi-language support
- Generate both Dart and TypeScript from same spec
- Shared model definitions
- Cross-platform type safety
- Large file handling - Performance issues with very large OpenAPI specs (>10MB)
- Error messages - Improve error messages for invalid OpenAPI specs
- Windows compatibility - Path handling issues on Windows
- Watch mode - Not yet implemented
- Multiple client support - Currently only Dio is supported
- Generate 1000+ endpoints in < 5 seconds
- Support OpenAPI specs up to 50MB
- Memory usage < 500MB for large specs
- Incremental generation for faster updates
- Refactor parser to use visitor pattern
- Improve template organization
- Add proper logging system
- Implement caching for parsed specs
- Better error recovery during generation
- Standardize naming conventions across codebase
- Complete API reference documentation
- Video tutorials for common use cases
- Migration guide from other generators
- Best practices guide
- Troubleshooting guide
- Performance optimization guide
We welcome contributions! See CONTRIBUTING.md for guidelines.
Want to help? Check the roadmap above and pick a task! We welcome contributions of all sizes.
MIT © 2025
Built on top of the excellent Orval project.