add-env-variable

Add a new environment variable to the application. Use when adding configuration for external services, feature flags, or application settings. Triggers on "add env", "environment variable", "config variable".

$ 安裝

git clone https://github.com/madooei/backend-template /tmp/backend-template && cp -r /tmp/backend-template/.claude/skills/add-env-variable ~/.claude/skills/backend-template

// tip: Run this command in your terminal to install the skill


name: add-env-variable description: Add a new environment variable to the application. Use when adding configuration for external services, feature flags, or application settings. Triggers on "add env", "environment variable", "config variable".

Add Environment Variable

Adds a new environment variable with Zod validation. All environment variables must be defined in src/env.ts and documented in .env.example.

Quick Reference

Files to modify:

  1. src/env.ts - Add to schema and mapping
  2. .env.example - Document the variable
  3. tests/env.test.ts - Add validation tests

Instructions

Step 1: Add to Schema in src/env.ts

Add the variable to the envSchema object:

const envSchema = z.object({
  // ... existing variables ...

  // Your new variable (with comment explaining purpose)
  NEW_VARIABLE: z.string(), // Required string
  // OR
  NEW_VARIABLE: z.string().optional(), // Optional string
  // OR
  NEW_VARIABLE: z.string().default("default-value"), // With default
  // OR
  NEW_VARIABLE: z.coerce.number().default(3000), // Number with coercion
  // OR
  NEW_VARIABLE: z.string().url(), // URL validation
});

Step 2: Add to Mapping Object

Add the variable to mappedEnv using the getEnv() helper (which reads prefixed variables):

const mappedEnv = {
  // ... existing mappings ...
  NEW_VARIABLE: getEnv("NEW_VARIABLE"),
};

Step 3: Document in .env.example

Add the variable with a descriptive comment, using the prefix (default: BT_):

# Description of what this variable is for
BT_NEW_VARIABLE=example-value

Note: The prefix is defined in src/env.ts as const PREFIX = "BT". Change this when creating a new service from the template.

Step 4: Add Tests in tests/env.test.ts

Add test cases for the new variable:

it("accepts valid NEW_VARIABLE", () => {
  const parsed = envSchema.parse({ NEW_VARIABLE: "valid-value" });
  expect(parsed.NEW_VARIABLE).toBe("valid-value");
});

it("defaults NEW_VARIABLE if missing", () => {
  const parsed = envSchema.parse({});
  expect(parsed.NEW_VARIABLE).toBe("default-value");
});

// OR for optional
it("accepts missing NEW_VARIABLE", () => {
  const parsed = envSchema.parse({});
  expect(parsed.NEW_VARIABLE).toBeUndefined();
});

// OR for required
it("rejects missing NEW_VARIABLE", () => {
  expect(() => envSchema.parse({})).toThrow();
});

Common Patterns

All examples use the getEnv() helper and BT_ prefix:

Required String

// Schema
MY_API_KEY: z.string(),

// Mapping
MY_API_KEY: getEnv("MY_API_KEY"),

// .env.example
BT_MY_API_KEY=your-api-key-here

Optional String

// Schema
OPTIONAL_FEATURE: z.string().optional(),

// Mapping
OPTIONAL_FEATURE: getEnv("OPTIONAL_FEATURE"),

// .env.example
# Optional: Enable feature X
# BT_OPTIONAL_FEATURE=enabled

String with Default

// Schema
LOG_LEVEL: z.string().default("info"),

// Mapping
LOG_LEVEL: getEnv("LOG_LEVEL"),

// .env.example
BT_LOG_LEVEL=info

Number with Coercion

// Schema
RATE_LIMIT: z.coerce.number().default(100),

// Mapping
RATE_LIMIT: getEnv("RATE_LIMIT"),

// .env.example
BT_RATE_LIMIT=100

URL Validation

// Schema
WEBHOOK_URL: z.string().url().optional(),

// Mapping
WEBHOOK_URL: getEnv("WEBHOOK_URL"),

// .env.example
# Webhook endpoint for notifications
BT_WEBHOOK_URL=https://example.com/webhook

Enum Values

// Schema
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),

// Mapping
NODE_ENV: getEnv("NODE_ENV"),

// .env.example
BT_NODE_ENV=development

Boolean (as string)

// Schema
ENABLE_FEATURE: z.string().transform(v => v === "true").default("false"),

// Mapping
ENABLE_FEATURE: getEnv("ENABLE_FEATURE"),

// .env.example
BT_ENABLE_FEATURE=false

Usage in Code

Always import from @/env, never use process.env directly:

import { env } from "@/env";

// Correct
const apiKey = env.MY_API_KEY;

// Wrong - bypasses validation
const apiKey = process.env.MY_API_KEY;

Full Example: Adding Email Service Config

1. Update src/env.ts

const envSchema = z.object({
  // ... existing ...

  // Email service configuration
  EMAIL_API_KEY: z.string().optional(),
  EMAIL_FROM_ADDRESS: z.string().email().optional(),
  EMAIL_PROVIDER: z.enum(["sendgrid", "mailgun"]).default("sendgrid"),
});

const mappedEnv = {
  // ... existing ...
  EMAIL_API_KEY: getEnv("EMAIL_API_KEY"),
  EMAIL_FROM_ADDRESS: getEnv("EMAIL_FROM_ADDRESS"),
  EMAIL_PROVIDER: getEnv("EMAIL_PROVIDER"),
};

2. Update .env.example

# Email service configuration
BT_EMAIL_API_KEY=your-email-api-key
BT_EMAIL_FROM_ADDRESS=noreply@example.com
BT_EMAIL_PROVIDER=sendgrid

3. Update tests/env.test.ts

it("accepts valid email configuration", () => {
  const parsed = envSchema.parse({
    EMAIL_API_KEY: "test-key",
    EMAIL_FROM_ADDRESS: "test@example.com",
    EMAIL_PROVIDER: "mailgun",
  });
  expect(parsed.EMAIL_API_KEY).toBe("test-key");
  expect(parsed.EMAIL_FROM_ADDRESS).toBe("test@example.com");
  expect(parsed.EMAIL_PROVIDER).toBe("mailgun");
});

it("defaults EMAIL_PROVIDER to sendgrid", () => {
  const parsed = envSchema.parse({});
  expect(parsed.EMAIL_PROVIDER).toBe("sendgrid");
});

it("rejects invalid EMAIL_FROM_ADDRESS", () => {
  expect(() =>
    envSchema.parse({ EMAIL_FROM_ADDRESS: "not-an-email" }),
  ).toThrow();
});

it("rejects invalid EMAIL_PROVIDER", () => {
  expect(() => envSchema.parse({ EMAIL_PROVIDER: "invalid" })).toThrow();
});

What NOT to Do

  • Do NOT use process.env directly in application code
  • Do NOT forget to add the mapping in mappedEnv
  • Do NOT skip documenting in .env.example
  • Do NOT skip adding tests for validation rules
  • Do NOT store secrets in .env.example (use placeholder values)

See Also

  • create-utility-service - Services that use environment config
  • test-schema - Testing Zod schemas (similar patterns)