better-env better-env Docs

Error Handling

Understand the error classes thrown by better-env and how to handle them.

better-env uses three distinct error types to signal different failure modes. All error classes are exported from the main package.

import { EnvValidationError, ClientAccessError } from "@ayronforge/better-env"

EnvValidationError

Thrown when one or more environment variables fail schema validation.

Name Type Default Description
_tag "EnvValidationError" Discriminant tag for pattern matching.
errors ReadonlyArray<string> Array of human-readable validation error messages.
message string Formatted error message with all failures.

Example

import { createEnv, requiredString, port, EnvValidationError } from "@ayronforge/better-env"

try {
  const env = createEnv({
    server: {
      DATABASE_URL: requiredString,
      PORT: port,
    },
  })
} catch (e) {
  if (e instanceof EnvValidationError) {
    console.error("Validation failed:")
    for (const error of e.errors) {
      console.error(`  - ${error}`)
    }
    // Output:
    //   - DATABASE_URL: Expected a string with a length of at least 1, but got undefined
    //   - PORT: Expected Port (1-65535), but got "abc"
  }
}
Note

EnvValidationError collects all validation failures, not just the first one. This lets you fix all issues in a single pass rather than playing whack-a-mole.

onValidationError callback

You can hook into validation errors before the exception is thrown:

createEnv({
  onValidationError: (errors) => {
    // errors is string[] — same as EnvValidationError.errors
    logger.error("Environment validation failed", { errors })
  },
  server: {
    DATABASE_URL: requiredString,
  },
})

The callback fires before the error is thrown. The EnvValidationError is still thrown after the callback completes.

ClientAccessError

Thrown when client-side code attempts to access a server-only environment variable.

Name Type Default Description
_tag "ClientAccessError" Discriminant tag for pattern matching.
variableName string The name of the server variable that was accessed.
message string Descriptive error message.

Example

import { createEnv, requiredString, ClientAccessError } from "@ayronforge/better-env"

const env = createEnv({
  server: {
    DATABASE_URL: requiredString,
  },
  client: {
    NEXT_PUBLIC_API_URL: requiredString,
  },
  isServer: false, // simulate client-side
})

try {
  env.DATABASE_URL // throws on client
} catch (e) {
  if (e instanceof ClientAccessError) {
    console.error(e.variableName) // "DATABASE_URL"
    // "Attempted to access server-side env var "DATABASE_URL" on client"
  }
}

ResolverError

Thrown when a resolver fails to initialize or fetch secrets. This is an Effect tagged error, used in the Effect error channel.

Name Type Default Description
_tag "ResolverError" Discriminant tag for Effect error handling.
resolver string Name of the resolver that failed (e.g., "aws", "gcp").
message string Human-readable error message.
cause unknown The underlying error, if any.

ResolverError is a Data.TaggedError from Effect, meaning you can use it with Effect’s error handling:

import { Effect } from "effect"
import { createEnv, requiredString } from "@ayronforge/better-env"
import { fromAwsSecrets, ResolverError } from "@ayronforge/better-env/aws"

const envEffect = createEnv({
  server: {
    DATABASE_URL: requiredString,
  },
  resolvers: [
    fromAwsSecrets({
      secrets: { DATABASE_URL: "prod/db-url" },
    }),
  ],
})

// Handle resolver errors specifically
const program = envEffect.pipe(
  Effect.catchTag("ResolverError", (err) => {
    console.error(`Resolver "${err.resolver}" failed: ${err.message}`)
    return Effect.fail(err)
  }),
)
Warning

When using resolvers, createEnv returns an Effect.Effect instead of a plain object. You must run it with Effect.runPromise or Effect.runSync (or use it within an Effect pipeline).