Claude Code Plugins

Community-maintained marketplace

Feedback

Manage environment variables across dev/staging/prod environments and SST config. Use when adding new secrets, updating environment-specific configs, or debugging environment issues.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name env-config
description Manage environment variables across dev/staging/prod environments and SST config. Use when adding new secrets, updating environment-specific configs, or debugging environment issues.
allowed-tools Read, Edit, Grep

Environment Configuration Skill

This skill helps you manage environment variables and secrets across the monorepo.

When to Use This Skill

  • Adding new environment variables
  • Configuring stage-specific variables
  • Setting up secrets for deployment
  • Debugging environment variable issues
  • Managing API keys and tokens
  • Configuring database connections
  • Setting up third-party integrations

Environment File Structure

sgcarstrends/
├── .env.example              # Example env file (committed)
├── .env.local                # Local development (not committed)
├── .env.test                 # Test environment (not committed)
├── apps/
│   ├── api/
│   │   ├── .env.example
│   │   └── .env.local
│   └── web/
│       ├── .env.example
│       └── .env.local
└── packages/
    └── database/
        ├── .env.example
        └── .env.local

Environment Variable Categories

Database

# PostgreSQL
DATABASE_URL=postgresql://user:password@host:5432/database

# Connection pooling (optional)
DATABASE_POOL_MIN=2
DATABASE_POOL_MAX=10

Redis

# Upstash Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here

AI Services

# Google Gemini
GOOGLE_GEMINI_API_KEY=your-api-key
GEMINI_MODEL=gemini-1.5-pro-latest

Workflows & Queues

# QStash
QSTASH_TOKEN=your-qstash-token
QSTASH_CURRENT_SIGNING_KEY=your-signing-key
QSTASH_NEXT_SIGNING_KEY=your-next-signing-key

Social Media

# Discord
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...

# Telegram
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_CHAT_ID=your-chat-id

# Twitter
TWITTER_BEARER_TOKEN=your-bearer-token

# LinkedIn
LINKEDIN_ACCESS_TOKEN=your-access-token
LINKEDIN_ORG_ID=your-org-id

Storage

# Vercel Blob
BLOB_READ_WRITE_TOKEN=vercel_blob_token

Next.js Public Variables

# Public variables (exposed to browser)
NEXT_PUBLIC_API_URL=https://api.sgcarstrends.com
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NEXT_PUBLIC_SITE_URL=https://sgcarstrends.com

Build & Runtime

# Node environment
NODE_ENV=development  # development | production | test

# Next.js
NEXT_TELEMETRY_DISABLED=1

Environment File Examples

Root .env.example

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends

# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here

# AI Services
GOOGLE_GEMINI_API_KEY=your-api-key

# Workflows
QSTASH_TOKEN=your-qstash-token

# Social Media
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
TELEGRAM_BOT_TOKEN=your-bot-token
TELEGRAM_CHAT_ID=your-chat-id

# Storage
BLOB_READ_WRITE_TOKEN=vercel_blob_token

apps/web/.env.example

# API
NEXT_PUBLIC_API_URL=http://localhost:3000

# Database (for server components)
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends

# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here

# Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

# Site URL
NEXT_PUBLIC_SITE_URL=http://localhost:3001

apps/api/.env.example

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/sgcarstrends

# Redis
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token-here

# All other API-specific vars...

Stage-Specific Configuration

Development

# .env.local (development)
NODE_ENV=development
DATABASE_URL=postgresql://localhost:5432/sgcarstrends_dev
NEXT_PUBLIC_API_URL=http://localhost:3000
NEXT_PUBLIC_SITE_URL=http://localhost:3001

Staging

# Staging (set in SST or CI/CD)
NODE_ENV=production
DATABASE_URL=postgresql://...staging...
NEXT_PUBLIC_API_URL=https://api.staging.sgcarstrends.com
NEXT_PUBLIC_SITE_URL=https://staging.sgcarstrends.com

Production

# Production (set in SST or CI/CD)
NODE_ENV=production
DATABASE_URL=postgresql://...production...
NEXT_PUBLIC_API_URL=https://api.sgcarstrends.com
NEXT_PUBLIC_SITE_URL=https://sgcarstrends.com

SST Configuration

Using Environment Variables in SST

// infra/api.ts
import { StackContext, Function } from "sst/constructs";

export function API({ stack, app }: StackContext) {
  const api = new Function(stack, "api", {
    handler: "apps/api/src/index.handler",
    environment: {
      NODE_ENV: app.stage === "production" ? "production" : "development",
      DATABASE_URL: process.env.DATABASE_URL!,
      UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL!,
      UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN!,
      GOOGLE_GEMINI_API_KEY: process.env.GOOGLE_GEMINI_API_KEY!,
      QSTASH_TOKEN: process.env.QSTASH_TOKEN!,
      DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL!,
      TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN!,
      TELEGRAM_CHAT_ID: process.env.TELEGRAM_CHAT_ID!,
    },
  });

  return { api };
}

