Docs

Getting Started

Expose your local development server to the internet in 30 seconds. No account required.


Prerequisites

  • Node.js 20+ — check with node --version
  • A local server running on any port (e.g., localhost:3000)

Install

Option A: Global install (recommended)

npm install -g workslocal

This gives you the workslocal command globally. Verify with:

workslocal --version
# WorksLocal v0.1.1

Option B: Run without installing

npx workslocal http 3000

Uses npx to download and run WorksLocal in one step. Good for trying it out.


Create your first tunnel

Make sure your local server is running, then:

workslocal http 3000

Replace 3000with whatever port your server is on. You'll see:

────────────────────────────────────────────────────────────

✔ Tunnel is live!

Public URL:   https://a8f3k2.workslocal.exposed
Forwarding:   http://localhost:3000
Inspector:    http://localhost:4040
Subdomain:    a8f3k2
Domain:       workslocal.exposed
Type:         ephemeral

Press Ctrl+C to stop the tunnel

────────────────────────────────────────────────────────────

GET     / 200 62ms

That https://...workslocal.exposed URL is publicly accessible from anywhere on the internet. Share it with a teammate, paste it in a webhook dashboard, or open it on your phone.


Custom subdomain

By default, WorksLocal assigns a random subdomain. To pick your own:

workslocal http 3000 --name myapp

This gives you https://myapp.workslocal.exposed — the same URL every time you start the tunnel. Custom subdomains are persistent across restarts for authenticated users.

Subdomain rules

  • Lowercase letters, numbers, and hyphens only
  • 3–50 characters
  • Cannot start or end with a hyphen
  • Reserved names (www, api, admin, mail, etc.) are blocked

Anonymous vs authenticated

Anonymous (no account)

Works immediately. You get:

  • Random subdomain (e.g., a8f3k2.workslocal.exposed)
  • Custom subdomains with --name (reserved for 30 minutes after disconnect)
  • Up to 2 simultaneous tunnels
  • Anonymous tunnels expire after 2 hours
  • Web inspector at localhost:4040
  • Full HTTP forwarding + catch mode

Authenticated (free account)

Sign in to get:

  • Persistent subdomains — your --name survives restarts permanently
  • Up to 5 simultaneous tunnels
  • 30-day stale cleanup (unused subdomains released after 30 days)

To authenticate:

workslocal login

This opens your browser to complete the sign-in flow via GitHub OAuth (Clerk). Your token is stored locally at ~/.workslocal/config.json.

To check your auth status:

workslocal whoami

Use with different frameworks

WorksLocal works with any local server — it doesn't care what framework you're using.

Next.js

# Terminal 1
npm run dev
# → localhost:3000

# Terminal 2
workslocal http 3000

Express / Fastify / Koa

# Terminal 1
node server.js
# → localhost:4000

# Terminal 2
workslocal http 4000

Python (Django / Flask / FastAPI)

# Terminal 1
python manage.py runserver 8000

# Terminal 2
workslocal http 8000

Go / Rust / Java / PHP

Same pattern — start your server on a port, point WorksLocal at that port.


Stop the tunnel

Press Ctrl+C in the terminal. The tunnel closes gracefully — no orphaned subdomains.

# Or from another terminal:
workslocal stop myapp

# Stop all tunnels:
workslocal stop --all

JSON output for scripts and AI

Every command supports --json for machine-readable output:

workslocal http 3000 --name myapp --json
{
  "tunnelId": "tun_abc123",
  "publicUrl": "https://myapp.workslocal.exposed",
  "subdomain": "myapp",
  "domain": "workslocal.exposed",
  "localPort": 3000,
  "inspectorUrl": "http://localhost:4040"
}

This enables AI assistants (Claude, Cursor) and scripts to create and manage tunnels programmatically.


Configuration

WorksLocal stores config in ~/.workslocal/config.json:

{
  "anonymousToken": "a1b2c3...",
  "sessionToken": "eyJ...",
  "userId": "user_xxx",
  "serverUrl": "wss://api.workslocal.dev/ws"
}

You typically never need to edit this file directly.

Environment variables

VariablePurposeDefault
WORKSLOCAL_SERVER_URLRelay server WebSocket URLwss://api.workslocal.dev/ws
WORKSLOCAL_API_KEYAPI key for authentication

How it works (30-second version)

  1. The CLI opens a WebSocket to the WorksLocal relay server (on Cloudflare Workers)
  2. The relay assigns your subdomain and creates a Durable Object for your tunnel
  3. When someone visits your tunnel URL, the request flows: browser → Cloudflare edge → Durable Object → WebSocket → your CLI → localhost
  4. Your local server responds, and the response flows back the same path
  5. The CLI captures every request/response in memory for the web inspector

Your traffic passes through the relay as encrypted packets. The relay is a “dumb pipe” — request and response bodies never touch server disk. All inspection and storage happens locally on your machine.


Limitations

  • HTTP/HTTPS only — TCP and UDP tunneling are not supported (planned for future)
  • No SSE/streaming responses yet — responses are fully buffered before sending (fix coming in next release)
  • 10 MB request body limit — requests larger than 10 MB return 413
  • 30-second timeout— if your local server doesn't respond within 30 seconds, the tunnel returns 504
  • Single tunnel domain — only workslocal.exposed is available (coming support for more domains)
  • No custom domains — bring-your-own-domain not supported yet
  • In-memory request store — captured requests are lost when the CLI exits (SQLite persistence planned)

Next steps