Adding a New Variable
When adding a new environment variable to the ElyOS project, you need to update the code in 3 places.
Checklist
Section titled “Checklist”-
.env.schema— with Varlock annotations -
schema.ts— in 4 places (EXPECTED_KEYS, REQUIRED_KEYS, validateSchema, validEnvArbitrary) -
.env.example— example value
Example: Adding REDIS_URL
Section titled “Example: Adding REDIS_URL”Suppose you are adding a Redis connection to the application.
1. Update the .env.schema file
Section titled “1. Update the .env.schema file”File: apps/web/.env.schema
# Redis# @type=string# @description=Redis connection URL# @optionalREDIS_URL=Annotations:
@type=string— string value (or@type=urlif you want URL validation)@description— description of the variable@optional— not required (or@requiredif required)
2. Update the schema.ts file
Section titled “2. Update the schema.ts file”File: apps/web/src/lib/secrets/schema.ts
a) EXPECTED_ENV_KEYS array
Section titled “a) EXPECTED_ENV_KEYS array”export const EXPECTED_ENV_KEYS = [ // ... existing variables 'REDIS_URL' // ← Add here] as const;b) REQUIRED_KEYS array (if required)
Section titled “b) REQUIRED_KEYS array (if required)”export const REQUIRED_KEYS = [ // ... existing variables 'REDIS_URL' // ← Only if required] as const;c) validateSchema function (if special validation is needed)
Section titled “c) validateSchema function (if special validation is needed)”If you want URL validation:
// URL type field validationconst urlFields = ['APP_URL', 'ORIGIN', 'BETTER_AUTH_URL', 'REDIS_URL'] as const;for (const key of urlFields) { const value = env[key]; if (value !== undefined && value !== null && value !== '') { if (!isValidUrl(value)) { errors.push(`Type validation failed: ${key} — expected: url, got: "${value}"`); } }}d) validEnvArbitrary function (for tests)
Section titled “d) validEnvArbitrary function (for tests)”export function validEnvArbitrary() { return fc.record({ // ... existing variables REDIS_URL: fc.option( fc.string({ minLength: 1, maxLength: 128 }), { nil: undefined } ), });}3. Update the .env.example file
Section titled “3. Update the .env.example file”File: .env.example (project root)
# RedisREDIS_URL=redis://localhost:6379Examples for Different Types
Section titled “Examples for Different Types”String variable
Section titled “String variable”# @type=stringAPP_NAME=ElyOS// schema.ts - validEnvArbitraryAPP_NAME: fc.option( fc.string({ minLength: 1, maxLength: 64 }), { nil: undefined }),Number variable
Section titled “Number variable”# @type=number# @type=number(min=1, max=100)MAX_CONNECTIONS=50// schema.ts - validateSchemaconst numericRangeFields = [ { key: 'MAX_CONNECTIONS', min: 1, max: 100 }];
// schema.ts - validEnvArbitraryMAX_CONNECTIONS: fc.option( fc.integer({ min: 1, max: 100 }).map(String), { nil: undefined }),Boolean variable
Section titled “Boolean variable”# @type=booleanFEATURE_ENABLED=true// schema.ts - validateSchemaconst booleanFields = [ 'FEATURE_ENABLED'] as const;
for (const key of booleanFields) { const value = env[key]; if (value && !isValidBoolean(value)) { errors.push(`Type validation failed: ${key}...`); }}
// schema.ts - validEnvArbitraryFEATURE_ENABLED: fc.option( fc.constantFrom('true', 'false'), { nil: undefined }),Enum variable
Section titled “Enum variable”# @type=enum(redis,memcached,none)CACHE_DRIVER=redis// schema.ts - validateSchemaconst cacheDriver = env['CACHE_DRIVER'];if (cacheDriver && !['redis', 'memcached', 'none'].includes(String(cacheDriver))) { errors.push(`Type validation failed: CACHE_DRIVER — expected: enum(redis, memcached, none), got: "${cacheDriver}"`);}
// schema.ts - validEnvArbitraryCACHE_DRIVER: fc.option( fc.constantFrom('redis', 'memcached', 'none'), { nil: undefined }),Port variable
Section titled “Port variable”# @type=portREDIS_PORT=6379// schema.ts - validateSchemaconst portFields = ['ELYOS_PORT', 'SMTP_PORT', 'REDIS_PORT'] as const;
// schema.ts - validEnvArbitraryREDIS_PORT: fc.option( fc.integer({ min: 1, max: 65535 }).map(String), { nil: undefined }),Conditional Requirements
Section titled “Conditional Requirements”If a variable is only required when another variable’s value meets a condition:
# @type=enum(redis,memcached,none)CACHE_DRIVER=redis
# Only required if CACHE_DRIVER=redis# @required=eq($CACHE_DRIVER, redis)REDIS_URL=
# @type=port @required=eq($CACHE_DRIVER, redis)REDIS_PORT=6379Varlock automatically handles conditional requirements, but you can also implement it in schema.ts if you prefer.
Using the Variable in the Application
Section titled “Using the Variable in the Application”After adding the variable, you can use it in the application:
import { env } from '$lib/env';
// Type-safe accessconst redisUrl = env.REDIS_URL; // string | undefined
if (redisUrl) { // Create Redis connection}Testing
Section titled “Testing”Run the tests to verify everything works:
# Unit testsbun test
# Property-based testsbun test:pbtNext Steps
Section titled “Next Steps”- Varlock schema format → — annotations in detail
- Runtime validation → — how schema.ts works
- Infisical integration → — adding secrets