SST Secrets

For sensitive values, use SST secrets:

# Set secret for specific stage
npx sst secrets set DATABASE_URL "postgresql://..." --stage production
npx sst secrets set UPSTASH_REDIS_REST_TOKEN "token" --stage production

# List secrets
npx sst secrets list --stage production

# Remove secret
npx sst secrets remove DATABASE_URL --stage production

Access in code:

import { Config } from "sst/node/config";

export const handler = async () => {
  const databaseUrl = Config.DATABASE_URL;
  // Use secret...
};

Define in SST config:

// infra/api.ts
import { Config } from "sst/constructs";

export function API({ stack }: StackContext) {
  const DATABASE_URL = new Config.Secret(stack, "DATABASE_URL");

  const api = new Function(stack, "api", {
    handler: "apps/api/src/index.handler",
    bind: [DATABASE_URL],
  });
}

TypeScript Type Safety

Declare Environment Variables

// env.d.ts (root or app-specific)
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      // Database
      DATABASE_URL: string;

      // Redis
      UPSTASH_REDIS_REST_URL: string;
      UPSTASH_REDIS_REST_TOKEN: string;

      // AI
      GOOGLE_GEMINI_API_KEY: string;
      GEMINI_MODEL?: string;

      // Workflows
      QSTASH_TOKEN: string;

      // Social Media
      DISCORD_WEBHOOK_URL: string;
      TELEGRAM_BOT_TOKEN: string;
      TELEGRAM_CHAT_ID: string;

      // Storage
      BLOB_READ_WRITE_TOKEN: string;

      // Next.js Public
      NEXT_PUBLIC_API_URL: string;
      NEXT_PUBLIC_SITE_URL: string;
      NEXT_PUBLIC_GA_MEASUREMENT_ID?: string;

      // Build
      NODE_ENV: "development" | "production" | "test";
    }
  }
}

export {};

Validate Environment Variables

// lib/env.ts
import { z } from "zod";

const envSchema = z.object({
  // Database
  DATABASE_URL: z.string().url(),

  // Redis
  UPSTASH_REDIS_REST_URL: z.string().url(),
  UPSTASH_REDIS_REST_TOKEN: z.string().min(1),

  // AI
  GOOGLE_GEMINI_API_KEY: z.string().min(1),

  // Workflows
  QSTASH_TOKEN: z.string().min(1),

  // Social Media
  DISCORD_WEBHOOK_URL: z.string().url().optional(),
  TELEGRAM_BOT_TOKEN: z.string().optional(),
  TELEGRAM_CHAT_ID: z.string().optional(),

  // Next.js
  NEXT_PUBLIC_API_URL: z.string().url(),
  NEXT_PUBLIC_SITE_URL: z.string().url(),

  // Build
  NODE_ENV: z.enum(["development", "production", "test"]),
});

export const env = envSchema.parse(process.env);

// Usage
import { env } from "./lib/env";
const db = new Database(env.DATABASE_URL);

Loading Environment Variables

Next.js

Next.js automatically loads .env.local:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
};

export default nextConfig;

Drizzle Studio

// packages/database/drizzle.config.ts
import { defineConfig } from "drizzle-kit";
import * as dotenv from "dotenv";

// Load environment variables
dotenv.config({ path: "../../.env.local" });

export default defineConfig({
  schema: "./src/db/schema",
  out: "./migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Vitest

// vitest.config.ts
import { defineConfig } from "vitest/config";
import * as dotenv from "dotenv";

// Load test environment
dotenv.config({ path: ".env.test" });

export default defineConfig({
  test: {
    env: {
      DATABASE_URL: process.env.DATABASE_URL!,
    },
  },
});

GitHub Actions

Secrets Configuration

Set secrets in GitHub:

  1. Go to repository Settings
  2. Secrets and variables → Actions
  3. Add repository secrets

Use in Workflows

# .github/workflows/deploy-prod.yml
name: Deploy Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
      UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL }}
      UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN }}
      GOOGLE_GEMINI_API_KEY: ${{ secrets.GOOGLE_GEMINI_API_KEY }}

    steps:
      - uses: actions/checkout@v4

      - name: Deploy
        run: pnpm deploy:prod

Debugging Environment Issues

Check Variables Are Set

