Environment variables have no types. Every value is a string. Your code assumes PORT is a number, DEBUG is a boolean, and DATABASE_URL is a valid URL. These assumptions fail silently until production breaks.
Type-safe environment variables solve this problem. Define expected types in a schema. Validate before deployment. Catch configuration errors before they cause outages.
This guide covers everything you need to implement type-safe environment variables in any project.
Why Type Safety Matters for Configuration
Consider this .env file:
PORT=three-thousand
DEBUG=yes
DATABASE_URL=not-a-url
API_TIMEOUT=30.5
Every value is technically valid as a string. But your application expects:
PORTto be an integerDEBUGto be a booleanDATABASE_URLto be a valid URLAPI_TIMEOUTto be a float
Without validation, these errors reach production. With type-safe schemas, they get caught immediately:
$ zenv check
zenv check failed:
- PORT: expected int, got 'three-thousand'
- DEBUG: expected bool, got 'yes'
- DATABASE_URL: expected url, got 'not-a-url'
Type safety transforms environment configuration from a source of bugs into a reliable foundation for your application.
Supported Types
A type-safe schema defines the expected format for each variable. zorath-env supports six types that cover most configuration needs.
String Type
The default type. Accepts any text value.
{
"APP_NAME": {
"type": "string",
"required": true,
"description": "Application display name"
}
}
Integer Type
Whole numbers only. Rejects decimals and non-numeric values.
{
"PORT": {
"type": "int",
"required": true,
"default": 3000,
"description": "HTTP server port"
},
"MAX_CONNECTIONS": {
"type": "int",
"default": 100
}
}
Float Type
Decimal numbers. Useful for rates, percentages, and thresholds.
{
"RATE_LIMIT": {
"type": "float",
"default": 100.0,
"description": "Requests per second limit"
},
"CACHE_TTL_HOURS": {
"type": "float",
"default": 24.0
}
}
Boolean Type
Accepts true, false, 1, 0, yes, no (case-insensitive). Normalizes inconsistent values across environments.
{
"DEBUG": {
"type": "bool",
"default": false,
"description": "Enable debug logging"
},
"FEATURE_ENABLED": {
"type": "bool",
"required": true
}
}
URL Type
Validates URL structure. Catches malformed connection strings before they cause runtime errors.
{
"DATABASE_URL": {
"type": "url",
"required": true,
"description": "PostgreSQL connection string"
},
"REDIS_URL": {
"type": "url",
"required": true
}
}
Enum Type
Restricts values to a predefined list. Prevents typos and invalid configurations.
{
"NODE_ENV": {
"type": "enum",
"values": ["development", "staging", "production"],
"required": true,
"description": "Runtime environment"
},
"LOG_LEVEL": {
"type": "enum",
"values": ["debug", "info", "warn", "error"],
"default": "info"
}
}
See the full schema reference for detailed documentation on each type.
Validation Rules
Types catch format errors. Validation rules catch semantic errors. Combine them for comprehensive configuration validation.
Numeric Constraints
Set minimum and maximum bounds for integers and floats:
{
"PORT": {
"type": "int",
"validate": {
"min": 1024,
"max": 65535
},
"description": "Must be a non-privileged port"
},
"CACHE_SIZE_MB": {
"type": "float",
"validate": {
"min_value": 0.0,
"max_value": 1024.0
}
}
}
String Length Constraints
Enforce minimum and maximum lengths for strings:
{
"API_KEY": {
"type": "string",
"required": true,
"validate": {
"min_length": 32,
"max_length": 64
},
"description": "API key must be 32-64 characters"
},
"SESSION_SECRET": {
"type": "string",
"validate": {
"min_length": 64
}
}
}
Pattern Matching
Use regular expressions for complex validation:
{
"EMAIL": {
"type": "string",
"validate": {
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}
},
"AWS_REGION": {
"type": "string",
"validate": {
"pattern": "^[a-z]{2}-[a-z]+-\\d$"
},
"description": "AWS region format (e.g., us-east-1)"
}
}
Validation rules work alongside type checking. A variable must pass both type validation and any custom rules to be considered valid.
Variable Interpolation
Complex configurations often reference other variables. Type-safe schemas support variable interpolation with ${VAR} syntax:
# .env
BASE_URL=https://api.example.com
API_V1_ENDPOINT=${BASE_URL}/v1
API_V2_ENDPOINT=${BASE_URL}/v2
WEBHOOK_URL=${BASE_URL}/webhooks/incoming
PORT=8080
HEALTH_CHECK_URL=http://localhost:${PORT}/health
Interpolation resolves before validation. The final values get type-checked against your schema.
Circular Reference Detection
Circular references cause infinite loops. zorath-env detects and reports them:
# .env
A=${B}
B=${C}
C=${A} # Circular!
$ zenv check
zenv check failed:
- Circular reference detected: A -> B -> C -> A
This prevents runtime hangs from misconfigured variable chains.
Schema Inheritance
Different environments need different configurations. Schema inheritance lets you define a base schema and extend it for specific environments.
Base Schema
Create a base schema with common variables:
// base.schema.json
{
"DATABASE_URL": {
"type": "url",
"required": true,
"description": "Database connection string"
},
"LOG_LEVEL": {
"type": "enum",
"values": ["debug", "info", "warn", "error"],
"default": "info"
},
"PORT": {
"type": "int",
"default": 3000
}
}
Production Schema
Extend the base schema with production-specific requirements:
// production.schema.json
{
"extends": "base.schema.json",
"SENTRY_DSN": {
"type": "url",
"required": true,
"description": "Error tracking endpoint"
},
"LOG_LEVEL": {
"type": "enum",
"values": ["info", "warn", "error"],
"default": "warn"
}
}
The production schema:
- Inherits all variables from
base.schema.json - Adds
SENTRY_DSNas a production requirement - Overrides
LOG_LEVELto exclude debug level
Development Schema
Create a development-specific schema with relaxed requirements:
// development.schema.json
{
"extends": "base.schema.json",
"DEBUG": {
"type": "bool",
"default": true
},
"MOCK_EXTERNAL_SERVICES": {
"type": "bool",
"default": true
}
}
Validate with the appropriate schema for each environment:
# Development
zenv check --schema development.schema.json
# Production
zenv check --schema production.schema.json
Schema inheritance keeps your configurations DRY while allowing environment-specific customization. See the GitHub wiki for more inheritance patterns.
Auto-Generated Documentation
Your schema contains everything needed for documentation: variable names, types, descriptions, defaults, and constraints. Generate documentation automatically instead of maintaining it manually.
Markdown Output
$ zenv docs --schema env.schema.json
Output:
# Environment Variables
## `DATABASE_URL`
- Type: `url`
- Required: `true`
PostgreSQL connection string
## `PORT`
- Type: `int`
- Required: `false`
- Default: `3000`
HTTP server port
Redirect to a file for your repository:
zenv docs > ENVIRONMENT.md
JSON Output
For tooling integration, generate JSON:
zenv docs --format json > schema-docs.json
Generate Example Files
Create .env.example files from your schema:
# Without defaults
zenv example > .env.example
# With default values included
zenv example --include-defaults > .env.example
Output:
# DATABASE_URL (url, required)
# PostgreSQL connection string
DATABASE_URL=
# PORT (int, optional)
# HTTP server port
# Default: 3000
PORT=3000
Documentation stays in sync with your schema automatically. Learn more in the CLI commands reference.
CI/CD Integration
Type-safe validation becomes powerful when integrated into your deployment pipeline. Catch configuration errors before they reach any environment.
GitHub Actions
Use the official GitHub Action for seamless integration:
- name: Validate environment configuration
uses: zorl-engine/zorath-env/.github/actions/zenv-action@main
with:
schema: env.schema.json
env-file: .env.example
The action downloads prebuilt binaries for fast execution. No Rust compilation required.
Pre-Commit Hooks
Validate on every commit:
#!/usr/bin/env bash
set -e
if [ -f "env.schema.json" ]; then
if command -v zenv >/dev/null 2>&1; then
zenv check || exit 1
else
echo "Warning: zenv not installed, skipping validation"
fi
fi
Exit Codes
zorath-env uses standard exit codes for CI/CD integration:
| Exit Code | Meaning | |-----------|---------| | 0 | Validation passed | | 1 | Validation failed |
Any CI system can interpret these codes to pass or fail builds appropriately.
For detailed CI/CD setup instructions, see our pipeline setup guide.
Getting Started
Implementing type-safe environment variables takes five minutes:
1. Install zorath-env
# Via cargo
cargo install zorath-env
# Or download binary from GitHub releases
# https://github.com/zorl-engine/zorath-env/releases
Prebuilt binaries available for Linux, macOS (Intel and Apple Silicon), and Windows. No Rust installation required.
2. Generate a Schema
If you have an existing .env.example file:
zenv init --example .env.example
This creates env.schema.json with types inferred from your values.
3. Refine Your Schema
Add descriptions, validation rules, and adjust types:
{
"DATABASE_URL": {
"type": "url",
"required": true,
"description": "PostgreSQL connection string"
},
"PORT": {
"type": "int",
"default": 3000,
"validate": { "min": 1024, "max": 65535 }
}
}
4. Validate
zenv check
5. Integrate
Add validation to your CI/CD pipeline and pre-commit hooks.
Resources
- Documentation: zorl.cloud/zenv/docs
- GitHub: github.com/zorl-engine/zorath-env
- Wiki: GitHub Wiki
- Package: crates.io/crates/zorath-env
- Community: r/zorath_env
Conclusion
Type-safe environment variables eliminate an entire category of production bugs. Define your schema once, validate everywhere, and catch configuration errors before they cause outages.
The investment is minimal: a JSON file and a single command. The return is reliable configuration across all environments.
Start with zorath-env today. Your future self debugging a production incident will thank you.