Skip to main content

configuration

Complete reference for every duck-gen.json configuration option with examples and explanations.

Overview

Duck Gen reads config from a duck-gen.json file at the project root. The file is required — the CLI fails if it can't find or parse it.

All paths resolve relative to the config file.

Minimal config

The smallest working configuration for a NestJS project:

duck-gen.json
{
  "$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"
    }
  }
}
duck-gen.json
{
  "$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 $schema for autocomplete and validation:

{
  "$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json"
}
{
  "$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json"
}

The schema ships with the @gentleduck/gen package.

Root options

framework

{ "framework": "nestjs" }
{ "framework": "nestjs" }
PropertyValue
Typestring
RequiredYes
Accepted values"nestjs"

The target framework adapter. Only nestjs is supported today — other values fail validation. More adapters are planned.

Shared options

extensions.shared applies to both the API routes and messages extensions.

tsconfigPath

{
  "extensions": {
    "shared": {
      "tsconfigPath": "./tsconfig.json"
    }
  }
}
{
  "extensions": {
    "shared": {
      "tsconfigPath": "./tsconfig.json"
    }
  }
}
PropertyValue
Typestring
RequiredYes (for NestJS)
DefaultNone

Path to tsconfig.json. Duck Gen uses ts-morph to build an in-memory TypeScript project from this config. The include and exclude globs pick which files get scanned.

The tsconfig must include the files you want scanned. Anything excluded by tsconfig stays invisible to Duck Gen.

includeNodeModules

{
  "extensions": {
    "shared": {
      "includeNodeModules": false
    }
  }
}
{
  "extensions": {
    "shared": {
      "includeNodeModules": false
    }
  }
}
PropertyValue
Typeboolean
RequiredYes
Recommendedfalse

Include node_modules during scanning. Keep this false unless you need to scan third-party code (for example, a linked workspace package).

outputSource

{
  "extensions": {
    "shared": {
      "outputSource": "./generated"
    }
  }
}
{
  "extensions": {
    "shared": {
      "outputSource": "./generated"
    }
  }
}
PropertyValue
Typestring or string[]
RequiredNo
DefaultNone

Extra output 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 more locations.

Single directory:

{ "outputSource": "./generated" }
{ "outputSource": "./generated" }

Multiple directories:

{ "outputSource": ["./generated", "../shared-types/generated"] }
{ "outputSource": ["./generated", "../shared-types/generated"] }

A directory path (no extension) creates files with default names inside it. A file path (with .d.ts) writes directly to that file.

sourceGlobs

{
  "extensions": {
    "shared": {
      "sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"]
    }
  }
}
{
  "extensions": {
    "shared": {
      "sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"]
    }
  }
}
PropertyValue
Typestring[]
RequiredNo
StatusReserved, present in the schema but not applied by the current NestJS adapter

The NestJS adapter uses the tsconfig include/exclude globs instead. This field is reserved for future adapters that don't rely on a tsconfig.

API Routes options

extensions.apiRoutes configures the API route type generator.

enabled

{
  "extensions": {
    "apiRoutes": {
      "enabled": true
    }
  }
}
{
  "extensions": {
    "apiRoutes": {
      "enabled": true
    }
  }
}
PropertyValue
Typeboolean
RequiredYes

Turns API route generation on or off. Set false to generate only message types.

globalPrefix

{
  "extensions": {
    "apiRoutes": {
      "globalPrefix": "/api"
    }
  }
}
{
  "extensions": {
    "apiRoutes": {
      "globalPrefix": "/api"
    }
  }
}
PropertyValue
Typestring
RequiredNo
DefaultNone (empty prefix)

Prefix applied to all generated routes. Match the global prefix set in the NestJS app:

main.ts
const app = await NestFactory.create(AppModule)
app.setGlobalPrefix('api')  // match this in duck-gen.json as "/api"
main.ts
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

normalizeAnyToUnknown

{
  "extensions": {
    "apiRoutes": {
      "normalizeAnyToUnknown": true
    }
  }
}
{
  "extensions": {
    "apiRoutes": {
      "normalizeAnyToUnknown": true
    }
  }
}
PropertyValue
Typeboolean
RequiredYes
Recommendedtrue

When enabled:

  • any return types become unknown in generated output.
  • Each method with an any return type logs a warning.

This keeps any from leaking into client types. Set false only to let any through on purpose.

outputSource (apiRoutes)

{
  "extensions": {
    "apiRoutes": {
      "outputSource": "./generated"
    }
  }
}
{
  "extensions": {
    "apiRoutes": {
      "outputSource": "./generated"
    }
  }
}
PropertyValue
Typestring or string[]
RequiredNo

Extra output locations for API route types only. Same semantics as shared.outputSource, scoped to the routes file.

Adds to shared.outputSource — both sets of locations receive the file.

Messages options

extensions.messages configures the message registry type generator.

enabled

{
  "extensions": {
    "messages": {
      "enabled": true
    }
  }
}
{
  "extensions": {
    "messages": {
      "enabled": true
    }
  }
}
PropertyValue
Typeboolean
RequiredYes

Turns message generation on or off. Set false to generate only API route types.

outputSource (messages)

{
  "extensions": {
    "messages": {
      "outputSource": "./generated"
    }
  }
}
{
  "extensions": {
    "messages": {
      "outputSource": "./generated"
    }
  }
}
PropertyValue
Typestring or string[]
RequiredNo

Extra output locations for message types only. Same semantics as the API routes outputSource.

Complete example

A fully configured duck-gen.json with every option set:

duck-gen.json
{
  "$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"
    }
  }
}
duck-gen.json
{
  "$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:

    1. node_modules/@gentleduck/gen/generated/nestjs/duck-gen-api-routes.d.ts (always)
    2. ./generated/duck-gen-api-routes.d.ts (from shared.outputSource)
    3. ../client/generated/duck-gen-api-routes.d.ts (from shared.outputSource)
    4. ./generated/duck-gen-api-routes.d.ts (from apiRoutes.outputSource, deduped)
  • Message types are written to the same locations but as duck-gen-messages.d.ts.

Configuration examples

API routes only

For projects without i18n or message keys:

duck-gen.json
{
  "$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
    }
  }
}
duck-gen.json
{
  "$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

For projects that only need i18n type safety:

duck-gen.json
{
  "$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"
    }
  }
}
duck-gen.json
{
  "$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 server and client live in separate packages:

apps/server/duck-gen.json
{
  "$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
    }
  }
}
apps/server/duck-gen.json
{
  "$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 the client package:

import type { ApiRoutes } from '../../packages/shared-types/generated/duck-gen-api-routes'
import type { ApiRoutes } from '../../packages/shared-types/generated/duck-gen-api-routes'

Next steps