envie logo

Package VersionHex DocsBuilt with GleamLicense: MIT

envie

Type-safe environment variables for Gleam. Zero external dependencies, cross-platform (Erlang + JavaScript).

Why the name? envy was already taken on Hex, so we went with envie. Because you shouldn’t be jealous of other languages’ config loaders — you should just desire (French: envie) a better one. Plus, it makes your environment variables feel 20% more sophisticated. 🥐

envie gets out of your way: import it, call get(...), and you’re done. When you need more typed parsing, validation, .env loading, test isolation, it’s all there without changing the core workflow.

Quick start

gleam add envie
import envie

pub fn main() {
  let port = envie.get_int("PORT", 3000)
  let debug = envie.get_bool("DEBUG", False)

  // That's it. No setup, no ceremony.
}

Core API

Read, write, and remove environment variables on any target.

envie.get("HOME")           // -> Ok("/Users/you")
envie.set("APP_ENV", "production")
envie.unset("TEMP_TOKEN")
let all = envie.all()       // -> Dict(String, String)

Type-safe getters

Parse common types without boilerplate. When the variable is missing or the value cannot be parsed, you get the default back.

let port    = envie.get_int("PORT", 3000)
let ratio   = envie.get_float("RATIO", 1.0)
let debug   = envie.get_bool("DEBUG", False)
let origins = envie.get_string_list("ORIGINS", separator: ",", default: [])

Boolean parsing accepts (case-insensitive):

TruthyFalsy
true yes 1 onfalse no 0 off

Validated access

When a missing or malformed variable should be a hard error, use require_*. Every function returns Result(value, Error) with a structured error you can format for logs.

No extra imports — just envie:

import envie

let assert Ok(key)    = envie.require_string("API_KEY")
let assert Ok(port)   = envie.require_port("PORT") // 1–65 535
let assert Ok(url)    = envie.require_web_url("DATABASE_URL") // requires http/https
let assert Ok(name)   = envie.require_non_empty_string("APP_NAME")
let assert Ok(on)     = envie.require_bool("DEBUG")
let assert Ok(ratio)  = envie.require_float_range("RATIO", min: 0.0, max: 1.0)
let assert Ok(env)    = envie.require_one_of("APP_ENV", ["development", "staging", "production"])

Custom validation

If none of the built-in require_* functions covers your case, import envie/decode and compose a custom decoder:

import envie
import envie/decode
import gleam/string

let secret_decoder =
  decode.string()
  |> decode.validated(fn(s) {
    case string.length(s) >= 32 {
      True -> Ok(s)
      False -> Error("Secret must be at least 32 characters")
    }
  })

let assert Ok(secret) = envie.require("JWT_SECRET", secret_decoder)

This is the only scenario where you need envie/decode.

Optional variables

let assert Ok(maybe_port) = envie.optional("METRICS_PORT", decode.int())
// Ok(None) when missing, Ok(Some(value)) when present and valid

.env file loading

Supports comments (#), inline comments (PORT=8080 # default), blank lines, export prefix, and single/double-quoted values.

By default, existing environment variables are not overwritten. Use load_override / load_override_from when .env values should win.

let assert Ok(Nil) = envie.load()                          // .env in cwd
let assert Ok(Nil) = envie.load_from("config/.env.local")  // custom path
let assert Ok(Nil) = envie.load_from_string("PORT=8080")   // from string

// Force overwrite
let assert Ok(Nil) = envie.load_override()

Example .env file:

# Application
PORT=8080
DEBUG=true

# Secrets
export API_KEY="sk-1234567890"
DB_PASSWORD='hunter2'

Testing utilities

Helpers that guarantee the environment is restored after each test.

import envie
import envie/testing

pub fn my_feature_test() {
  testing.with_env([#("PORT", "3000"), #("DEBUG", "true")], fn() {
    let port = envie.get_int("PORT", 8080)
    port |> should.equal(3000)
  })
  // Original environment is restored automatically
}

pub fn isolated_test() {
  testing.isolated(fn() {
    envie.get("PATH") |> should.equal(Error(Nil))
  })
  // Everything restored
}

Test Concurrency: Environment variables are global to the process. Since Gleam runs tests in parallel by default, multiple tests using testing.* concurrently may interfere with each other. Use gleam test -- --seed 123 (or any seed) to run tests with a single worker if you encounter flaky tests.

Error formatting

Every error type carries enough context to produce clear messages.

case envie.require_int("PORT") {
  Ok(port) -> start_server(port)
  Error(err) -> {
    io.println_error(envie.format_error(err))
    // "PORT: invalid value "abc" — Expected integer, got: abc"
  }
}

API at a glance

FunctionReturnsNotes
getResult(String, Nil)Raw access
setNil
unsetNil
allDict(String, String)
get_stringStringFalls back to caller-supplied default
get_intIntFalls back to caller-supplied default
get_floatFloatFalls back to caller-supplied default
get_boolBoolFalls back to default; true/yes/1/on
get_string_listList(String)Falls back to default; splits & trims
requireResult(a, Error)Decoder-based
require_stringResult(String, Error)
require_intResult(Int, Error)
require_int_rangeResult(Int, Error)
require_floatResult(Float, Error)
require_float_rangeResult(Float, Error)
require_urlResult(Uri, Error)Permissive RFC parse
require_url_with_schemeResult(Uri, Error)e.g. [“postgres”]
require_web_urlResult(Uri, Error)http or https only
require_non_empty_stringResult(String, Error)
require_string_prefixResult(String, Error)
require_string_listResult(List(String), Error)
require_int_listResult(List(Int), Error)
require_boolResult(Bool, Error)true/yes/1/on
require_portResult(Int, Error)1–65 535
require_one_ofResult(String, Error)Allow-list check
optionalResult(Option(a), Error)
loadResult(Nil, LoadError).env in cwd
load_fromResult(Nil, LoadError)Custom path
load_from_stringResult(Nil, LoadError)From string
load_overrideResult(Nil, LoadError)Overwrites env
load_override_fromResult(Nil, LoadError)Overwrites env
load_from_string_overrideResult(Nil, LoadError)Overwrites env

Cross-platform


envie works on Erlang and all major JavaScript runtimes.

Dependencies & Requirements



Made with Gleam 💜

Search Document