Migrating from 1.x to 2.0
TRAPI 2.0 replaces the 1.x decorator configuration system (DecoratorConfig[] + DecoratorID enum) with a v2 preset system: declarative handlers that mutate drafts, with explicit support for marker-based discovery, extends / replaces, and JSDoc tag handlers.
If your application only consumes the high-level entry points and a bundled preset (@trapi/preset-decorators-express or @trapi/preset-typescript-rest), the migration is small. If you author custom decorator mappings or reach into 1.x internals (DecoratorResolver, DecoratorConfig, ...), there are concrete code changes to make.
@trapi/decorators was removed
The 1.x reference package @trapi/decorators no longer exists. Its handlers were inlined into each framework preset (@trapi/preset-decorators-express, @trapi/preset-typescript-rest) so every preset is now self-contained. If you depended on @trapi/decorators directly, switch to one of the framework presets — most user code didn't import from it directly. See the examples/decorators bundle for a worked decorator+preset reference.
At a glance
| Area | 1.x | 2.0 |
|---|---|---|
| Inline decorator mapping | decorators: DecoratorConfig[] on MetadataGenerateOptions | Removed — author a Preset and pass preset |
| Preset shape | PresetSchema = { extends, items: DecoratorConfig[] } | Preset = { name, controllers, methods, parameters, controllerJsDoc, methodJsDoc, parameterJsDoc, extends? } |
| Decorator identity | DecoratorID enum (DecoratorID.CONTROLLER, DecoratorID.GET, ...) | Removed — handlers match by name string with optional on target filter |
| Argument mapping | properties: { value: { index: 0 } } config | apply(ctx, draft) — read via ctx.argument(i) |
| Resolver-side concepts | Hardcoded names in the resolver | marker: ResolverMarker on each handler |
| Reference decorator package | @trapi/decorators (preset + runtime stubs) | Removed — handlers inlined into each framework preset; see examples/decorators for a worked reference |
Version.V3 | openapi: '3.1.0' | openapi: '3.0.0' (use Version.V3_1 for the 3.1.0 default) |
File output from generateSwagger() | output: false / outputDirectory / yaml options | Removed — call saveSwagger(spec, ...) separately |
What you may need to change
1. The decorators option is gone
// 1.x
import { DecoratorID, generateMetadata } from '@trapi/metadata';
await generateMetadata({
entryPoint: 'src/**/*.ts',
decorators: [
{ id: DecoratorID.CONTROLLER, name: 'Route', properties: { value: {} } },
{ id: DecoratorID.GET, name: 'HttpGet' },
],
});In 2.0, custom mappings are authored as a Preset and passed through preset. For inline use, you can resolve a local module that exports a Preset:
// 2.0 — author a preset module
// my-preset.ts
import { type Preset, controller, method } from '@trapi/core';
const preset: Preset = {
name: 'my-app/preset',
controllers: [
controller({
match: { name: 'Route', on: 'class' },
apply: (ctx, draft) => {
const arg = ctx.argument(0);
if (arg?.kind === 'literal' && typeof arg.raw === 'string') {
draft.paths = [arg.raw];
} else if (arg?.kind === 'array' && Array.isArray(arg.raw)) {
draft.paths = arg.raw.filter((v): v is string => typeof v === 'string');
} else {
draft.paths = [''];
}
},
}),
],
methods: [
method({
match: { name: 'HttpGet', on: 'method' },
apply: (ctx, draft) => {
draft.verb = 'get';
const path = ctx.argument(0);
if (path?.kind === 'literal' && typeof path.raw === 'string') {
draft.path = path.raw;
}
},
}),
],
};
export default preset;// generate-metadata.ts
await generateMetadata({
entryPoint: 'src/**/*.ts',
preset: './my-preset.ts', // relative path resolves to the local module
});If you only need to extend a framework preset, declare it via extends:
const preset: Preset = {
name: 'my-app/preset',
extends: ['@trapi/preset-decorators-express'],
controllers: [
controller({
match: { name: 'Route', on: 'class' },
apply: (_ctx, draft) => { draft.paths = ['']; },
}),
],
};2. The DecoratorID enum is gone
If you imported DecoratorID (e.g. DecoratorID.CONTROLLER, DecoratorID.GET, ...) — these are removed. Handlers in 2.0 match by name string and an optional on: 'class' | 'method' | 'parameter' target.
// 1.x
{ id: DecoratorID.QUERY, name: 'Query', properties: { value: {} } }
// 2.0
parameter({
match: { name: 'Query', on: 'parameter' },
apply: (ctx, draft) => {
const name = ctx.argument(0);
if (name?.kind === 'literal' && typeof name.raw === 'string') {
draft.in = ParamKind.QueryProp;
draft.name = name.raw;
} else {
draft.in = ParamKind.Query;
}
},
})3. Resolver-side concepts use marker
In 1.x the resolver looked for @Hidden, @IsInt, @Extension(...) by hardcoded name. In 2.0 the resolver discovers them via the handler's marker field — preset authors can rename a decorator without breaking resolver behaviour.
controller({
match: { name: 'Skip', on: 'class' }, // renamed @Skip instead of @Hidden
apply: flag('hidden'),
marker: MarkerName.Hidden, // resolver still treats @Skip as the hidden concept
});
parameter({
match: { name: 'AsInteger', on: 'parameter' },
apply: (_ctx, draft) => {
draft.validators.isInt = { value: 'int' };
},
marker: { numeric: NumericKind.Int },
});Markers: 'hidden' | 'deprecated' | 'extension' | { numeric: 'int' | 'long' | 'float' | 'double' }. JSDoc handlers can carry markers too — see Custom Presets — Resolver Markers.
4. @trapi/decorators was removed
The 1.x reference package no longer exists. Its handlers were inlined into the two framework presets so each preset is self-contained:
// 1.x / early 2.0
import preset from '@trapi/decorators';
// preset.items: DecoratorConfig[] (1.x) or preset.controllers/methods/... (early 2.0)
// Current 2.x — install one of the framework presets and reference it by name
import preset from '@trapi/preset-decorators-express';
// preset.controllers, preset.methods, preset.parameters, controllerJsDoc, methodJsDoc, parameterJsDocIf you imported runtime decorators (@Controller, @Get, …) from @trapi/decorators, switch the import source to your routing library — @decorators/express for the Express preset, typescript-rest for the typescript-rest preset. If you imported TRAPI-specific markers (@Hidden, @Tags, @Description, @IsInt, …), define your own no-op stubs locally or copy from examples/decorators/src/decorators.ts.
5. Removed types and runtime symbols
| Removed | Replacement |
|---|---|
DecoratorID enum | Match by name string |
DecoratorConfig type | Author handlers (controller(...), method(...), parameter(...)) |
DecoratorPropertyConfig / DecoratorPropertyConfigInput | Read inside apply via ctx.argument(i) / ctx.typeArgument(i) |
DecoratorResolver class | loadRegistryByName(...) + the orchestrator |
DecoratorPropertyManager | n/a — drafts are mutated directly |
PresetSchema type | Preset |
decorators?: DecoratorConfig[] on MetadataGenerateOptions | Removed; use preset |
6. Version.V3 now emits OpenAPI 3.0.0
@trapi/swagger 1.x defaulted Version.V3 to OpenAPI 3.1.0. In 2.0, Version.V3 emits 3.0.0 — use Version.V3_1 if you need the 1.x default.
// 1.x
generate({ version: Version.V3, ... }); // produced openapi: '3.1.0'
// 2.0 — same OpenAPI version requires V3_1
generateSwagger({ version: Version.V3_1, ... });7. File writing is no longer baked into spec generation
// 1.x
await generate({
version: Version.V3,
options: { ... },
output: './docs/openapi.json',
outputDirectory: './docs',
yaml: false,
});
// 2.0
import { generateSwagger, saveSwagger } from '@trapi/swagger';
const spec = await generateSwagger({ version: Version.V3, metadata: { ... } });
await saveSwagger(spec, { cwd: './docs', name: 'openapi', format: 'json' });generateSwagger() no longer accepts output / outputDirectory / yaml. Call saveSwagger(spec, options) if you want a file on disk.
8. New: strict-mode warnings (opt-in)
generateMetadata({ strict: true }) warns about decorators that don't match any registered handler. Useful for catching typos (@Hiden instead of @Hidden) or unwired decorators in custom presets. Output goes to console.warn once at the end of generation. Pass strict: 'throw' to throw a GeneratorError instead — useful as a CI gate. For full control, pass an onUnmatchedDecorator(reports) callback to replace the default warn/throw behaviour.
9. New: loud failure on missing preset
If you call generateMetadata({ entryPoint }) without a preset and source files were scanned, generation now throws ConfigError(code = PRESET_MISSING) instead of silently returning an empty controller list. Most 1.x users pass preset already, but if you relied on the decorators option this is the signal to migrate.
Migration checklist
- [ ] Replace any
DecoratorID/DecoratorConfigimports — neither symbol exists in 2.0. - [ ] Replace inline
decorators: [...]with aPresetandpreset: '...'. - [ ] If you renamed
@Hidden/@Deprecated/@Extension/@IsIntetc. — addmarker:on those handlers. - [ ] If you used
Version.V3and emittedopenapi: '3.1.0', switch toVersion.V3_1. - [ ] If you relied on
output/outputDirectory/yamlfromgenerate(), replace with a separatesaveSwagger()call. - [ ] If you imported
@trapi/decoratorsdirectly, replace it with a framework preset (@trapi/preset-decorators-expressor@trapi/preset-typescript-rest) and update runtime decorator imports to come from your routing library. - [ ] Run
generateMetadataagainst your project — controllers should be discovered identically. If not,strict: truewill surface unmatched decorators.
See also
- Custom Presets — full reference for authoring a v2 preset
- Decorators & Presets — handler API, drafts,
into/append/flaghelpers - Configuration — current
MetadataGenerateOptionssurface - examples/decorators — runnable worked example of a custom decorator runtime + matching v2
Preset