configuration
Complete reference for every duck-gen.json configuration option with examples and explanations.
Overview
Duck Gen reads its configuration from a duck-gen.json file in your project root. This file
is required. The CLI fails if it cannot find or parse it.
All paths inside the config are resolved relative to the config file location.
Add the $schema field to your config file to get autocomplete, validation, and inline
documentation in VS Code and other editors that support JSON Schema.
Minimal config
The smallest working configuration for a NestJS project:
{
"$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json",
"framework": "nestjs",
"extensions": {
"shared": {
"includeNodeModules": false,
"outputSource": "./generated",
"sourceGlobs": ["src/**/*.ts"],
"tsconfigPath": "./tsconfig.json"
},
"apiRoutes": {
"enabled": true,
"globalPrefix": "/api",
"normalizeAnyToUnknown": true,
"outputSource": "./generated"
},
"messages": {
"enabled": true,
"outputSource": "./generated"
}
}
}JSON Schema
Add the $schema field to get autocomplete and validation in VS Code and other editors
that support JSON Schema:
{
"$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json"
}This points to the schema file bundled with the @gentleduck/gen package.
Root options
framework
{ "framework": "nestjs" }| Property | Value |
|---|---|
| Type | string |
| Required | Yes |
| Accepted values | "nestjs" |
The target framework adapter to use. Currently only nestjs is supported. Other values
fail config validation. More adapters are planned.
Shared options
The extensions.shared section configures settings that apply to both the API routes
and messages extensions.
Settings in extensions.shared affect all extensions. If you set outputSource here,
both API route types and message types are written to that location. Use the
per-extension outputSource when you need different paths for each.
tsconfigPath
{
"extensions": {
"shared": {
"tsconfigPath": "./tsconfig.json"
}
}
}| Property | Value |
|---|---|
| Type | string |
| Required | Yes (for NestJS) |
| Default | None |
Path to your tsconfig.json file. Duck Gen uses ts-morph to create
an in-memory TypeScript project from this config. The include and exclude globs in your
tsconfig.json determine which files get scanned.
Make sure your tsconfig includes the source files you want scanned. If a controller file is excluded by your tsconfig, Duck Gen won't see it.
includeNodeModules
{
"extensions": {
"shared": {
"includeNodeModules": false
}
}
}| Property | Value |
|---|---|
| Type | boolean |
| Required | Yes |
| Recommended | false |
Whether to include node_modules files when scanning. Set to false unless you have
a specific reason to scan third-party code (e.g. scanning a local workspace package).
Setting includeNodeModules: true significantly increases scanning time because Duck Gen
will parse every TypeScript file in your node_modules. Only enable this if you need to
scan controllers in a linked workspace package.
outputSource
{
"extensions": {
"shared": {
"outputSource": "./generated"
}
}
}| Property | Value |
|---|---|
| Type | string or string[] |
| Required | No |
| Default | None |
Additional output directory or directories for all generated files (both API routes
and messages). Duck Gen always writes to its internal generated folder inside the package.
This option adds extra output locations.
Single directory:
{ "outputSource": "./generated" }Multiple directories:
{ "outputSource": ["./generated", "../shared-types/generated"] }When a directory path is given (no file extension), Duck Gen creates files with default
names inside that directory. When a file path is given (with .d.ts extension), Duck Gen
writes directly to that file.
sourceGlobs
{
"extensions": {
"shared": {
"sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"]
}
}
}| Property | Value |
|---|---|
| Type | string[] |
| Required | No |
| Status | Reserved, present in the schema but not applied by the current NestJS adapter |
The NestJS adapter uses your tsconfig.json include/exclude globs instead. This field
is reserved for future adapters that may not use a tsconfig.
API Routes options
The extensions.apiRoutes section configures the API route type generator.
enabled
{
"extensions": {
"apiRoutes": {
"enabled": true
}
}
}| Property | Value |
|---|---|
| Type | boolean |
| Required | Yes |
Enable or disable API route generation. Set to false if you only want message types.
globalPrefix
{
"extensions": {
"apiRoutes": {
"globalPrefix": "/api"
}
}
}| Property | Value |
|---|---|
| Type | string |
| Required | No |
| Default | None (empty prefix) |
A path prefix applied to all generated routes. This should match the global prefix you set in your NestJS application:
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix('api') // match this in duck-gen.json as "/api"Without a prefix, routes start directly from the controller path:
- With
globalPrefix: "/api":/api/users/:id - Without:
/users/:id
If your NestJS app uses app.setGlobalPrefix('api') but your config says
globalPrefix: "/v1", the generated route paths won't match the actual server paths.
Always keep these aligned.
normalizeAnyToUnknown
{
"extensions": {
"apiRoutes": {
"normalizeAnyToUnknown": true
}
}
}| Property | Value |
|---|---|
| Type | boolean |
| Required | Yes |
| Recommended | true |
When enabled:
- Controller methods with
anyreturn types are converted tounknownin generated types. - A warning is printed for each method with an
anyreturn type.
This prevents any from silently leaking into your client types. Set to false only
if you intentionally want any to pass through.
outputSource (apiRoutes)
{
"extensions": {
"apiRoutes": {
"outputSource": "./generated"
}
}
}| Property | Value |
|---|---|
| Type | string or string[] |
| Required | No |
Additional output locations for API route types specifically. Works the same as
shared.outputSource but only applies to the API routes file.
This is additive with shared.outputSource. Both locations receive the generated file.
Messages options
The extensions.messages section configures the message registry type generator.
enabled
{
"extensions": {
"messages": {
"enabled": true
}
}
}| Property | Value |
|---|---|
| Type | boolean |
| Required | Yes |
Enable or disable message generation. Set to false if you only want API route types.
outputSource (messages)
{
"extensions": {
"messages": {
"outputSource": "./generated"
}
}
}| Property | Value |
|---|---|
| Type | string or string[] |
| Required | No |
Additional output locations for message types specifically. Works the same as the API
routes outputSource.
Complete example
Here is a fully configured duck-gen.json with every option set:
{
"$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json",
"framework": "nestjs",
"extensions": {
"shared": {
"includeNodeModules": false,
"outputSource": ["./generated", "../client/generated"],
"sourceGlobs": ["src/**/*.ts"],
"tsconfigPath": "./tsconfig.json"
},
"apiRoutes": {
"enabled": true,
"globalPrefix": "/api",
"normalizeAnyToUnknown": true,
"outputSource": "./generated"
},
"messages": {
"enabled": true,
"outputSource": "./generated"
}
}
}With this config:
-
API route types are written to:
node_modules/@gentleduck/gen/generated/nestjs/duck-gen-api-routes.d.ts(always)./generated/duck-gen-api-routes.d.ts(fromshared.outputSource)../client/generated/duck-gen-api-routes.d.ts(fromshared.outputSource)./generated/duck-gen-api-routes.d.ts(fromapiRoutes.outputSource, deduped)
-
Message types are written to the same locations but as
duck-gen-messages.d.ts.
Configuration examples
API routes only
Use this when your project doesn't use i18n or message keys:
{
"$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json",
"framework": "nestjs",
"extensions": {
"shared": {
"includeNodeModules": false,
"sourceGlobs": ["src/**/*.ts"],
"tsconfigPath": "./tsconfig.json"
},
"apiRoutes": {
"enabled": true,
"globalPrefix": "/api",
"normalizeAnyToUnknown": true
},
"messages": {
"enabled": false
}
}
}Messages only
Use this when you only need i18n type safety and handle routes differently:
{
"$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json",
"framework": "nestjs",
"extensions": {
"shared": {
"includeNodeModules": false,
"sourceGlobs": ["src/**/*.ts"],
"tsconfigPath": "./tsconfig.json"
},
"apiRoutes": {
"enabled": false
},
"messages": {
"enabled": true,
"outputSource": "./generated"
}
}
}Monorepo with shared output
When your server and client live in different packages:
{
"$schema": "../../node_modules/@gentleduck/gen/duck-gen.schema.json",
"framework": "nestjs",
"extensions": {
"shared": {
"includeNodeModules": false,
"outputSource": ["./generated", "../../packages/shared-types/generated"],
"sourceGlobs": ["src/**/*.ts"],
"tsconfigPath": "./tsconfig.json"
},
"apiRoutes": {
"enabled": true,
"globalPrefix": "/api",
"normalizeAnyToUnknown": true
},
"messages": {
"enabled": true
}
}
}Then in your client package:
import type { ApiRoutes } from '../../packages/shared-types/generated/duck-gen-api-routes'When using a shared output directory, add the generated files to your .gitignore and
regenerate them in CI. This avoids merge conflicts and ensures types are always fresh.
Next steps
- API Routes guide: how route scanning works.
- Messages guide: how message scanning works.
- Generated types: every exported type explained.