# How to Integrate Custom Code with Cloudflare Computer Runtime Types

> Integrate custom code with Cloudflare Computer runtime types using workspace.runtime.exec(). Run shell commands or ES modules with structured input for callable backends.

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

---

**Use `workspace.runtime.exec()` to run arbitrary code as either shell commands or ES modules, with structured input support for callable backends like `worker-javascript`.**

The Cloudflare Computer SDK provides a flexible execution environment for running custom code inside Durable Objects. Whether you need to execute shell commands in containers or run JavaScript modules with structured data, the `WorkspaceRuntime` class in [`packages/computer/src/runtime/runtime.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/runtime.ts) handles routing, validation, and result streaming.

## Understanding the Runtime Router Architecture

The `WorkspaceRuntime` class acts as a central router between your application code and configured backends. All execution flows through `workspace.runtime.exec(source, options)`, which delegates to the appropriate backend based on the `backend` option.

### Backend Selection and Validation

Backends are identified by opaque strings you assign during construction. The router validates two critical properties before execution:

- **`backend`** – Specifies which backend interprets the source string. Defaults to the first backend in the `Workspace` constructor.
- **`callableBackendIds`** – Tracks backends that accept structured `input`. Stored in `WorkspaceRuntime.isCallable` and `WorkspaceRuntime.callableBackendIds` ([runtime.ts#L22-L28](packages/computer/src/runtime/runtime.ts#L22-L28)).

If you provide `options.input` to a non-callable backend, the router throws the canonical error message produced by `notCallableMessage` ([runtime.ts#L26-L28](packages/computer/src/runtime/runtime.ts#L26-L28)).

### Execution Flow

```ts
// Core execution pipeline (simplified)
const backendHandle = await this.getBackend(backend);  // runtime.ts#L55-L82
const envelope = await runtime.exec(moduleExecutionInput);
const handle = wrapModuleHandle(envelope, syncBracket); // runtime.ts#L86-L124

```

The returned `WorkspaceRuntimeExecHandle` extends `ReadableStream<WorkspaceRuntimeEvent>` and provides a `result()` method for eager consumption.

## Running Custom JavaScript Modules

The **`worker-javascript`** backend executes source strings as ECMAScript modules. Your module must export a default async function; its return value becomes `result.value`.

### Basic Module Execution

```ts
import { Workspace } from "@cloudflare/computer";
import { WorkerJavaScriptBackend } from "@cloudflare/computer/backends/worker-javascript";

const ws = new Workspace({
  storage, // DurableObjectStorage-like instance
  backends: [
    new WorkerJavaScriptBackend({ id: "worker-javascript" })
  ],
  useThink: false,
});

const handle = await ws.runtime.exec(
  `
  import fs from "node:fs/promises";
  export default async () => {
    const data = await fs.readFile("/workspace/package.json", "utf8");
    return JSON.parse(data).name;
  };
  `,
  { backend: "worker-javascript", encoding: "utf8" }
);

const result = await handle.result();
console.log(result.value); // "my-project"

```

Key implementation details from the source:

- The SDK wraps your source in a temporary file before loading ([worker-javascript.ts](packages/computer/src/backends/worker-javascript/worker-javascript.ts))
- `encoding: "utf8"` converts `stdout`/`stderr` from `Uint8Array` to strings
- File system access uses the `WorkspaceRuntimeFilesystem` API ([types.ts#L35-L66](packages/computer/src/runtime/types.ts#L35-L66))

## Passing Structured Input to Callable Backends

Only backends with `callable: true` accept the `input` option. The value is serialized as a `WorkspaceRuntimeValue` (JSON-compatible: null, boolean, number, string, array, object) and passed as the first argument to your exported function.

```ts
const handle = await ws.runtime.exec(
  `
  export default async (input) => {
    return { greeting: \`Hello, \${input.name}!\` };
  };
  `,
  {
    backend: "worker-javascript",
    input: { name: "Alice" }, // structured argument
    encoding: "utf8"
  }
);

const { value } = await handle.result();
console.log(value.greeting); // "Hello, Alice!"

```

Attempting to use `input` with a non-callable backend triggers the validation error in `WorkspaceRuntime.exec` ([runtime.ts#L55-L82](packages/computer/src/runtime/runtime.ts#L55-L82)).

## Consuming Results: Streaming vs. Eager

The `WorkspaceRuntimeExecHandle` provides two mutually exclusive consumption patterns, enforced in `wrapModuleHandle` ([runtime.ts#L86-L124](packages/computer/src/runtime/runtime.ts#L86-L124)).

### Streaming Events for Real-Time Output

```ts
const handle = await ws.runtime.exec("npm test", {
  backend: "container-shell",
  encoding: "utf8"
});

for await (const ev of handle) {
  if (ev.name === "stdout") process.stdout.write(ev.value);
  if (ev.name === "stderr") process.stderr.write(ev.value);
  if (ev.name === "exit") console.log(`Exit code: ${ev.value}`);
}

```

### Eager Result Consumption

```ts
const result = await handle.result(); // internally consumes the stream

```

**Critical constraint:** Calling `result()` after iterating the stream—or iterating after calling `result()`—throws `runtime handle already consumed`.

## Sync Brackets for Container Backends

Container backends like **`container-shell`** automatically synchronize filesystem state:

| Phase | Operation | Purpose |
|-------|-----------|---------|
| Pre-execution | Push | Sync local VFS changes to container store |
| Execution | Spawn | Run command inside `computerd` |
| Post-execution | Pull | Sync container store back to local VFS |

The `WorkspaceRuntimeResult` reports synchronization metrics via `drainModuleResult` ([runtime.ts#L105-L148](packages/computer/src/runtime/runtime.ts#L105-L148)):

```ts
const result = await handle.result();
console.log(result.sync);     // { status: "complete" | "pending" }
console.log(result.pushed);   // files pushed to container
console.log(result.pulled);   // files pulled from container
console.log(result.skipped);  // unchanged files

```

## Complete Integration Examples

### Shell Command with Default Backend

```ts
const handle = await ws.runtime.exec("ls -la /workspace", {
  encoding: "utf8"
});
const { stdout } = await handle.result();

```

### Module with Computation and Structured Return

```ts
const handle = await ws.runtime.exec(
  `
  export default async (payload) => {
    return { sum: payload.a + payload.b };
  };
  `,
  {
    backend: "worker-javascript",
    input: { a: 3, b: 7 }
  }
);
const { value } = await handle.result();
console.log(value.sum); // 10

```

### Filesystem Operations in Module Context

```ts
const handle = await ws.runtime.exec(
  `
  import fs from "node:fs/promises";
  export default async () => {
    const files = await fs.readdir("/workspace");
    const stats = await Promise.all(
      files.map(f => fs.stat("/workspace/" + f).then(s => ({ name: f, size: s.size })))
    );
    return stats;
  };
  `,
  { backend: "worker-javascript" }
);

```

## Backend Configuration and Security

Backend IDs are arbitrary strings defined at construction time. The router performs no authorization—your gateway must validate backend selection against a server-side allowlist.

Example configuration from the reference implementation ([examples/think/src/agent.ts#L92-L103](examples/think/src/agent.ts#L92-L103)):

```ts
const ws = new Workspace({
  storage,
  backends: [
    new WorkerShellBackend({ id: "worker-shell" }),
    new WorkerJavaScriptBackend({ id: "worker-javascript" }),
    new ContainerBackend({ id: "container-shell" })
  ]
});

```

## Summary

- **`workspace.runtime.exec()`** is the single entry point for all custom code execution in Cloudflare Computer
- **`worker-javascript`** backend runs ES modules with optional structured `input` and returns values via `result.value`
- **Streaming and eager consumption** are mutually exclusive—choose based on whether you need real-time progress
- **Container backends** automatically handle filesystem synchronization via push/pull brackets
- **Backend validation** prevents `input` on non-callable backends with a clear error message
- Reference [`packages/computer/src/runtime/runtime.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/runtime.ts) for the router implementation and [`packages/computer/src/runtime/types.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/types.ts) for complete type definitions

## Frequently Asked Questions

### What backends support structured input in Cloudflare Computer?

Only backends with `callable: true` accept structured input. Currently this includes **`worker-javascript`**. The `WorkerJavaScriptBackend` class sets this property during construction, and the `WorkspaceRuntime` router validates against `callableBackendIds` before execution. Shell and container backends execute commands without structured argument passing.

### Can I use both streaming and `result()` on the same execution handle?

No. The `WorkspaceRuntimeExecHandle` enforces single-consumer semantics in `wrapModuleHandle` ([runtime.ts#L86-L124](packages/computer/src/runtime/runtime.ts#L86-L124)). Choose streaming when you need incremental output (UI progress, logs) or `result()` when you only need the final return value. Attempting both throws `runtime handle already consumed`.

### How does filesystem persistence work with container backends?

Container backends automatically wrap execution in a **sync bracket**: pending local changes push before the command starts, and container state pulls back after completion. The `WorkspaceRuntimeResult` includes `pushed`, `pulled`, `skipped` counts and a `sync` status object. This ensures durability without manual intervention, implemented in `drainModuleResult` ([runtime.ts#L105-L148](packages/computer/src/runtime/runtime.ts#L105-L148)).

### What module formats does the JavaScript backend support?

The **`worker-javascript`** backend requires ES modules with a default async function export. The source string is wrapped in a temporary file and loaded as a module. You can use `import` statements including the built-in `node:fs/promises` shim for VFS access. CommonJS (`require`, `module.exports`) is not supported in this backend.