Safe Parsing
Use safeCreateEnv to validate environment variables without throwing — get typed result objects instead.
safeCreateEnv is a safe alternative to createEnv. Rather than crashing on validation failure, it returns a discriminated result object you can inspect.
import { safeCreateEnv } from "@ayronforge/envil"
Without resolvers
When no resolvers are configured, safeCreateEnv returns a result object synchronously — just like createEnv returns a plain object.
import { safeCreateEnv, requiredString, port, EnvValidationError } from "@ayronforge/envil"
const result = safeCreateEnv({
server: {
DATABASE_URL: requiredString,
PORT: port,
},
isServer: true,
})
if (result.success) {
// result.data is fully typed — same as createEnv's return
console.log(result.data.DATABASE_URL)
console.log(result.data.PORT)
} else {
// result.error is EnvValidationError
console.error("Validation failed:", result.error.errors)
}
Return type
type Result =
| { success: true; data: EnvResult<...> }
| { success: false; error: EnvValidationError }
With resolvers
When resolvers are present, safeCreateEnv returns an Effect that never fails. Both resolver errors and validation errors are captured in the result object.
import { Effect, Redacted } from "effect"
import { safeCreateEnv, requiredString } from "@ayronforge/envil"
import { fromAwsSecrets } from "@ayronforge/envil/aws"
const program = safeCreateEnv({
server: {
DATABASE_URL: requiredString,
API_KEY: requiredString,
},
resolvers: [
fromAwsSecrets({
secrets: { API_KEY: "prod/api-key" },
}),
],
isServer: true,
})
// The Effect never fails — errors are in the result
const result = await Effect.runPromise(program)
if (result.success) {
console.log(result.data.DATABASE_URL)
// API_KEY is auto-redacted from resolver
console.log(Redacted.value(result.data.API_KEY))
} else {
// result.error is ResolverError | EnvValidationError
console.error(result.error.message)
}
Return type
type Result = Effect.Effect<
| { success: true; data: EnvResult<...> }
| { success: false; error: ResolverError | EnvValidationError },
never // Effect error channel is never — it cannot fail
>
Because the Effect error channel is never, you don’t need Effect.catchTag or Effect.catchAll — the result object handles all error cases for you.
autoRedactResolver
safeCreateEnv supports the same autoRedactResolver option as createEnv. When resolvers are present, resolver-provided values are auto-wrapped in Redacted by default.
Pass autoRedactResolver: false to disable this:
const result = await Effect.runPromise(
safeCreateEnv({
server: {
SECRET: requiredString,
PLAIN: requiredString,
},
resolvers: [myResolver],
autoRedactResolver: false,
isServer: true,
}),
)
if (result.success) {
// SECRET and PLAIN are plain strings, not Redacted
result.data.SECRET // string
}
Exported types
All result types are exported from the main package:
import type {
SafeCreateEnvResult,
SafeCreateEnvSuccess,
SafeCreateEnvFailure,
} from "@ayronforge/envil"
| Name | Type | Description |
|---|---|---|
| SafeCreateEnvSuccess<T> | { success: true; data: T } | Success branch of the result. |
| SafeCreateEnvFailure<E> | { success: false; error: E } | Failure branch of the result. |
| SafeCreateEnvResult<T, E> | SafeCreateEnvSuccess<T> | SafeCreateEnvFailure<E> | Discriminated union of success and failure. |
When to use safeCreateEnv
Use safeCreateEnv when you want to handle validation failures gracefully rather than crashing at startup:
- Graceful degradation — fall back to defaults or partial configs
- Custom error reporting — format and send errors to your logging pipeline
- Conditional startup — decide at runtime whether a missing variable is fatal
- Testing — assert on specific validation failures without try/catch
For most applications, createEnv with its fail-fast behavior is the right choice. Use safeCreateEnv when you need control over the error path.
safeCreateEnv accepts the exact same options as createEnv — the only difference is how errors are surfaced.