Converters
Turn a raw environment string into a number, URL, duration, list, or any type you need, with a built-in token or your own function.
process.env.CACHE_TTL is the string 15m. Turning it into milliseconds took a parser you wrote yourself. A flag and a comma-separated list each took one too. A converter does that step. Pass one to getAs, or set it as the converter option on the @Envapt decorator, to make the read return the converted type. Without a converter, Envapter.get returns the string as it is.
const timeout = .('REQUEST_TIMEOUT', ., '30s');
const adminEmails = .('ADMIN_EMAILS', () => new (.(',')));The third argument is the fallback, which the read returns when the variable is unset or its value can't convert. An array read throws on a bad element. timeout is a number of milliseconds because Converters.Time converts 30s. The fallback removes undefined from the type. adminEmails is a Set built by a function you wrote. It has no fallback, so it can be undefined. On Workers, the browser, and edge runtimes, every read needs a bound source first.
Built-in tokens
A token is one of the built-in converters on the Converters object. Each token in the table is a plain string underneath (Converters.Port is 'port'). A mistyped string throws InvalidBuiltInConverter (202) with the list of valid tokens. Use Converters, which gives you autocomplete and catches the typo before you run.
| Token | Returns | Accepts |
|---|---|---|
Converters.String | string | any value, unchanged |
Converters.Number | number | what Number(raw) reads. NaN can't convert |
Converters.Integer | number | a safe integer |
Converters.Float | number | what Number(raw) reads, except a whitespace-only value. NaN can't convert |
Converters.Boolean | boolean | 1/yes/true/on and 0/no/false/off, in any case |
Converters.BigInt | bigint | what BigInt(raw) reads |
Converters.Json | JsonValue | what JSON.parse(raw) reads |
Converters.Url | URL | an absolute URL |
Converters.RegExp | RegExp | /pattern/flags, or a whole string used as the pattern |
Converters.Date | Date | a millisecond timestamp or a UTC ISO 8601 string |
Converters.Time | number | a duration like 30s, returned as milliseconds |
Converters.Port | number | an integer from 0 to 65535 |
Converters.Email | string | an address matching the WHATWG email pattern, so user@localhost passes |
const port = .('PORT', ., 3000);const apiUrl = .('API_URL', .);port has a fallback, so its type is number. apiUrl has none, so it's URL | undefined. The fallback has to be a valid value for its token, such as a real email, an in-range port, or a non-NaN number. An invalid one throws FallbackConverterTypeMismatch (104) on every read of that key, whether the variable is set or not.
Number, Integer, and Float
Number reads whatever JavaScript's Number() reads, including decimals, 0x hex, and scientific notation. Integer accepts the same forms but requires a safe integer, so 3.9 and values larger than 2^53 - 1 can't convert. Float matches Number except for a whitespace-only value. Number reads it as 0, while Float, Integer, and Port can't convert it, so pick Float for a decimal that a blank value must not turn into 0. Read a count, a retry limit, or a pool size with Integer, because Number accepts 3.9.
// MAX_CONNECTIONS=100
const = .('MAX_CONNECTIONS', .); // 100
// RETRIES="8abc" is not a clean integer, so the read returns the fallback
const = .('RETRIES', ., 3); // 3
const = .('SAMPLE_RATE', ., 1.0);JSON
Json runs JSON.parse. The return type is JsonValue, the recursive union of every JSON-representable value, so every read needs narrowing before use. null is valid JSON, so FEATURE_FLAGS=null returns null, and a required read returns it too. To type the result yourself, pass the type as a generic. A schema is the other route, and it also checks the shape.
interface FeatureFlags {
?: boolean;
}
// FEATURE_FLAGS={"beta":true}
const raw = .('FEATURE_FLAGS', ., {});
const flags = .<FeatureFlags>('FEATURE_FLAGS', ., {});flags is typed from your FeatureFlags interface, though envapt doesn't check that the JSON matches it. Without the {} fallback, the type is FeatureFlags | undefined, because an unset variable returns undefined.
URL
Url builds a URL, which needs an absolute value. A relative path makes the constructor throw, but the read catches that and returns the fallback.
RegExp
RegExp reads a /pattern/flags literal with flags from g i m s u v y. A value without the surrounding slashes becomes the whole pattern, with no flags.
Date
Date accepts two forms. Any other string can't convert.
// RELEASED_AT=2024-01-15T10:30:00Z
const released = .('RELEASED_AT', .);
// TRIAL_ENDS_AT=1705314600000
const = .('TRIAL_ENDS_AT', .);RELEASED_AT uses the UTC ISO 8601 form, which must end in Z and may carry .mmm milliseconds. TRIAL_ENDS_AT uses the other form, an all-digit millisecond timestamp. A date-only string, a local time, and an offset like +02:00 can't convert, so store dates in UTC.
Time and durations
Time reads a duration and returns milliseconds. The raw value is a number with an optional unit. Without a unit, the number is milliseconds. The units are ms, s, m (minutes), h, d, and w (weeks). Decimals work too, as in 1.5h.
// CACHE_TTL=15m
const = .('CACHE_TTL', .); // 900000
const grace = .('GRACE_PERIOD', ., '1.5h');const = .('RETRY_DELAY', ., 5000);The fallback is a number in milliseconds, like 5000 in retryDelay, or a time string with a unit, like '1.5h'. The exported TimeFallback type accepts both. A time string without a unit, such as '90', throws MalformedTimeFallback (105), though CACHE_TTL=90 in the variable reads as 90 milliseconds. Pass the number 90 for that.
When a value can't convert
If a token can't convert the value, the read returns the fallback, or undefined without one. If a custom function returns undefined or null, the read returns the fallback too. Neither case throws. Set Envapter.debug = 'verbose' to log which key fell back, as Errors describes.
// PORT=80a
const = .('PORT', ., 3000); // 3000PORT=80a isn't a port, yet the app starts on 3000. That suits a value the app can run without. For a value the app can't run without, a required read throws and reports the key.
Some failures throw whether strict mode is on or off. Among them are a fallback that doesn't fit its token (104), a mistyped token string (202), and a failing array element (206). An error thrown inside your own converter function propagates out of getAs unchanged. A required read reports it as an issue on its error.
Arrays
Without Converters.array you split the value, trim each piece, and skip the empty ones by hand. Converters.array splits the value on a delimiter and converts each element. Both options have defaults, so Converters.array() splits on commas and returns string[].
Prop
Type
// FEATURE_TAGS=beta, checkout, dark-mode
const tags = .('FEATURE_TAGS', .());
// PLUGIN_DIRS=/opt/app/plugins;/usr/local/share/app/plugins
const = .('PLUGIN_DIRS', .({ : ';' }));
// CORS_ORIGINS=https://a.example.com,https://b.example.com
const origins = .('CORS_ORIGINS', .({ : . }));
// WORKER_PORTS=3000 3001 3002
const ports = .('WORKER_PORTS', .({ : ., : ' ' }));With strict mode off, envapt trims every element and skips the empty ones, so FEATURE_TAGS above yields three tags even with spaces after the commas. A value of only separators or spaces returns [], and the fallback stays unused.
An array fallback must be an array. If it isn't one, the read throws InvalidFallback (101). If its elements have the wrong type, it throws ArrayFallbackElementTypeMismatch (103). With of: Converters.Time, the elements must be all time strings with units or all numbers of milliseconds.
The of option also takes a function, for an element shape the tokens can't produce:
const = (: string) => {
const [, ] = .(':');
return { , : () };
};
// UPSTREAMS=10.0.0.1:8080,10.0.0.2:8080
const upstreams = .('UPSTREAMS', .({ : }));toUpstream gets one trimmed, non-empty element at a time.
An array read throws when an element fails, even if you passed a fallback. An element that fails its token throws ArrayElementConversionFailed (206). If your function returns undefined or null for an element, that throws the same code. Under strict mode, a trailing comma in a deployed .env throws EmptyArrayElement (207) at read time, which can stop startup. A required read reports each of these as an issue on one InvalidEnvValue.
Custom converters
When no token fits, pass a (raw: string) => T function to getAs. envapt calls it only when the variable is set, with ${VAR} templates already expanded. A missing variable returns the fallback, or undefined without one.
const = (: string) => {
const = /^(\d+)\.(\d+)\.(\d+)$/.();
if (!) return ;
return { : ([1]), : ([2]), : ([3]) };
};
// APP_VERSION=2.4.1
const version = .('APP_VERSION', );For a value like 2.4, toSemver returns undefined. This read passes no fallback, so version is undefined. Returning null works the same way. The exported ConverterFunction<T> type describes such a function, for a converter you keep in its own file.
A function that checks a rule, like a minimum version, returns undefined without saying which rule failed. A schema reports the failed rule.
Reading values
Read one environment variable as a string or through a converter, with a fallback, and see what each reader returns for a bad value.
Validating with a schema
Check an environment value against a zod, valibot, or arktype schema and get the schema's output type back, with the failed rule in the error's issues.