# How to Register and Configure Multiple Backends with Stable IDs in a Cloudflare Computer Workspace

> Easily register and configure multiple backends with stable IDs in Cloudflare Computer Workspace. Learn how to pass backend objects with explicit ID selectors to the Workspace constructor for seamless execution routing.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-07

---

**Pass an array of backend objects with explicit `id` selectors to the `Workspace` constructor, then route execution via `workspace.runtime.exec(source, { backend: "your-id" })`.**

A **Cloudflare Computer Workspace** is the core abstraction that combines a SQLite-backed virtual filesystem with a pluggable execution surface. According to the `cloudflare/computer` source code, you can attach multiple backend implementations to a single workspace and address each one through a **stable, persistent ID** that survives restarts and lazy initialization.

## Understanding Backend Handles and Stable IDs

In [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts), the `Workspace` class accepts a `backends` array where each entry becomes a **BackendHandle**. This handle wraps the backend implementation with a selector `id` that the runtime uses for routing.

The ID provides three critical properties:

- **Deterministic routing** — Callers reference backends by logical name rather than implementation details
- **Cross-session stability** — The same ID resolves to the same backend configuration even after the Durable Object restarts
- **Implementation swapping** — Replace the underlying backend without changing consumer code

## Built-in Backends and Default IDs

The `cloudflare/computer` repository ships with three built-in backends, each with a default stable ID:

| Backend | Default ID | Implementation File |
|---------|-----------|---------------------|
| `WorkerShellBackend` | `worker-shell` | [`packages/computer/src/backends/worker-shell.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/worker-shell.ts) |
| `ContainerBackend` | `container` | [`packages/computer/src/backends/container.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/container.ts) |
| `WorkerJavascriptBackend` | `worker-javascript` | [`packages/computer/src/backends/worker-javascript.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/backends/worker-javascript.ts) |

These defaults are defined in the respective backend constructors and used when you pass a bare backend instance without wrapping it in a handle object.

## Registering Multiple Backends in a Workspace

### Basic Registration with Default IDs

The simplest pattern passes backend instances directly. Each receives its default ID automatically:

```typescript
import { Workspace, WorkerShellBackend, ContainerBackend } from "@cloudflare/computer";

const workspace = new Workspace({
  storage: ctx.storage,
  sessionId: ctx.id.toString(),
  backends: [
    new WorkerShellBackend({
      loader: env.LOADER,
      workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
      ctx,
    }),
    new ContainerBackend({
      // container-specific configuration
    }),
  ],
});

```

### Custom IDs for Multiple Instances of the Same Backend

To register multiple instances of the same backend type—such as two isolated shells with different configurations—supply an explicit `id` by wrapping the backend in a handle object:

```typescript
const workspace = new Workspace({
  storage: ctx.storage,
  sessionId: ctx.id.toString(),
  backends: [
    // First shell with default ID "worker-shell"
    new WorkerShellBackend({
      loader: env.LOADER,
      workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
      ctx,
    }),

    // Second shell with custom ID "debug-shell"
    {
      id: "debug-shell",
      backend: new WorkerShellBackend({
        loader: env.DEBUG_LOADER,
        workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
        ctx,
      }),
    },

    // Container with custom ID
    {
      id: "my-container",
      backend: new ContainerBackend({
        // container-specific options
      }),
    },
  ],
});

```

The `BackendHandle` structure (defined in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts)) accepts:

- `id`: `string` — your stable selector
- `backend`: `Backend` instance — the actual implementation

## Routing Execution to Specific Backends

Use `workspace.runtime.exec()` with the `backend` option to target a specific backend by its stable ID:

```typescript
// Execute on the default worker-shell backend
await workspace.runtime.exec("echo hello", { backend: "worker-shell" });

// Execute on the custom debug shell
await workspace.runtime.exec("ls -la", { backend: "debug-shell" });

// Execute on the custom container backend
await workspace.runtime.exec("echo container", { backend: "my-container" });

```

The runtime performs **lazy initialization**: the backend is constructed or retrieved from cache on first use, but the ID-to-implementation mapping remains constant throughout the workspace lifetime.

## Complete Durable Object Example

Here's a production-ready pattern from the `cloudflare/computer` documentation:

```typescript
import {
  Workspace,
  WorkerShellBackend,
  ContainerBackend,
  WorkerJavascriptBackend,
} from "@cloudflare/computer";

export class MyAgent extends DurableObject<Env> {
  #workspace: Workspace;

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);

    this.#workspace = new Workspace({
      storage: ctx.storage,
      sessionId: ctx.id.toString(),
      backends: [
        // Worker shell with default ID
        new WorkerShellBackend({
          loader: env.LOADER,
          workspace: { binding: "MyWorkspace", id: ctx.id.toString() },
          ctx,
        }),

        // Container with custom ID
        {
          id: "my-container",
          backend: new ContainerBackend({
            /* container options */
          }),
        },

        // JavaScript runtime with custom ID
        {
          id: "js-runtime",
          backend: new WorkerJavascriptBackend({
            /* JS runtime options */
          }),
        },
      ],
    });
  }

  async fetch(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);

    if (pathname === "/run-shell") {
      return this.#workspace.runtime.exec("echo hello", { backend: "worker-shell" });
    }
    if (pathname === "/run-container") {
      return this.#workspace.runtime.exec("echo container", { backend: "my-container" });
    }
    if (pathname === "/run-js") {
      return this.#workspace.runtime.exec(
        `export default () => "js"`,
        { backend: "js-runtime" }
      );
    }
    return new Response("not found", { status: 404 });
  }
}

```

## Why Stable IDs Matter for Backend Configuration

Stable IDs in a Cloudflare Computer Workspace enable three architectural benefits:

1. **Logical decoupling** — Your application code references `"debug-shell"` without knowing whether it runs in a worker, container, or future backend type
2. **Multi-tenancy within one workspace** — Isolate different execution environments (production vs. debugging shells) under one SQLite filesystem
3. **Configuration versioning** — Change backend parameters (different loader bindings, resource limits) while preserving the external API contract

The stability guarantee holds because the ID is bound at `Workspace` construction time in your Durable Object constructor, which executes consistently on every fetch.

## Summary

- **Create backends** by instantiating `WorkerShellBackend`, `ContainerBackend`, or `WorkerJavascriptBackend`
- **Assign stable IDs** either by accepting defaults (`worker-shell`, `container`, `worker-javascript`) or wrapping in `{ id: string, backend: Backend }` handles
- **Register in Workspace** by passing the `backends` array to the constructor in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts)
- **Route execution** with `workspace.runtime.exec(source, { backend: "your-id" })` — the runtime resolves IDs lazily but consistently

## Frequently Asked Questions

### What happens if two backends share the same ID?

The `Workspace` constructor in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) does not deduplicate IDs. If you register multiple backends with identical selectors, the last one in the array wins. Always use unique IDs when registering multiple instances.

### Can I change a backend's ID after creating the Workspace?

No. Stable IDs are immutable after construction. The `BackendHandle` is frozen when passed to the `Workspace` constructor. To change IDs, you must create a new `Workspace` instance with updated configuration.

### Do stable IDs persist across Durable Object migrations?

Yes. The ID is part of your code's construction logic, not runtime state. As long as your Durable Object constructor passes the same ID string, the backend selector remains stable even when the Durable Object migrates between Cloudflare edges or restarts.

### How do I check which backends are available at runtime?

The `cloudflare/computer` source does not expose a public API for listing registered backends. Track your backend IDs in application code or environment configuration. The runtime will throw if you attempt to `exec` with an unregistered backend selector.