# When to Use `getWorkspace()` Stub vs Constructing Workspace Directly in Cloudflare Computer

> Learn when to use getWorkspace() stub versus constructing Workspace directly in Cloudflare Computer. Optimize Durable Object interactions and unit tests for better performance.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: best-practices
- Published: 2026-08-16

---

**Use `getWorkspace()` to return a `WorkspaceStub` when crossing Durable Object boundaries via Workers RPC, and construct `new Workspace()` directly when operating inside the same Durable Object or in unit tests.**

Choosing between the `getWorkspace()` stub and direct `Workspace` construction determines how your application handles RPC boundaries in Cloudflare Computer. The `Workspace` class in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) provides both patterns to support internal DO operations and external access patterns. Understanding when to marshal calls through a stub versus accessing the local SQLite store directly impacts performance, API availability, and resource isolation.

## Understanding the Two Approaches

### What is the `getWorkspace()` Stub?

When a Durable Object (DO) exposes its workspace via `getWorkspace()`, it returns a `WorkspaceStub` instance defined in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) (lines 94-106). This stub implements `RpcTarget` from the capnweb library, enabling it to cross Workers RPC boundaries. The stub exposes only high-level facades—`fs`, `runtime`, `git`, `assets`, `artifacts`, and `useThink`—and marshals all calls to the host Workspace.

### Direct Workspace Construction

Inside the owning Durable Object, you instantiate `Workspace` directly using the constructor in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) (lines 32-88). This creates a local `SQLiteWorkspaceProvider` and provides full access to internal methods like `provider()`, `ready()`, `push()`, and `pull()` that the stub deliberately conceals. You can optionally enable the Think filesystem mixin by passing `useThink: true`.

## Decision Framework: When to Use Each Pattern

**Cross-DO or Worker Communication** — Use `getWorkspace()`. When a Worker or another Durable Object needs to access a workspace owned by a different DO, the stub acts as a thin RPC proxy. Only value-shaped data (JSON, `Uint8Array`, newline-delimited byte frames) crosses the boundary, keeping transport overhead minimal.

**Inside the Same Durable Object** — Use `new Workspace()`. The DO already holds direct access to the local SQLite storage handle via `this.ctx.storage`. Direct construction eliminates RPC marshalling overhead and exposes the complete API surface, including methods hidden by the stub.

**Unit Testing** — Use `new Workspace()`. Tests can provide a mock `DurableObjectStorageLike` such as `@cloudflare/dofs/testing`'s `SQLiteTestStorage`. This runs purely in-memory without RPC or stub tracking overhead.

**Think Helpers Without External DO** — Use `new Workspace({ useThink: true })`. The `useThink` option mixes filesystem helpers (`readFile`, `writeFile`, `rm`, `glob`) directly onto the instance. These helpers are only available through direct construction; the stub does not expose them.

**Resource Isolation** — Use `workspace.stub()`. When exposing a workspace to external code but hiding internal implementation details, the stub isolates the DO's resources and tracks its own lifetime via `trackStub`/`untrackStub` to prevent memory leaks.

## Implementation Details

### How `Workspace.stub()` Works

In [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) (lines 57-64), the `stub()` method instantiates `WorkspaceStub` from [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts). The stub wraps the host Workspace and implements `RpcTarget`, allowing it to be transferred over Workers RPC. Each stub tracks its lifecycle through `trackStub` and automatically disposes sub-stubs via `[Symbol.dispose]` when the caller finishes.

### The Workspace Constructor

The constructor in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts) (lines 32-88) builds the full workspace infrastructure. It initializes the `SQLiteWorkspaceProvider`, registers mounts (such as `/tmp` or custom backends), and optionally applies the Think mixin when `useThink: true` is passed in the options.

### RPC Boundaries and Data Shapes

The stub enforces that only value-shaped data crosses RPC boundaries. Streaming interfaces are not transported over the stub; instead, operations return handles or buffers. This design keeps the transport layer cheap and predictable across the Cloudflare Workers RPC boundary.

## Code Examples

### Accessing a Workspace from a Worker

When calling from a Worker into a Durable Object that owns the workspace, use the stub pattern:

