Skip to main content
command

vercel-env-sync — Claude Code command: what it does, how to install

Synchronizes environment variables between local .env files and Vercel deployments via the Vercel CLI. Handles pulling, pushing, backups, and format checks.

Install into Claude Code · claude-code-templates All methods and platforms ↓
npx claude-code-templates@latest --command nextjs-vercel/vercel-env-sync
Open on GitHub

What vercel-env-sync does

This command automates environment variable management across local files (.env.local, .env.development, .env.staging, .env.production) and Vercel projects. It supports four flags: --pull to retrieve remote values, --push to upload local definitions, --validate to inspect variable syntax, and --backup to preserve existing states.

When pulling, it creates a timestamped copy of .env.local before writing changes. When pushing, staging files map directly to Vercel preview environments, stripping outer quotation marks and applying keys via vercel env add --force. Built-in validation checks for essential keys like DATABASE_URL or NEXTAUTH_SECRET and flags default placeholders such as change-me.

Without a working Vercel CLI installation and an active project link, the command fails immediately.

What it helps with

  • pull environment variables from Vercel into .env.local
  • push local environment files to Vercel production, preview, or development targets
  • back up existing .env files with timestamps before syncing
  • validate required keys and detect placeholder secrets like change-me

Example prompts for the agent

  • Pull remote variables to your local project using /vercel-env-sync --pull
  • Audit environment variables for missing keys via /vercel-env-sync --validate
  • Push production secrets to Vercel with /vercel-env-sync --push

How to install vercel-env-sync

1claude-code-templates

npx claude-code-templates@latest --command nextjs-vercel/vercel-env-sync

2Manually: put the files in place

.claude/commands/vercel-env-sync.md

Call it with /vercel-env-sync. For all projects use ~/.claude/commands/

Questions about vercel-env-sync

How to install vercel-env-sync in Claude Code?

Run npx claude-code-templates@latest --command nextjs-vercel/vercel-env-sync in your project terminal, or place the template directly at .claude/commands/vercel-env-sync.md.

What prerequisites are needed for vercel-env-sync?

You need the Vercel CLI installed and your repository linked to an active Vercel project so the CLI can query environment settings.

How does the command handle .env.staging during push?

The script automatically maps variables from .env.staging to Vercel's preview environment target.

Source text

vercel-env-sync.md · MIT
---
allowed-tools: Read, Write, Edit, Bash
argument-hint: "[--pull] [--push] [--validate] [--backup]"
description: Synchronize environment variables between local development and Vercel deployments
---

## Vercel Environment Sync

**Sync Operation**: $ARGUMENTS

## Current Environment Analysis

### Local Environment
- Environment files: 
  - @.env.local (if exists)
  - @.env.development (if exists)
  - @.env.production (if exists)
  - @.env (if exists)
- Environment example: @.env.example (if exists)
- Vercel config: @vercel.json (if exists)

### Project Status
- Vercel CLI status: !`vercel --version 2>/dev/null || echo "Vercel CLI not installed"`
- Current project: !`vercel project ls 2>/dev/null | head -5 || echo "Not linked to Vercel project"`
- Git status: !`git status --porcelain | head -5`

## Environment Synchronization Strategy

### 1. Environment File Analysis
```typescript
// Environment file structure analysis
interface EnvironmentConfig {
  development: Record<string, string>;
  preview: Record<string, string>;
  production: Record<string, string>;
}

const environmentFiles = {
  '.env.local': 'Local development overrides',
  '.env.development': 'Development environment',
  '.env.staging': 'Staging/preview environment', 
  '.env.production': 'Production environment',
  '.env': 'Default environment (committed to git)',
  '.env.example': 'Environment template (safe to commit)',
};
```

### 2. Vercel Environment Management
```bash
# List all environment variables for all environments
vercel env ls

# List environment variables for specific environment
vercel env ls --environment=production
vercel env ls --environment=preview
vercel env ls --environment=development

# Pull environment variables from Vercel
vercel env pull .env.vercel

# Add new environment variable
vercel env add [name] [environment]

# Remove environment variable
vercel env rm [name] [environment]
```

## Synchronization Operations

