Rust · TypeScript · Kotlin

Catch bad config before it ships

Schema-driven validation for environment variables and .env files — one portable schema, three native implementations.

Open source · MIT license · Rust · TypeScript · Kotlin

envlint.toml + envlint check
# envlint.toml — the SAME file works across all three implementations
[vars.DATABASE_URL]
type = "url"
required = true

[vars.LOG_LEVEL]
type = "enum"
values = ["debug", "info", "warn", "error"]
default = "info"

$ envlint check --env-file .env
envlint: validating .env
error: DATABASE_URL: required variable is not set
error: LOG_LEVEL: must be one of ["debug","info","warn","error"], got "verbose"
2 error(s), 0 warning(s)   →   exit 1
What it is

envlint validates your environment against a single portable envlint.toml — in CI, in a container entrypoint, or at process boot. Catch missing or malformed config before it ships. CLI + library, native in Rust, TypeScript, and Kotlin.

Most production incidents that trace back to "config" are not subtle: a required variable was never set, a PORT held a hostname, a LOG_LEVEL of "verbose" silently fell back to a default, a timeout was 30 — seconds or milliseconds? These are caught trivially if something declares what the service expects.

envlint is that something. You describe your environment once in envlint.toml, then validate a .env file or the live process environment. Unlike dotenv, envalid, or zod — which make a running program read its config — envlint is a gate that runs before your program (or your deploy) does, with no code in the target service and no language lock-in. The same envlint.toml validates a Rust binary, a Node container, and a JVM service alike. It is a CI/CD check first, a library second.

Why envlint

Built to get out of your way

📐

One portable schema

A single envlint.toml drives identical validation semantics across Rust, TypeScript, and Kotlin. Types: string, int, float, bool, url, port, enum, duration — plus required, default, pattern, min/max, and secret masking.

🚦

A gate, not a runtime read

Runs in CI, a container entrypoint, or at boot — before your program does. Exit codes are pipeline-ready: 0 clean, 1 validation errors, 2 usage/IO. Drop it into GitHub Actions in two lines.

🔒

Secrets stay masked

Mark a variable secret and it renders as ****** in every output — text and JSON, CLI and library. Validated values come back typed and default-filled, ready to hand to the rest of your config layer.

Installation

Add it in one line

Same library, three ecosystems. Pick yours.

Rustcargo
cargo add envlint --git https://github.com/slothlabsorg/envlint
# CLI:
cargo install --git https://github.com/slothlabsorg/envlint envlint
TypeScriptnpm
npm i @slothlabs/envlint
Kotlin / JVMgradle · jitpack
repositories {
    maven("https://jitpack.io")
}

dependencies {
    implementation("com.github.slothlabsorg:envlint:v0.1.0")
}

npm packages publish under the @slothlabs scope · JitPack builds from the git tag v0.1.0

In practice

One small example

Schema-driven validation for environment variables and .env files — one portable schema, three native implementations.

RustTypeScriptKotlinCLICI/CDConfig
envlint.toml + envlint check
# envlint.toml — the SAME file works across all three implementations
[vars.DATABASE_URL]
type = "url"
required = true

[vars.LOG_LEVEL]
type = "enum"
values = ["debug", "info", "warn", "error"]
default = "info"

$ envlint check --env-file .env
envlint: validating .env
error: DATABASE_URL: required variable is not set
error: LOG_LEVEL: must be one of ["debug","info","warn","error"], got "verbose"
2 error(s), 0 warning(s)   →   exit 1

Try envlint in your stack

Free and open source. Rust, TypeScript, or Kotlin — same semantics everywhere.