# Errors

`@zap-studio/env` throws three structured errors, each covering a different failure: a bad setup, a failed validation, and an out-of-bounds read. All three extend `Error`.

## `EnvironmentError`

Thrown by `createEnvironment` and [`generateEnvironmentExample`](/env/generate-env-example) when the schema itself is misconfigured — not a runtime validation failure:

* a `client` key declared without a `clientPrefix`
* a `client` key that doesn't start with the configured `clientPrefix`
* a key declared by more than one composed [`extends`](/env/extends) source with two different schemas

```ts
import { EnvironmentError } from "@zap-studio/env/errors";

try {
  createEnvironment({ extends: [base, override], runtimeEnv: process.env });
} catch (error) {
  if (error instanceof EnvironmentError) {
    console.error(error.message);
  }
}
```

## `EnvironmentValidationError`

Thrown by `createEnvironment` when one or more declared vars fail Standard Schema validation, and no [`onValidationError`](#custom-validation-handling) callback is provided.

| Property      | Type                                                | Description                                          |
| ------------- | --------------------------------------------------- | ---------------------------------------------------- |
| `invalidKeys` | `readonly string[]`                                 | The keys that failed validation. Never their values. |
| `issues`      | `Record<string, readonly StandardSchemaV1.Issue[]>` | The full Standard Schema issues, per invalid key.    |

```ts
import { createEnvironment, EnvironmentValidationError } from "@zap-studio/env";
import { z } from "zod";

try {
  const env = createEnvironment({
    server: { PORT: z.coerce.number() },
    runtimeEnv: process.env,
  });
} catch (error) {
  if (error instanceof EnvironmentValidationError) {
    console.error("Invalid env vars:", error.invalidKeys);
  }
}
```

Every key's issues are collected — the error is not thrown on the first invalid key, so `invalidKeys` and `issues` always report the full picture in one pass.

### Custom Validation Handling

Pass `onValidationError` to run your own logic instead of throwing `EnvironmentValidationError` — for example, to format a startup error message. It receives the same per-key issues and must throw or otherwise never return:

```ts
createEnvironment({
  server: { PORT: z.coerce.number() },
  runtimeEnv: process.env,
  onValidationError: (issues) => {
    console.error("Invalid environment variables:", Object.keys(issues).join(", "));
    process.exit(1);
  },
});
```

## `EnvironmentAccessError`

Thrown by the object `createEnvironment` returns when client-side code reads a `server`-only key, and no `onInvalidAccess` callback is provided. See [`server`/`client`/`shared`](/env/server-client-shared#the-access-guard) for when this fires and how to override it with `onInvalidAccess`.

| Property | Type     | Description                            |
| -------- | -------- | -------------------------------------- |
| `key`    | `string` | The server-only key that was accessed. |

```ts
import { EnvironmentAccessError } from "@zap-studio/env";

try {
  env.DATABASE_URL; // server-only key, accessed from the browser
} catch (error) {
  if (error instanceof EnvironmentAccessError) {
    console.error(error.message);
  }
}
```

## Subpath Import

All three errors are also available from the dedicated `@zap-studio/env/errors` subpath, for consumers who prefer granular imports:

```ts
import {
  EnvironmentAccessError,
  EnvironmentError,
  EnvironmentValidationError,
} from "@zap-studio/env/errors";
```

## See Also

* [`server`/`client`/`shared`](/env/server-client-shared) — where `EnvironmentError` and `EnvironmentAccessError` come from
* [`extends`](/env/extends) — the conflict case that throws `EnvironmentError`
* [Advanced Options](/env/advanced-options) — `skipValidation` to bypass `EnvironmentValidationError` entirely
