tnl.dev :: docs

develop with tnl.

Run development apps in parallel Git worktrees.

start a configured service

With a configured project, start its development service. If you have not configured tnl yet, follow the quickstart.

npx tnl dev

Sign in when prompted. tnl dev starts the configured command, waits for its local HTTP listener, and gives it a public HTTPS URL. The app's output passes through unchanged; Ctrl+C stops both the app and its tunnel.

When the server manages DNS, tnl dev waits for the public URL record before showing ready. With --open, it also waits up to two minutes for your computer to resolve the hostname before opening the browser. If local DNS still has no address, the tunnel remains ready and tnl reports why it did not open the browser; try the public URL again after DNS resolves.

If more than one service is configured, name one when running tnl dev. See project configuration for service definitions and the config reference for settings.

who can visit?

By default, tnl allows the current public IP of the machine running tnl dev. Add a reviewer's public IP or select a webhook provider in project configuration to allow it on subsequent runs. These three files are equivalent; choose the format your project uses:

tnl.config.ts
import { defineConfig } from "@tnldotdev/tnl/config";
 
export default defineConfig({
  tunnel: {
    allowIP: ["192.0.2.34", "198.51.100.0/24", "2001:db8::/32"],
    allowProviders: ["stripe", "github"],
  },
  services: {
    app: { directory: ".", dev: { command: ["vite"] } },
  },
});

Use your project's actual start command and replace these documentation-only IP addresses and CIDRs with visitor addresses. Keep your existing service settings when adding IPs or providers. If the package's dev script starts tnl dev, dev.command must start the app directly rather than running that script. Your current public IP is still added automatically. For visitors from any IP, set tunnel.allowAllIPs: true (or tunnel.allow_all_ips: true in YAML/JSON) instead of the lists. IP policy is not application authentication; keep your app's own login for sensitive content. See security and public url settings.

stripe uses Stripe's published webhook addresses, and github uses GitHub's webhook hooks ranges. tnl fetches these lists when the tunnel starts; restart it to pick up changes. The addresses apply to the whole public URL hostname, not just your webhook path. Your webhook handler should still verify Stripe or GitHub signatures.

develop in parallel worktrees

Start the same service in each Git worktree. Each checkout gets its own public URL and reuses it when you restart tnl dev.

git worktree add -b perf ../perf

Run tnl dev from each worktree in a separate terminal. Vite and Next.js report the port they actually opened if the preferred port is occupied; Node and Bun can use tnl.port for an available port. See framework integrations.

Here, example is the primary checkout directory and perf is the linked worktree directory. Switching branches in the same checkout does not change its public URL.

The name before the member namespace has four parts. For the web service in project directory shop and worktree directory perf:

The name goes before your member namespace and team domain, for example web-shop-perf-k7n2p9.alex.studio.example.com. tnl shortens long names to fit one DNS label. Two services in the same worktree get different names because each begins with its service name; the ID stays stable for that checkout and local client state.

tnl status shows this project's locally recorded tunnels; tnl status --all shows tunnels across local projects and worktrees on this machine. Neither queries the server for remote activity.

npx tnl status --all

run more than one service

Configure web and api as separate project services. Run each in its own terminal; with multiple services, tnl dev needs a service name. Each tunnel has its own public URL.

# terminal 1
npx tnl dev web
# terminal 2
npx tnl dev api

Read the assigned API service URL in the frontend with tnl.services?.api?.url; see service url metadata. A service URL does not imply that service is running.

connect a different app

If your app has no integration, start it with a command after -- and specify its fixed HTTP port. The app must actually listen on that port:

npx tnl dev --port 3000 -- npm run dev

For an app that's already running, use tnl publish instead.