// Check at runtime
console.log("DATABASE_URL:", process.env.DATABASE_URL ? "✓ Set" : "✗ Not set");
console.log("REDIS_URL:", process.env.UPSTASH_REDIS_REST_URL ? "✓ Set" : "✗ Not set");

// Fail early if missing
if (!process.env.DATABASE_URL) {
  throw new Error("DATABASE_URL is required");
}

Use dotenv-cli

# Install
pnpm add -D dotenv-cli

# Run command with env file
pnpm dotenv -e .env.local -- pnpm dev

# Run tests with test env
pnpm dotenv -e .env.test -- pnpm test

Debug Script

// scripts/check-env.ts
import * as dotenv from "dotenv";

dotenv.config({ path: ".env.local" });

const requiredVars = [
  "DATABASE_URL",
  "UPSTASH_REDIS_REST_URL",
  "UPSTASH_REDIS_REST_TOKEN",
];

console.log("Checking environment variables...\n");

let missing = 0;

for (const varName of requiredVars) {
  const value = process.env[varName];

  if (value) {
    console.log(`✓ ${varName}`);
  } else {
    console.log(`✗ ${varName} - MISSING`);
    missing++;
  }
}

if (missing > 0) {
  console.error(`\n❌ ${missing} required variables are missing!`);
  process.exit(1);
} else {
  console.log("\n✅ All required variables are set!");
}

Add to package.json:

{
  "scripts": {
    "check-env": "tsx scripts/check-env.ts"
  }
}

Common Patterns

Conditional Loading

// Load different configs based on environment
const config = {
  development: {
    apiUrl: "http://localhost:3000",
    debug: true,
  },
  production: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    debug: false,
  },
  test: {
    apiUrl: "http://localhost:3000",
    debug: false,
  },
}[process.env.NODE_ENV || "development"];

Default Values

const config = {
  apiUrl: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000",
  cacheTimeout: parseInt(process.env.CACHE_TIMEOUT || "3600", 10),
  enableFeature: process.env.ENABLE_FEATURE === "true",
};

Required vs Optional

// Required (throw if missing)
const databaseUrl = process.env.DATABASE_URL!;

// Optional (with default)
const cacheTimeout = process.env.CACHE_TIMEOUT || "3600";

// Optional (may be undefined)
const optionalKey: string | undefined = process.env.OPTIONAL_KEY;

Security Best Practices

1. Never Commit Secrets

# .gitignore
.env
.env.local
.env.*.local
.env.development.local
.env.test.local
.env.production.local

2. Use Environment-Specific Files

.env.example        ✅ Committed (no sensitive data)
.env.local          ❌ Not committed (has secrets)
.env.development    ❌ Not committed
.env.production     ❌ Not committed

3. Rotate Secrets Regularly

# Update secret for all stages
npx sst secrets set API_KEY "new-key" --stage dev
npx sst secrets set API_KEY "new-key" --stage staging
npx sst secrets set API_KEY "new-key" --stage production

4. Least Privilege

Only grant necessary permissions:

  • Database: Use read-only user for read operations
  • API keys: Use restricted scopes
  • AWS: Use IAM roles with minimal permissions

5. Audit Access

# Check who has access to secrets
npx sst secrets list --stage production

# Review CloudWatch logs for unauthorized access

Testing with Environment Variables

// __tests__/api.test.ts
import { describe, it, expect, beforeAll } from "vitest";

describe("API Tests", () => {
  beforeAll(() => {
    // Set test environment variables
    process.env.DATABASE_URL = "postgresql://test:test@localhost:5432/test";
    process.env.UPSTASH_REDIS_REST_URL = "https://test.upstash.io";
  });

  it("uses correct environment", () => {
    expect(process.env.DATABASE_URL).toContain("test");
  });
});

Troubleshooting

Variables Not Loading

Issue: Environment variables are undefined Solutions:

  1. Check file name is exactly .env.local
  2. Restart development server
  3. Verify file is in correct directory
  4. Check for syntax errors in .env file

Next.js Public Variables

Issue: NEXT_PUBLIC_ variables not available in browser Solutions:

  1. Ensure variable starts with NEXT_PUBLIC_
  2. Restart Next.js dev server
  3. Check browser console for actual value

SST Secrets Not Working

Issue: SST secrets return undefined Solutions:

  1. Verify secret is set for correct stage
  2. Check bind configuration in SST
  3. Use Config import from sst/node/config

References

Best Practices

  1. Use .env.example: Provide template for required variables
  2. Type Safety: Define types for environment variables
  3. Validation: Validate on application startup
  4. SST Secrets: Use for sensitive production values
  5. Never Commit: Add .env.local to .gitignore
  6. Document: Comment what each variable is for
  7. Stage-Specific: Use different values per environment
  8. Fail Fast: Validate required variables at startup