---
title: "Sandbox SDK app preview URLs"
canonical: https://trickest.com/docs/developer-tools/sandbox-sdk/app-previews
description: "Expose a running sandbox application through a declared port and a preview URL."
---

# Sandbox SDK app preview URLs

Use **`@trickest/sandbox`** to expose an app running inside a sandbox. Declare the port when creating the environment, start a server listening on that port, and ask for its preview URL.

## Start an app and check the page

This function creates a small HTTP server, waits for a registered route, and checks the response. It returns the sandbox handle so your application can keep the preview running and delete it when the user finishes.

```typescript
import { Sandbox, SandboxApiError } from "@trickest/sandbox";

export async function createPreview(token: string) {
  const sandbox = await Sandbox.create({
    token,
    runtime: "node24",
    ports: [3000],
  });
  try {
    await sandbox.writeFile(
      "server.cjs",
      [
        'const http = require("node:http")',
        "http.createServer((req, res) => {",
        '  res.end("Sandbox preview ready")',
        '}).listen(3000, "0.0.0.0")',
      ].join("\n"),
    );
    await sandbox.runCommand({
      cmd: "node",
      args: ["server.cjs"],
      detached: true,
    });

    for (let attempt = 0; attempt < 20; attempt++) {
      try {
        const url = await sandbox.getPortDomain(3000);
        const response = await fetch(url, {
          signal: AbortSignal.timeout(5_000),
        });
        const text = await response.text();
        if (response.ok && text === "Sandbox preview ready")
          return { sandbox, url };
      } catch (error) {
        if (error instanceof SandboxApiError) {
          if (error.status !== 404 || error.code !== "port_not_mapped")
            throw error;
        } else if (
          !(error instanceof TypeError) &&
          !(error instanceof Error && error.name === "TimeoutError")
        ) {
          throw error;
        }
      }
      await new Promise((resolve) => setTimeout(resolve, 250));
    }
    throw new Error("The preview did not become available");
  } catch (error) {
    await sandbox.delete();
    throw error;
  }
}
```

Call `await sandbox.delete()` on the returned handle when the preview is no longer needed. Stopping or deleting its environment interrupts the app.

The example retries temporary route-registration failures and app connection failures. Each HTTP page check has a five-second timeout. `getPortDomain()` has no timeout or abort parameter, so the retry count does not guarantee an overall deadline for this function.

## Choose the URL method

| Method                              | What it does                         | Requirement                                                                                       |
| ----------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `await sandbox.getPortDomain(3000)` | Ask the server for the canonical URL | The port must be published and its route registered                                               |
| `sandbox.domain(3000)`              | Construct a URL locally              | The port must be published and `TRICKEST_SANDBOX_PUBLIC_DOMAIN` configured in the calling process |

Neither method starts the application. The synchronous method does not verify that its constructed URL works. A registered route also does not prove the application serves the expected response; check the page as the example does.

## Add a port to an existing environment

`setPorts()` replaces the declared port list. Preserve existing ports when adding another. A running session keeps its current mappings until the next start, so stop and resume a **persistent** sandbox to apply the change:

```typescript
import { Sandbox } from "@trickest/sandbox";

await sandbox.setPorts([...new Set([...sandbox.ports, 3000])]);
await sandbox.stop();
const resumed = await Sandbox.get({ token, id: sandbox.sandboxId });
await resumed.runCommand({
  cmd: "node",
  args: ["server.cjs"],
  detached: true,
});
const url = await resumed.getPortDomain(3000);
```

This example assumes `server.cjs` already exists and listens on port 3000. Stopping interrupts every process in the environment. Restart the app after resuming, then check the page with the retry pattern above. Do not stop a non-persistent environment if you need to keep it.

## Troubleshoot a preview

- **`404 port_not_mapped`:** route registration may still be pending, or the port is not mapped in the current session. Declare it with `setPorts()`, then stop and resume the persistent environment.
- **Connection failure or unexpected response:** check the command's status and logs, then confirm the server listens on the declared port and on `0.0.0.0`.
- **Missing public-domain configuration:** use `getPortDomain()` or configure the synchronous `domain()` method's environment.

Treat a preview URL as an application endpoint. Keep platform tokens out of pages and client JavaScript, and apply your app's access controls before exposing sensitive results. See [Commands & output](/docs/developer-tools/sandbox-sdk/commands) for detached command controls and [Lifecycle](/docs/developer-tools/sandbox-sdk/lifecycle) for cleanup.

---
_Markdown source of https://trickest.com/docs/developer-tools/sandbox-sdk/app-previews._