### 1. Pull Environment Variables from Vercel
```bash
#!/bin/bash
# Pull environments from Vercel

echo "🔄 Pulling environment variables from Vercel..."

# Create backup of existing files
if [ -f .env.local ]; then
  cp .env.local .env.local.backup.$(date +%Y%m%d_%H%M%S)
  echo "📦 Backup created for .env.local"
fi

# Pull from Vercel (creates .env.local by default)
vercel env pull .env.local

if [ $? -eq 0 ]; then
  echo "✅ Successfully pulled environment variables"
  echo "📁 Variables saved to .env.local"
  
  # Show summary
  echo ""
  echo "📊 Environment Variables Summary:"
  echo "================================"
  grep -c "=" .env.local 2>/dev/null && echo "Total variables: $(grep -c "=" .env.local)"
  
  # List variable names (hide values for security)
  echo ""
  echo "🔑 Variable Names:"
  grep "^[A-Z]" .env.local | cut -d'=' -f1 | sort
else
  echo "❌ Failed to pull environment variables"
  exit 1
fi
```

### 2. Push Environment Variables to Vercel
```bash
#!/bin/bash
# Push environment variables to Vercel

echo "🚀 Pushing environment variables to Vercel..."

# Check if environment files exist
ENV_FILES=(".env.production" ".env.staging" ".env.development")
FOUND_FILES=()

for file in "${ENV_FILES[@]}"; do
  if [ -f "$file" ]; then
    FOUND_FILES+=("$file")
  fi
done

if [ ${#FOUND_FILES[@]} -eq 0 ]; then
  echo "❌ No environment files found to push"
  echo "💡 Expected files: ${ENV_FILES[*]}"
  exit 1
fi

# Push each environment file
for file in "${FOUND_FILES[@]}"; do
  echo "📤 Processing $file..."
  
  # Determine target environment
  if [[ "$file" == *"production"* ]]; then
    ENV="production"
  elif [[ "$file" == *"staging"* ]]; then
    ENV="preview"  # Vercel uses 'preview' for staging
  elif [[ "$file" == *"development"* ]]; then
    ENV="development"
  else
    ENV="development"  # Default
  fi
  
  echo "🎯 Pushing to $ENV environment..."
  
  # Read variables from file and push to Vercel
  while IFS='=' read -r key value; do
    # Skip empty lines and comments
    if [[ -z "$key" || "$key" =~ ^#.* ]]; then
      continue
    fi
    
    # Remove quotes from value if present
    value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\(.*\)'$/\1/")
    
    echo "  🔑 Setting $key..."
    echo "$value" | vercel env add "$key" "$ENV" --force
    
  done < "$file"
  
  echo "✅ Completed $file -> $ENV"
  echo ""
done

echo "🎉 All environment variables pushed successfully!"
```

### 3. Environment Validation
```typescript
// Environment validation script
interface ValidationRule {
  name: string;
  required: boolean;
  pattern?: RegExp;
  description: string;
}

const validationRules: ValidationRule[] = [
  {
    name: 'DATABASE_URL',
    required: true,
    pattern: /^(postgresql|mysql|sqlite):\/\/.+/,
    description: 'Database connection string',
  },
  {
    name: 'NEXTAUTH_SECRET',
    required: true,
    pattern: /.{32,}/,
    description: 'NextAuth.js secret key (min 32 characters)',
  },
  {
    name: 'NEXTAUTH_URL',
    required: true,
    pattern: /^https?:\/\/.+/,
    description: 'NextAuth.js canonical URL',
  },
  {
    name: 'API_KEY',
    required: false,
    pattern: /^[A-Za-z0-9_-]+$/,
    description: 'API key for external services',
  },
];

function validateEnvironment(envFile: string): ValidationResult {
  const errors: string[] = [];
  const warnings: string[] = [];
  const env = readEnvironmentFile(envFile);
  
  // Check required variables
  validationRules.forEach(rule => {
    const value = env[rule.name];
    
    if (rule.required && !value) {
      errors.push(`Missing required variable: ${rule.name}`);
      return;
    }
    
    if (value && rule.pattern && !rule.pattern.test(value)) {
      errors.push(`Invalid format for ${rule.name}: ${rule.description}`);
    }
  });
  
  // Check for common issues
  Object.entries(env).forEach(([key, value]) => {
    // Check for placeholder values
    if (value === 'your-secret-here' || value === 'change-me') {
      warnings.push(`Placeholder value detected for ${key}`);
    }
    
    // Check for potentially committed secrets
    if (key.includes('SECRET') || key.includes('PRIVATE')) {
      if (value.length < 16) {
        warnings.push(`${key} appears to be too short for a secret`);
      }
    }
  });
  
  return {
    valid: errors.length === 0,
    errors,
    warnings,
  };
}

function readEnvironmentFile(filePath: string): Record<string, string> {
  // Implementation to read and parse environment file
  return {};
}

interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}
```