```typescript
export default {
  async fetch(request, env) {
    const wsStub = await env.WSD.get(env.WSD.idFromName("my-workspace")).getWorkspace();

    await wsStub.fs.writeFile("/hello.txt", "Hello from the Worker!");

    const execHandle = await wsStub.runtime.exec("ls /workspace");
    const { stdout } = await execHandle.result();

    return new Response(`Workspace listing:\n${stdout}`);
  },
};

```

The `wsStub` variable is a `WorkspaceStub`. All filesystem and runtime calls travel over the Workers RPC boundary, with automatic cleanup of sub-stubs when operations complete.

### Direct Construction Inside a Durable Object

Inside the DO that owns the workspace, construct directly for full API access:

```typescript
import { Workspace } from "./workspace.js";

export class MyDO extends DurableObject {
  #ws = new Workspace({
    storage: this.ctx.storage,
    backends: [{ id: "default", type: "container", connect: createContainerBackend }],
    mounts: { "/tmp": { kind: "tmp" } },
    useThink: true,
  });

  async fetch(request) {
    await this.#ws.fs.writeFile("/data.txt", "local write");
    const txt = await this.#ws.readFile("/data.txt");
    return new Response(txt);
  }

  async getWorkspace() {
    await this.#ws.ready();
    return this.#ws.stub();
  }
}

```

Direct construction provides access to internal methods like `this.#ws.provider()` and Think helpers like `this.#ws.readFile()`. The `getWorkspace()` method returns a stub for external callers.

### Unit Testing Without RPC

For tests, instantiate with mock storage to avoid RPC entirely:

```typescript
import { Workspace } from "./workspace.js";
import { SQLiteTestStorage } from "@cloudflare/dofs/testing";

test("workspace basic ops", async () => {
  const ws = new Workspace({
    storage: new SQLiteTestStorage(),
    backends: [],
  });

  await ws.fs.mkdir("/tmp");
  await ws.fs.writeFile("/tmp/file.txt", "test");
  const content = await ws.fs.readFile("/tmp/file.txt", "utf8");
  expect(content).toBe("test");
});

```

This pattern runs purely in-process with no stub tracking or RPC overhead.

## Summary

- Use `getWorkspace()` (which returns `WorkspaceStub`) when crossing Workers RPC boundaries between Workers and Durable Objects or between different DOs.
- Construct `new Workspace()` directly when operating inside the same Durable Object that owns the workspace.
- Enable Think filesystem helpers by passing `useThink: true` to the constructor; these methods are unavailable through the stub.
- The stub tracks its own lifecycle via `trackStub`/`untrackStub` to prevent resource leaks when exposing workspaces externally.
- Unit tests should use direct construction with `SQLiteTestStorage` to avoid RPC complexity.

## Frequently Asked Questions

### Can I use Think helpers through a WorkspaceStub?

No. The Think-compatible filesystem helpers (`readFile`, `writeFile`, `glob`, etc.) are only available when you construct the `Workspace` directly with `useThink: true` in [`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts). These methods are mixed into the instance at construction time and are not exposed through the `WorkspaceStub` RPC interface.

### Why does `getWorkspace()` return a stub instead of the full Workspace?

The stub isolates the Durable Object's internal resources and enforces the Workers RPC contract. According to the implementation in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts), the stub exposes only value-shaped data transfer and prevents external callers from accessing internal methods like `provider()` or `ready()`. This design maintains security boundaries and enables automatic lifecycle tracking.

### How do I clean up WorkspaceStub resources?

The `WorkspaceStub` implements `[Symbol.dispose]` which automatically cleans up sub-stubs (for filesystem, runtime, etc.) when the calling scope ends. The stub also registers itself via `trackStub` and removes itself via `untrackStub` to prevent memory leaks. You generally do not need manual cleanup when using standard `await` patterns with RPC.

### Can I instantiate Workspace outside a Durable Object in production?

While technically possible, direct instantiation outside a DO context in production is not recommended because you lose the persistence guarantees of `DurableObjectStorage`. The `Workspace` class expects a storage backend that persists across requests. For serverless contexts, always access workspaces through `getWorkspace()` stubs bound to Durable Objects.