emulateby vercel
Local drop-in API emulator for Vercel, GitHub, Google, Slack, Apple, Microsoft, and AWS. Use when the user needs to start emulated services, configure seed…
npx skills add https://github.com/vercel-labs/emulate --skill emulateService Emulation with emulate
Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation, not mocks.
Quick Start
npx emulate
All services start with sensible defaults:
| Service | Default Port |
|---|---|
| Vercel | 4000 |
| GitHub | 4001 |
| 4002 | |
| Slack | 4003 |
| Apple | 4004 |
| Microsoft | 4005 |
| AWS | 4006 |
CLI
# Start all services (zero-config)
npx emulate
# Start specific services
npx emulate --service vercel,github
# Custom base port (auto-increments per service)
npx emulate --port 3000
# Use a seed config file
npx emulate --seed config.yaml
# Generate a starter config
npx emulate init
# Generate config for a specific service
npx emulate init --service vercel
# List available services
npx emulate list
Options
| Flag | Default | Description |
|---|---|---|
-p, --port | 4000 | Base port (auto-increments per service) |
-s, --service | all | Comma-separated services to enable |
--seed | auto-detect | Path to seed config (YAML or JSON) |
--base-url | none | Override advertised base URL (supports {service} template) |
--portless | off | Serve over HTTPS via portless (auto-registers aliases) |
The port can also be set via EMULATE_PORT or PORT environment variables.
The advertised base URL (used in OAuth redirects, webhook URLs, etc.) can be overridden via --base-url, the EMULATE_BASE_URL env var (supports {service} template), or per-service baseUrl in the seed config. When running under portless, the PORTLESS_URL env var is also detected automatically.
Programmatic API
npm install emulate
Each call to createEmulator starts a single service:
import { createEmulator } from 'emulate'
const github = await createEmulator({ service: 'github', port: 4001 })
const vercel = await createEmulator({ service: 'vercel', port: 4002 })
github.url // 'http://localhost:4001'
vercel.url // 'http://localhost:4002'
await github.close()
await vercel.close()
Options
| Option | Default | Description |
|---|---|---|
service | (required) | 'vercel', 'github', 'google', 'slack', 'apple', 'microsoft', or 'aws' |
port | 4000 | Port for the HTTP server |
seed | none | Inline seed data (same shape as YAML config) |
baseUrl | none | Override advertised base URL. Per-service baseUrl in seed config takes highest priority, then this option, then EMULATE_BASE_URL env var (supports {service}), then PORTLESS_URL (supports {service}, automatically set by the portless CLI wrapper), then http://localhost:<port>. |
Instance Methods
| Method | Description |
|---|---|
url | Base URL of the running server |
reset() | Wipe the store and replay seed data |
close() | Shut down the HTTP server, returns a Promise |
Vitest / Jest Setup
import { createEmulator, type Emulator } from 'emulate'
let github: Emulator
let vercel: Emulator
beforeAll(async () => {
;[github, vercel] = await Promise.all([
createEmulator({ service: 'github', port: 4001 }),
createEmulator({ service: 'vercel', port: 4002 }),
])
process.env.GITHUB_EMULATOR_URL = github.url
process.env.VERCEL_EMULATOR_URL = vercel.url
})
afterEach(() => { github.reset(); vercel.reset() })
afterAll(() => Promise.all([github.close(), vercel.close()]))
Configuration
Configuration is optional. The CLI auto-detects config files in this order:
emulate.config.yaml/.ymlemulate.config.jsonservice-emulator.config.yaml/.ymlservice-emulator.config.json
Or pass --seed <file> explicitly. Run npx emulate init to generate a starter file.
Config Structure
tokens:
my_token:
login: admin
scopes: [repo, user]
vercel:
users:
- username: developer
name: Developer
email: [email protected]
teams:
- slug: my-team
name: My Team
projects:
- name: my-app
team: my-team
framework: nextjs
integrations:
- client_id: oac_abc123
client_secret: secret_abc123
name: My Vercel App
redirect_uris:
- http://localhost:3000/api/auth/callback/vercel
github:
users:
- login: octocat
name: The Octocat
email: [email protected]
orgs:
- login: my-org
name: My Organization
repos:
- owner: octocat
name: hello-world
language: JavaScript
auto_init: true
oauth_apps:
- client_id: Iv1.abc123
client_secret: secret_abc123
name: My Web App
redirect_uris:
- http://localhost:3000/api/auth/callback/github
google:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
redirect_uris:
- http://localhost:3000/api/auth/callback/google
slack:
team:
name: My Workspace
domain: my-workspace
users:
- name: developer
real_name: Developer
email: [email protected]
channels:
- name: general
topic: General discussion
bots:
- name: my-bot
oauth_apps:
- client_id: "12345.67890"
client_secret: example_client_secret
name: My Slack App
redirect_uris:
- http://localhost:3000/api/auth/callback/slack
apple:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: com.example.app
team_id: TEAM001
name: My Apple App
redirect_uris:
- http://localhost:3000/api/auth/callback/apple
microsoft:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: example-client-id
client_secret: example-client-secret
name: My Microsoft App
redirect_uris:
- http://localhost:3000/api/auth/callback/microsoft-entra-id
aws:
region: us-east-1
s3:
buckets:
- name: my-app-bucket
sqs:
queues:
- name: my-app-events
iam:
users:
- user_name: developer
create_access_key: true
roles:
- role_name: lambda-execution-role
Auth
Tokens map to users. Pass them as Authorization: Bearer <token> or Authorization: token <token>. When no tokens are configured, a default test_token_admin is created for the admin user.
Each service also has a fallback user. If no token is provided, requests authenticate as the first seeded user.
HTTPS with portless
portless gives emulators trusted HTTPS URLs with auto-generated certs. Use the --portless flag to auto-register each service as a portless alias:
npx emulate start --portless
# github https://github.emulate.localhost
# google https://google.emulate.localhost
# ...
This requires the portless proxy to be running (portless proxy start). If portless is not installed, emulate will prompt to install it.
The --portless flag overwrites any existing portless aliases matching *.emulate. Aliases are removed automatically when emulate shuts down.
For a single service behind portless:
portless github.emulate emulate start --service github
For a custom base URL without portless (any reverse proxy):
npx emulate start --base-url "https://{service}.myproxy.test"
# or
EMULATE_BASE_URL="https://{service}.myproxy.test" npx emulate start
The PORTLESS_URL env var is automatically set by the portless CLI wrapper when running a command through it (e.g. portless github.emulate emulate start), typically to a value like https://{service}.emulate.localhost. It supports {service} interpolation, just like --base-url and EMULATE_BASE_URL. When no explicit baseUrl is provided, it is used as a fallback.
Per-service overrides in the seed config (these take highest priority over all other base URL sources):
github:
baseUrl: https://github.emulate.localhost
google:
baseUrl: https://google.emulate.localhost
Pointing Your App at the Emulator
Set environment variables to override real service URLs:
VERCEL_EMULATOR_URL=http://localhost:4000
GITHUB_EMULATOR_URL=http://localhost:4001
GOOGLE_EMULATOR_URL=http://localhost:4002
SLACK_EMULATOR_URL=http://localhost:4003
APPLE_EMULATOR_URL=http://localhost:4004
MICROSOFT_EMULATOR_URL=http://localhost:4005
AWS_EMULATOR_URL=http://localhost:4006
Then use these in your app to construct API and OAuth URLs. See each service's skill for SDK-specific override instructions.
Next.js Integration (Embedded Mode)
The @emulators/adapter-next package embeds emulators directly into a Next.js app on the same origin. See the next skill (skills/next/SKILL.md) for full setup, Auth.js configuration, persistence, and font tracing details.
Persistence
By default, all emulator state is in-memory. For persistence across process restarts and serverless cold starts, use a PersistenceAdapter.
Built-in file persistence
import { filePersistence } from '@emulators/core'
// CLI or local dev: persists to a JSON file
const adapter = filePersistence('.emulate/state.json')
Custom adapters
import type { PersistenceAdapter } from '@emulators/core'
const kvAdapter: PersistenceAdapter = {
async load() { return await kv.get('emulate-state') },
async save(data) { await kv.set('emulate-state', data) },
}
State is loaded on cold start and saved after every mutating request (POST, PUT, PATCH, DELETE). Saves are serialized to prevent race conditions.
Architecture
packages/
emulate/ # CLI entry point + programmatic API
@emulators/
core/ # HTTP server, Store, plugin interface, middleware
adapter-next/ # Next.js App Router integration
vercel/ # Vercel API service plugin
github/ # GitHub API service plugin
google/ # Google OAuth 2.0 / OIDC plugin
slack/ # Slack Web API, OAuth, incoming webhooks plugin
apple/ # Sign in with Apple / OIDC plugin
microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC plugin
aws/ # AWS S3, SQS, IAM, STS plugin
The core provides a generic Store with typed Collection<T> instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers routes with the shared internal app and uses the store for state.