### 4. Environment Backup and Restore
```bash
#!/bin/bash
# Backup and restore environment variables

BACKUP_DIR=".env-backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

backup_environment() {
  echo "📦 Creating environment backup..."
  
  mkdir -p "$BACKUP_DIR"
  
  # Backup local files
  for file in .env.local .env.development .env.staging .env.production; do
    if [ -f "$file" ]; then
      cp "$file" "$BACKUP_DIR/${file}.${TIMESTAMP}"
      echo "✅ Backed up $file"
    fi
  done
  
  # Backup Vercel environment variables
  echo "📤 Backing up Vercel environment variables..."
  
  for env in production preview development; do
    vercel env ls --environment="$env" > "$BACKUP_DIR/vercel-${env}.${TIMESTAMP}.txt"
    echo "✅ Backed up Vercel $env environment"
  done
  
  echo "🎉 Backup completed in $BACKUP_DIR/"
  ls -la "$BACKUP_DIR/" | grep "$TIMESTAMP"
}

restore_environment() {
  local backup_timestamp="$1"
  
  if [ -z "$backup_timestamp" ]; then
    echo "❌ Please specify backup timestamp"
    echo "💡 Available backups:"
    ls -1 "$BACKUP_DIR/" | grep -E "\.env" | cut -d'.' -f3 | sort -u
    exit 1
  fi
  
  echo "🔄 Restoring environment from backup $backup_timestamp..."
  
  # Restore local files
  for file in .env.local .env.development .env.staging .env.production; do
    backup_file="$BACKUP_DIR/${file}.${backup_timestamp}"
    if [ -f "$backup_file" ]; then
      cp "$backup_file" "$file"
      echo "✅ Restored $file"
    fi
  done
  
  echo "🎉 Environment restored from backup"
}

# Usage functions
case "$1" in
  backup)
    backup_environment
    ;;
  restore)
    restore_environment "$2"
    ;;
  *)
    echo "Usage: $0 {backup|restore} [timestamp]"
    exit 1
    ;;
esac
```

## Advanced Synchronization Features

### 1. Environment Diff and Comparison
```typescript
// Environment comparison tool
interface EnvironmentDiff {
  added: string[];
  removed: string[];
  modified: Array<{
    key: string;
    local: string;
    remote: string;
  }>;
  unchanged: string[];
}

function compareEnvironments(
  local: Record<string, string>,
  remote: Record<string, string>
): EnvironmentDiff {
  const diff: EnvironmentDiff = {
    added: [],
    removed: [],
    modified: [],
    unchanged: [],
  };
  
  const allKeys = new Set([...Object.keys(local), ...Object.keys(remote)]);
  
  allKeys.forEach(key => {
    if (!(key in local)) {
      diff.added.push(key);
    } else if (!(key in remote)) {
      diff.removed.push(key);
    } else if (local[key] !== remote[key]) {
      diff.modified.push({
        key,
        local: local[key],
        remote: remote[key],
      });
    } else {
      diff.unchanged.push(key);
    }
  });
  
  return diff;
}

// Generate diff report
function generateDiffReport(diff: EnvironmentDiff): string {
  let report = '# Environment Variables Comparison\n\n';
  
  if (diff.added.length > 0) {
    report += '## ➕ Variables in Remote (not in Local)\n';
    diff.added.forEach(key => {
      report += `- \`${key}\`\n`;
    });
    report += '\n';
  }
  
  if (diff.removed.length > 0) {
    report += '## ➖ Variables in Local (not in Remote)\n';
    diff.removed.forEach(key => {
      report += `- \`${key}\`\n`;
    });
    report += '\n';
  }
  
  if (diff.modified.length > 0) {
    report += '## 🔄 Modified Variables\n';
    diff.modified.forEach(({ key, local, remote }) => {
      report += `### \`${key}\`\n`;
      report += `- **Local**: \`${maskSensitive(local)}\`\n`;
      report += `- **Remote**: \`${maskSensitive(remote)}\`\n\n`;
    });
  }
  
  if (diff.unchanged.length > 0) {
    report += `## ✅ Unchanged Variables (${diff.unchanged.length})\n`;
    report += `${diff.unchanged.map(key => `- \`${key}\``).join('\n')}\n\n`;
  }
  
  return report;
}

function maskSensitive(value: string): string {
  // Mask sensitive values for security
  if (value.length <= 8) {
    return '*'.repeat(value.length);
  }
  return `${value.substring(0, 4)}${'*'.repeat(value.length - 8)}${value.substring(value.length - 4)}`;
}
```

