# Migration (v4 to v5)

Upgrade to envapt v5, where Converters.array() and envFileOptions replace the old names.

Most of v5 is renames that TypeScript reports once you upgrade. [Behavior changes to watch](#behavior-changes-to-watch) lists the changes that compile cleanly but run differently.

## Array converters

`Converters.Array` and the inline `{ delimiter, type }` object are replaced by the `Converters.array(...)` builder.

```ts
// v4
@Envapt('PORTS', { converter: { delimiter: ',', type: Converters.Number }, fallback: [] })

// v5
@Envapt('PORTS', { converter: Converters.array({ of: Converters.Number }), fallback: [] })
```

`of` defaults to `string`, and `delimiter` defaults to `,`. `of` also accepts a `(raw) => T` function for a typed element. `Converters` is now a plain object, so it isn't usable as a type anymore. Use `ConverterToken` in its place. See [Converters](/docs/converters).

## dotenvConfig is now envFileOptions

`Envapter.dotenvConfig` is renamed to `Envapter.envFileOptions` (the type `DotenvConfigOptions` becomes `EnvFileOptions`). Only `encoding` and `override` remain. `quiet`, `DOTENV_KEY`, `debug`, `path`, and `processEnv` are gone.

```ts
// v4
Envapter.dotenvConfig = { override: true, quiet: false, debug: true };

// v5
Envapter.envFileOptions = { override: true };
```

To choose which files load, use [`Envapter.envPaths`](/docs/env-files#replacing-the-cascade). envapt doesn't read `DOTENV_KEY` (dotenv-vault). Decrypt the file yourself, then pass its path to `envPaths`.

## Debug output

The `debug` option is now `Envapter.debug`. Output goes to stderr with an `[envapt]` prefix. v4 printed `[dotenv]`.

```ts
// v4
Envapter.dotenvConfig = { debug: true };

// v5
Envapter.debug = 'verbose'; // or set ENVAPT_DEBUG=verbose
```

The debug levels are `silent` (default), `warn`, and `verbose`, as [Debug logging](/docs/errors#debug-logging) explains.

## Validation with Standard Schema

Custom converters that throw still work. v5 also takes a [Standard Schema](/docs/standard-schema) from zod, valibot, arktype, or your own validator, passed as `schema`.

```ts
// v4: a custom converter that throws
@Envapt('PORT', {
    converter: (raw) => {
        const n = Number(raw);
        if (Number.isNaN(n)) throw new Error('bad PORT');
        return n;
    }
})

// v5
@Envapt('PORT', { schema: z.coerce.number().int().min(1024) })
```

## Decorators

The classic positional form is deprecated in v5 and removed in v6. Use the options object. See the [v5 to v6 migration](/docs/migration-v5-to-v6).

```ts
// v4 (removed in v6)
@Envapt('PORT', 8080, Number)

// v5
@Envapt('PORT', { converter: Converters.Number, fallback: 8080 })
```

Declare a static field as `static readonly` and an instance field as `declare readonly`, both with no initializer. v7 later replaced this with the TC39 accessor form, as [Migration (v6 to v7)](/docs/migration-v6-to-v7) explains. [Decorators](/docs/decorators#declaring-decorated-fields) has the current forms.

## Node version

v5 lowered the minimum Node version from `>=24` to `>=20`, since the old minimum existed only for an API envapt no longer uses. Bun `>=1.3` and Deno `>=2.5` are unchanged.

## Behavior changes to watch

> **DANGER**
>
> These changes compile without edits but run differently from v4. Check them before upgrading.

- **Per-environment files auto-load.** When `envPaths` isn't set, v5 loads a cascade of `.env.${env}.local`, `.env.${env}`, `.env.local`, then `.env`, where `${env}` is the detected environment. A stray `.env.local` or `.env.production` in your working directory now loads. v4 ignored it. The [.env files](/docs/env-files) page has the details.
- **Array element failures throw.** If an element fails its converter, the read throws `EnvaptError` with code `ArrayElementConversionFailed` (206). v4 returned a malformed array without an error.
- **Time-string fallbacks coerce.** With `CACHE_TTL` unset, `Envapter.getUsing('CACHE_TTL', Converters.Time, '10s')` now returns `10000` milliseconds. v4 returned the string `'10s'`. A malformed time-string fallback throws `MalformedTimeFallback` (105). v4 threw an older, more general code. [Errors](/docs/errors) lists every code.

envapt leaves `process.env` unchanged by default, as v4 did. If you relied on loaded keys appearing in `process.env`, opt in with [`Envapter.syncProcessEnv`](/docs/env-files#coming-from-dotenv).