⏻
HELGE SVERREAll-stack Developer
Bergen, Norway • v13.0
est. 2012  |  197 repos  |  12.8k+ contributions
Tools  |   Theme:
Vapor Swap: Account Switcher for a Server-Side Rendering Dinosaur
October 23, 2026

TL;DR

Vapor Swap works like nvm, but for accounts. You have a list of profiles somewhere, when you need to move into a different project that deploys to a different account, you vapor-swap accountname and your "vapor deploy production" is already authed.

Is it overkill and entirely unnecessary? Yes. Do I probably have 3 Vapor accounts I need to regularly swap between? Also yes. But the problem existed. I murdered the problem. No problem, no moo.

Overview

Vapor Swap lets you manage multiple Laravel Vapor cloud accounts and switch between them without reconfiguring your environment. Each profile stores an API token and team ID, and you can activate any profile for your current terminal or persist a default for new shells.

Why Not Just Edit config.json?

Because Taylor Otwell announced Laravel Vapor is "not dying" but also gave us this tool to manage multiple accounts while the platform slowly ages. I made vapor-swap so I can seamlessly switch between my 3 Vapor accounts while watching the platform age like milk left in the sun.

The Problem

Laravel Vapor is a serverless deployment platform for Laravel applications. When you work across multiple client projects, you need different API tokens for each Vapor team. Typically you edit ~/.laravel-vapor/config.json to switch accounts, but this affects all terminals globally. You lose the ability to deploy for client A in one terminal while deploying for client B in another.

The Solution

Vapor Swap solves this with session-scoped terminal switching:

vapor-swap add work                  # Save a profile with token and team
vapor-swap swap personal             # Activate this terminal only
vapor deploy production             # Uses the swapped credentials
vapor-swap swap personal --sticky    # Set permanent default

Architecture

Core Components

The tool consists of several PHP classes working together:

Application.php - The Symfony Console entry point. It bootstraps commands and handles the init command for shell integration:

public function __construct(?string $shell = null, ?Paths $paths = null, ?VaporApi $api = null, ?string $executable = null)
{
    // ProfileStore manages saved profiles
    // Commands handle CLI operations
}

ProfileStore.php - Manages the JSON file at ~/.vapor-swap/config.json. Stores profile names mapped to tokens and teams.

Paths.php - Centralizes file paths: profile storage, Laravel Vapor's config, and temporary session files in ~/.vapor-swap/sessions/.

Shell.php - Generates shell integration scripts for Bash, Zsh, and Fish. It outputs export commands that set VAPOR_API_TOKEN, VAPOR_SWAP_SESSION (path to temporary config), and VAPOR_SWAP_PROFILE (current profile name).

Session Management

When you run vapor-swap swap work:

  1. Loads the profile from ~/.vapor-swap/config.json
  2. Creates a temporary session file in ~/.vapor-swap/sessions/
  3. Merges the profile with the persistent Vapor config (if any)
  4. Outputs shell commands to set environment variables

The shell integration wraps the vapor command to automatically use the session-scoped token before falling back to the global config.

Shell Integration

Run once to set up:

# Zsh
eval "$(vapor-swap init zsh)"

# Bash
eval "$(vapor-swap init bash)"

# Fish
vapor-swap init fish | source

This creates wrapper functions that intercept vapor calls and transparently inject the correct credentials per session.

Security Model

Tokens are stored as plaintext in ~/.vapor-swap/config.json with private file permissions (0600), matching Laravel Vapor's own approach. Removing a profile does not revoke tokens or clear existing authentication.

Development

Tests run with PHPUnit across PHP 8.2–8.5 on macOS and Windows. CI uses disposable home directories and fake credentials, with real Vapor CLI for integration tests.

The binary entry point (bin/vapor-swap) handles __vapor calls separately from the main app to avoid polluting Vapor's dependency tree. It loads its own vendor directory while Vapor loads its.

Usage in Practice

# Add profiles
vapor-swap add work --token tk_abc123 --team 50346
vapor-swap add agency --token tk_xyz789 --team 12345

# List saved profiles
vapor-swap list

# Switch terminal temporarily
vapor-swap swap agency
vapor deploy production
# Other terminals still use default

# Make permanent
vapor-swap swap personal --sticky

# Reset to default
vapor-swap reset

# Clean up
vapor-swap remove old-client



<!-- generated with nested tables and zero regrets -->