### 2. Environment Template Generation
```typescript
// Generate .env.example from existing environment
function generateEnvExample(envFile: string): string {
  const env = readEnvironmentFile(envFile);
  let template = '# Environment Variables Template\n';
  template += '# Copy this file to .env.local and fill in the values\n\n';
  
  const categories = categorizeVariables(env);
  
  Object.entries(categories).forEach(([category, variables]) => {
    template += `# ${category.toUpperCase()}\n`;
    variables.forEach(({ key, description, example }) => {
      if (description) {
        template += `# ${description}\n`;
      }
      template += `${key}=${example || 'your-value-here'}\n\n`;
    });
  });
  
  return template;
}

function categorizeVariables(env: Record<string, string>) {
  const categories: Record<string, Array<{
    key: string;
    description?: string;
    example?: string;
  }>> = {
    database: [],
    authentication: [],
    external_apis: [],
    configuration: [],
  };
  
  Object.keys(env).forEach(key => {
    if (key.includes('DATABASE') || key.includes('DB_')) {
      categories.database.push({ key, description: getDatabaseDescription(key) });
    } else if (key.includes('AUTH') || key.includes('SECRET')) {
      categories.authentication.push({ key, description: getAuthDescription(key) });
    } else if (key.includes('API_KEY') || key.includes('_TOKEN')) {
      categories.external_apis.push({ key, description: getApiDescription(key) });
    } else {
      categories.configuration.push({ key, description: getConfigDescription(key) });
    }
  });
  
  return categories;
}

function getDatabaseDescription(key: string): string {
  if (key === 'DATABASE_URL') return 'Database connection string';
  if (key === 'DB_HOST') return 'Database host';
  if (key === 'DB_PORT') return 'Database port';
  if (key === 'DB_NAME') return 'Database name';
  return 'Database configuration';
}

function getAuthDescription(key: string): string {
  if (key === 'NEXTAUTH_SECRET') return 'NextAuth.js secret key';
  if (key === 'NEXTAUTH_URL') return 'NextAuth.js canonical URL';
  if (key === 'JWT_SECRET') return 'JWT secret key';
  return 'Authentication configuration';
}

function getApiDescription(key: string): string {
  return `API key for ${key.toLowerCase().replace(/_/g, ' ')}`;
}

function getConfigDescription(key: string): string {
  return `Configuration for ${key.toLowerCase().replace(/_/g, ' ')}`;
}
```

### 3. Security and Validation
```bash
#!/bin/bash
# Security checks for environment variables

