Decorators
Bind a class property to an environment variable with the @Envapt decorator, the class-based alternative to the Envapter readers.
@Envapt binds a class property to an environment variable. It's the class-based counterpart to the Envapter readers: the same parsing, converters, and cache, declared as typed fields instead of method calls.
class {
@('PORT', { : ., : 3000 })
declare static readonly : number;
@('DEBUG', { : ., : false })
declare static readonly : boolean;
}
.port;The value is read and converted once, on first access, then cached.
Declare the property, never initialize it
Always declare a decorated property with declare and no initializer.
@Envapt installs a getter on the class with Object.defineProperty. Under useDefineForClassFields (on by default for modern targets), a real field declaration emits an assignment in the constructor that overwrites that getter with undefined. The declare keyword makes the property type-only, so it emits nothing and the getter survives.
A decorated property with an initializer or without declare reads back as undefined at runtime: the emitted
field clobbers envapt's getter. Write declare readonly x: T, never readonly x: T = ....
You also need experimentalDecorators enabled.
{
"compilerOptions": {
"experimentalDecorators": true
}
}Runtime-specific decorator notes (Bun running a .ts entry directly, Deno's flag) live on Compatibility.
Converters
The converter option takes the same values as getUsing/getWith: a built-in token, a primitive constructor, a custom function, or Converters.array(...). A fallback removes undefined from the value and must match the converter's type.
class {
// duration token: a time-string fallback is coerced to milliseconds
@('CACHE_TTL', { : ., : '15m' })
declare static readonly : number;
// array token
@('ALLOWED_ORIGINS', { : .({ : . }), : [] })
declare static readonly : string[];
// custom function: receives the raw value (string, or undefined when unset)
@('FEATURE_FLAGS', { : () => ( ? .(',') : []) })
declare static readonly : string[];
}With no converter and no fallback, the property resolves to the raw string or null.
Shorthand decorators
@EnvNum, @EnvStr, @EnvBool, @EnvUrl, and @EnvTime are shorthands for @Envapt with a fixed converter. The call site is the key and an optional fallback.
class {
@('PORT', 3000)
declare static readonly : number;
@('AWS_REGION', 'us-east-1')
declare static readonly : string;
@('DEBUG', false)
declare static readonly : boolean;
@('APP_URL', new ('http://localhost:3000'))
declare static readonly : URL;
@('CACHE_TTL', '15m')
declare static readonly : number;
}The second argument is the fallback, typed to the converter: @EnvUrl takes a URL, @EnvTime takes a millisecond number or a time string, the rest take their primitive. Omit it and the property resolves to the converted value or null. The key can also be an ordered list.
These install the same getter as @Envapt, so the declare rule applies. They take a fallback only: for required, a schema, an array, or a custom function, use @Envapt with the options object.
Required values
Pass required: true to throw EnvaptError on a missing or empty value instead of returning a fallback. It's mutually exclusive with fallback.
class {
@('DATABASE_URL', { : ., : true })
declare static readonly : URL;
}The throw happens on first access to the property, independent of global strict mode. See Strict mode for the global switch and how required relates to it.
First access can be deep in a request, not at boot. A missing required value then surfaces as a runtime error mid-request. Read the property once during startup to fail early.
Validation with a schema
Pass a schema to validate through zod, valibot, arktype, or a hand-rolled StandardSchemaV1. The property type is the schema's output.
class {
@('PORT', { : ..().().(1024).(65535) })
declare static readonly : number;
}schema and converter are mutually exclusive. See Standard Schema for the full behavior.
Ordered keys
Like the functional readers, the key can be an ordered list. The first key with a value wins.
class {
// CANARY_URL if set, otherwise APP_URL
@(['CANARY_URL', 'APP_URL'], { : . })
declare static readonly : URL | null;
}Static and instance properties
@Envapt works on both static and instance properties, and the class does not need to extend anything. The cache is keyed per ClassName.property, so a static and an instance property of the same name never collide.
Extend Envapter when you also want the reader methods on the same class:
class extends {
@('PORT', { : ., : 3000 })
declare readonly : number;
// mix decorator fields with reader calls
readonly = this.('AWS_REGION', 'us-east-1');
}
const = new ();
.port;A misconfigured options object throws EnvaptError as the decorator is applied, before any property access: required together with a fallback, or a non-StandardSchemaV1 value passed as schema. See Errors for the codes.