# Migration (v7 to v8) (/docs/migration-v7-to-v8)



v8 is a major release with **six** breaking changes. The source classes are renamed, the `envapt/workerd` and `envapt/browser` subpaths are removed, the portable build's filesystem-only config APIs warn once and no-op by default instead of throwing, the public type surface is trimmed, the functional required-read bag moves to `getRequired`, and the `Integer` and `Float` converters reject malformed values. Everything else (the positional `Envapter` readers, decorators, and `envapt/legacy`) is unchanged.

## Source and type renames [#source-and-type-renames]

The source class names drop the `Env` infix. `PortableSource` is the one source for every runtime without a filesystem, and it replaces both `ManualEnvSource` and `WorkerEnvSource`, which were the same class. `FileSource` replaces `NodeEnvSource`, and the `Source` type replaces `EnvSource`.

| v7                                   | v8               |
| ------------------------------------ | ---------------- |
| `ManualEnvSource`, `WorkerEnvSource` | `PortableSource` |
| `NodeEnvSource`                      | `FileSource`     |
| `EnvSource` (type)                   | `Source`         |

```ts
// v7
import { Envapter, ManualEnvSource } from 'envapt';

Envapter.useSource(new ManualEnvSource(config));

// v8
import { Envapter, PortableSource } from 'envapt';

Envapter.useSource(new PortableSource(config));
```

If you were already on v7.1, `PortableSource` and `Source` existed and `ManualEnvSource` / `WorkerEnvSource` / `EnvSource` were deprecated aliases, so v8 only removes the aliases. `NodeEnvSource` is renamed to `FileSource` in v8.

## One universal `envapt` import [#one-universal-envapt-import]

The `envapt/workerd` and `envapt/browser` subpaths are gone. Import from `envapt` everywhere. The package export conditions resolve the portable build on Workers, edge runtimes, the browser, and react-native, and the node build on Node, Bun, and Deno.

```ts
// v7
import { Envapter, WorkerEnvSource } from 'envapt/workerd';
import { Envapter, ManualEnvSource } from 'envapt/browser';

// v8
import { Envapter, PortableSource } from 'envapt';
```

The edge runtimes that used to fall through to the node build now resolve the portable build through the `edge-light`, `fastly`, and `worker` conditions. Vercel Edge and Workers apply these automatically. On Fastly Compute, add the `fastly` condition to your bundler's resolve conditions. `envapt/config` and `envapt/legacy` are unchanged.

## File-only APIs warn instead of throwing [#file-only-apis-warn-instead-of-throwing]

On the portable build, the filesystem-only config APIs (`envPaths`, `baseDir`, `envFileOptions`, `configureProfiles`, `resetProfiles`) used to throw `FileApiUnsupported`. They now warn once and no-op by default, so a config module that touches them can be written once and run on both Node and the portable runtimes. To restore the v7 behavior, set `Envapter.fileApiMode = 'throw'`.

```ts
// v7: calling a file API on the portable build threw FileApiUnsupported

// v8: it warns once and no-ops by default
Envapter.envPaths = ['.env']; // warns once, then does nothing on Workers

// opt back into throwing
Envapter.fileApiMode = 'throw';
Envapter.envPaths = ['.env']; // throws FileApiUnsupported again
```

The safety net is unchanged. An unconfigured read still throws `NoSourceBound` on the first access, whatever `fileApiMode` is, so a worker that forgot to bind a source fails fast rather than serving wrong config. And `fileApiMode` governs only the portable file-API behavior. On the node build, binding a non-filesystem source and then calling a file API still throws `FileApiUnsupported`.

## The portable types now include the file APIs [#the-portable-types-now-include-the-file-apis]

Before v8 the portable build's types omitted the five file APIs, so calling one was a compile error. v8 drops that fence. The portable types include them, so a config module shared between your Node dev setup and a Workers deploy type-checks the same in both places. At runtime you get the warn-once and no-op above, or a throw under `fileApiMode = 'throw'`.

## Required reads move to `getRequired` [#required-reads-move-to-getrequired]

The `{ required: true }` options-bag form of `getUsing` and `getWith` is removed. A required read is now `getRequired(key, converter)`, which takes the converter positionally, returns the non-undefined value, and throws `MissingEnvValue` when the key is missing or empty. New in v8, `getRequiredAll(spec)` reads a group of required values in one call and returns a typed record, throwing once with every missing key.

```ts
// v7
const url = Envapter.getUsing('DATABASE_URL', { converter: Converters.Url, required: true });
const upper = Envapter.getWith('NAME', { converter: (raw) => (raw ?? '').toUpperCase(), required: true });

// v8
const url = Envapter.getRequired('DATABASE_URL', Converters.Url);
const upper = Envapter.getRequired('NAME', (raw) => raw.toUpperCase());

// v8, several at once
const { DATABASE_URL: db, PORT: port } = Envapter.getRequiredAll({
    DATABASE_URL: Converters.Url,
    PORT: Converters.Number
});
```

`getUsing` and `getWith` keep their positional fallback forms unchanged. The `@Envapt` decorator's `{ required: true }` option stays, only the functional readers dropped the bag.

## Trimmed public type exports [#trimmed-public-type-exports]

v8 exports 15 types instead of 36. The internal machinery that leaked into v7's public surface is gone, the source shape interfaces (`BareEnvSource` / `FileEnvSource`), the decorator return types, the schema brands, the converter and inference helper types, `EnvKeyInput`, `ArrayOf` / `ArrayElement`, and the `InferSchemaInput` / `InferSchemaOutput` aliases. Inference on the readers and decorators is unchanged, since those types resolve at the call site.

You only need a change here if you imported one of these by name.

* A custom source typed against `BareEnvSource` / `FileEnvSource` uses the `Source` union instead.
* A schema output type from `InferSchemaOutput<typeof schema>` uses your validator's own inference (`z.infer`, valibot's `InferOutput`, arktype's `.infer`) or `StandardSchemaV1.InferOutput`.

## Stricter `Integer` and `Float` converters [#stricter-integer-and-float-converters]

`Converters.Integer` and `Converters.Float` used to parse with `parseInt` and `parseFloat`, which read a leading number and ignored the rest. `42abc` became `42`, `3.9` became `3` under `Integer`, and a value past 2^53 lost precision. v8 parses both with `Number` and validates the result, so a value that is not a clean number falls back to the default, or throws under `getRequired`.

`Integer` requires `Number.isSafeInteger`, so `42abc`, `3.9`, and magnitudes past 2^53 all fall back. `Float` rejects trailing characters like `3.14xyz`. `Float` still accepts `Infinity`, matching the `number` converter and `getNumber`.

```ts
import { Converters, Envapter } from 'envapt';

// with PORT="8080abc"
Envapter.getRequired('PORT', Converters.Integer);
// v7: 8080 (parseInt read the leading number)
// v8: throws MissingEnvValue (Number rejects it)
```

You only need a change here if an env value was relying on the loose parse. A value that is already a clean integer or float reads the same.
