Skip to main content
Exposes an internal port and generates a preview URL for accessing services running in the sandbox. This enables external access to web servers, APIs, and other services.
Preview URLs require a custom domain with wildcard DNS (*.yourdomain.com). .workers.dev domains do not support the subdomain patterns needed for port proxying.

Method Signature

await sandbox.exposePort(
  port: number,
  options: {
    hostname: string;
    name?: string;
    token?: string;
  }
): Promise<{ url: string; port: number; name?: string }>

Parameters

port
number
required
Port number to expose (1024-65535, excluding 3000 which is reserved for the sandbox control plane)
options
object
required
Configuration options

Returns

result
object

Examples

Expose a web server

const process = await sandbox.startProcess('python -m http.server 8080');
await process.waitForPort(8080);

const { url } = await sandbox.exposePort(8080, {
  hostname: 'example.com'
});

console.log('Server available at:', url);
// https://8080-sandbox-abc123random4567.example.com

Expose with custom token

const { url } = await sandbox.exposePort(3000, {
  hostname: 'example.com',
  token: 'my_app_v1'
});

console.log('App available at:', url);
// https://3000-sandbox-my_app_v1.example.com

Expose with friendly name

const { url, name } = await sandbox.exposePort(8080, {
  hostname: 'example.com',
  name: 'Web Server'
});

console.log(`${name} available at: ${url}`);

Expose multiple ports

// Start services on different ports
await sandbox.startProcess('npm run web'); // port 3000
await sandbox.startProcess('npm run api'); // port 8080

// Expose both
const web = await sandbox.exposePort(3000, {
  hostname: 'example.com',
  name: 'Web UI'
});

const api = await sandbox.exposePort(8080, {
  hostname: 'example.com',
  name: 'API'
});

console.log('Web UI:', web.url);
console.log('API:', api.url);

Full server lifecycle

// Start server
const process = await sandbox.startProcess('node server.js');

// Wait for it to be ready
await process.waitForPort(8080);

// Expose it
const { url } = await sandbox.exposePort(8080, {
  hostname: 'example.com',
  token: 'prod_api'
});

console.log('Server ready at:', url);

Security

Preview URLs use token-based authentication to prevent unauthorized access:
  • Each exposed port gets a unique token (auto-generated or custom)
  • The token is embedded in the subdomain
  • Only requests with the correct token can access the service
  • Tokens are stored securely in Durable Object storage
// Auto-generated token (random, 16 characters)
const { url } = await sandbox.exposePort(8080, {
  hostname: 'example.com'
});
// https://8080-sandbox-a1b2c3d4e5f67890.example.com

// Custom token (predictable, for stable URLs)
const { url } = await sandbox.exposePort(8080, {
  hostname: 'example.com',
  token: 'my_token'
});
// https://8080-sandbox-my_token.example.com

Error Handling

import { SecurityError, CustomDomainRequiredError } from '@cloudflare/sandbox';

try {
  await sandbox.exposePort(8080, { hostname: 'example.workers.dev' });
} catch (error) {
  if (error instanceof CustomDomainRequiredError) {
    console.error('.workers.dev domains are not supported');
  }
}

try {
  await sandbox.exposePort(3000, { hostname: 'example.com' });
} catch (error) {
  if (error instanceof SecurityError) {
    console.error('Port 3000 is reserved for the sandbox control plane');
  }
}

Build docs developers (and LLMs) love