security_check() {
  echo "🔐 Running security checks on environment variables..."
  
  local issues=0
  
  # Check for common security issues
  for file in .env.local .env.development .env.staging .env.production; do
    if [ ! -f "$file" ]; then
      continue
    fi
    
    echo "🔍 Checking $file..."
    
    # Check for weak secrets
    while IFS='=' read -r key value; do
      if [[ -z "$key" || "$key" =~ ^#.* ]]; then
        continue
      fi
      
      # Remove quotes
      value=$(echo "$value" | sed 's/^"\(.*\)"$/\1/' | sed "s/^'\(.*\)'$/\1/")
      
      # Check for placeholder values
      if [[ "$value" == *"your-"* || "$value" == *"change-me"* || "$value" == *"replace-me"* ]]; then
        echo "⚠️  Placeholder value in $key"
        ((issues++))
      fi
      
      # Check for short secrets
      if [[ "$key" =~ (SECRET|PRIVATE|KEY|TOKEN) ]]; then
        if [ ${#value} -lt 16 ]; then
          echo "⚠️  $key appears to be too short for a secret (${#value} characters)"
          ((issues++))
        fi
      fi
      
      # Check for hardcoded URLs in production
      if [[ "$file" == *"production"* && "$value" =~ localhost ]]; then
        echo "⚠️  $key contains localhost in production environment"
        ((issues++))
      fi
      
    done < "$file"
  done
  
  # Check if .env files are in .gitignore
  if [ -f .gitignore ]; then
    if ! grep -q ".env.local" .gitignore; then
      echo "⚠️  .env.local not in .gitignore"
      ((issues++))
    fi
    if ! grep -q ".env.production" .gitignore; then
      echo "⚠️  .env.production not in .gitignore"
      ((issues++))
    fi
  else
    echo "⚠️  No .gitignore file found"
    ((issues++))
  fi
  
  echo ""
  if [ $issues -eq 0 ]; then
    echo "✅ No security issues found"
  else
    echo "❌ Found $issues security issues"
    exit 1
  fi
}

security_check
```

## Automation and Integration

### 1. GitHub Actions Integration
```yaml
# .github/workflows/env-sync.yml
name: Environment Sync

on:
  push:
    branches: [main, develop]
    paths: ['.env.example', '.env.*']
  
  workflow_dispatch:
    inputs:
      action:
        description: 'Sync action'
        required: true
        default: 'validate'
        type: choice
        options:
        - validate
        - pull
        - push

jobs:
  env-sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Vercel CLI
        run: npm i -g vercel@latest
      
      - name: Link to Vercel
        run: vercel link --token=${{ secrets.VERCEL_TOKEN }} --yes
      
      - name: Validate Environment
        if: github.event.inputs.action == 'validate' || github.event.inputs.action == ''
        run: |
          # Run environment validation
          node scripts/validate-env.js
      
      - name: Pull Environment
        if: github.event.inputs.action == 'pull'
        run: |
          vercel env pull .env.ci --token=${{ secrets.VERCEL_TOKEN }}
          # Validate pulled environment
          node scripts/validate-env.js .env.ci
      
      - name: Push Environment
        if: github.event.inputs.action == 'push'
        run: |
          # Push environment variables to Vercel
          node scripts/push-env.js --token=${{ secrets.VERCEL_TOKEN }}
```

### 2. Development Workflow Integration
```bash
#!/bin/bash
# Pre-commit hook for environment validation

echo "🔍 Validating environment variables..."

# Check if environment files are properly configured
if [ -f .env.local ]; then
  echo "✅ .env.local exists"
else
  echo "⚠️  .env.local not found"
  if [ -f .env.example ]; then
    echo "💡 Copy .env.example to .env.local and configure your variables"
  fi
fi

# Validate environment structure
node scripts/validate-env.js

if [ $? -ne 0 ]; then
  echo "❌ Environment validation failed"
  exit 1
fi

echo "✅ Environment validation passed"
```

## Comprehensive Sync Operations

Implement complete environment synchronization workflow with validation, backup, security checks, and automation integration for seamless development and deployment processes.

<function_calls>
<invoke name="TodoWrite">
<parameter name="todos">[{"content": "Analyze hook system and context monitoring capabilities", "status": "completed"}, {"content": "Design context threshold detection mechanism", "status": "completed"}, {"content": "Create hook script for automatic compact + clear sequence", "status": "pending"}, {"content": "Configure hook in settings.json with proper event triggers", "status": "pending"}, {"content": "Test hook execution and threshold detection", "status": "pending"}, {"content": "Add markdown copy button to Supabase blog post", "status": "completed"}, {"content": "Write Claude Code + Next.js and Vercel Integration article", "status": "completed"}, {"content": "Create Next.js Architecture Expert agent", "status": "completed"}, {"content": "Create Vercel Deployment Specialist agent", "status": "completed"}, {"content": "Create React Performance Optimizer agent", "status": "completed"}, {"content": "Create Next.js app scaffolding command", "status": "completed"}, {"content": "Create Vercel deployment optimization command", "status": "completed"}, {"content": "Create component generator command", "status": "completed"}, {"content": "Create API route tester command", "status": "completed"}, {"content": "Create bundle analyzer command", "status": "completed"}, {"content": "Create middleware creator command", "status": "completed"}, {"content": "Create edge function generator command", "status": "completed"}, {"content": "Create performance audit command", "status": "completed"}, {"content": "Create environment sync command", "status": "completed"}, {"content": "Create migration helper command", "status": "in_progress"}]

© davila7 · MIT · davila7/claude-code-templates

Similar slash commands

All slash commands for DevOps and infrastructure →

From the same set495

The whole set →

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor