Skip to content

Commit 826d651

Browse files
Add documentation for extending Sandbox class
- Add new guide explaining how to extend Sandbox with custom methods - Update getSandbox() API reference to document generic type parameter - Include TypeScript examples showing custom Sandbox usage - Add best practices for custom methods This documents the fix from PR #264 which made getSandbox and SandboxEnv generic to support extending the Sandbox class without type errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1fe9193 commit 826d651

File tree

2 files changed

+309
-4
lines changed

2 files changed

+309
-4
lines changed

src/content/docs/sandbox/api/lifecycle.mdx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ Create and manage sandbox containers. Get sandbox instances, configure options,
1616
Get or create a sandbox instance by ID.
1717

1818
```ts
19-
const sandbox = getSandbox(
20-
binding: DurableObjectNamespace<Sandbox>,
19+
function getSandbox<T extends Sandbox>(
20+
binding: DurableObjectNamespace<T>,
2121
sandboxId: string,
2222
options?: SandboxOptions
23-
): Sandbox
23+
): T
2424
```
2525

26+
**Type parameters**:
27+
- `T` - The Sandbox class type (use when extending Sandbox with custom methods)
28+
2629
**Parameters**:
2730
- `binding` - The Durable Object namespace binding from your Worker environment
2831
- `sandboxId` - Unique identifier for this sandbox. The same ID always returns the same sandbox instance
@@ -32,7 +35,7 @@ const sandbox = getSandbox(
3235
- `containerTimeouts` - Configure container startup timeouts
3336
- `normalizeId` - Lowercase sandbox IDs for preview URL compatibility (default: `false`)
3437

35-
**Returns**: `Sandbox` instance
38+
**Returns**: Sandbox instance of type `T`
3639

3740
:::note
3841
The container starts lazily on first operation. Calling `getSandbox()` returns immediatelythe container only spins up when you execute a command, write a file, or perform other operations. See [Sandbox lifecycle](/sandbox/concepts/sandboxes/) for details.
@@ -52,6 +55,35 @@ export default {
5255
```
5356
</TypeScriptExample>
5457

58+
**Using with custom Sandbox classes**:
59+
60+
When extending the `Sandbox` class with custom methods, specify the type parameter to get proper TypeScript inference:
61+
62+
<TypeScriptExample>
63+
```
64+
import { getSandbox, Sandbox } from '@cloudflare/sandbox';
65+
66+
export class CustomSandbox extends Sandbox {
67+
async customMethod(): Promise<string> {
68+
return 'custom result';
69+
}
70+
}
71+
72+
export default {
73+
async fetch(request: Request, env: Env): Promise<Response> {
74+
// Specify the custom type to access custom methods
75+
const sandbox = getSandbox<CustomSandbox>(env.Sandbox, 'user-123');
76+
77+
// TypeScript knows about customMethod()
78+
const result = await sandbox.customMethod();
79+
return Response.json({ result });
80+
}
81+
};
82+
```
83+
</TypeScriptExample>
84+
85+
See [Extend Sandbox class](/sandbox/guides/extend-sandbox/) for a complete guide on creating custom Sandbox classes.
86+
5587
:::caution
5688
When using `keepAlive: true`, you **must** call `destroy()` when finished to prevent containers running indefinitely.
5789
:::
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
---
2+
title: Extend Sandbox class
3+
pcx_content_type: how-to
4+
sidebar:
5+
order: 12
6+
---
7+
8+
import { TypeScriptExample, WranglerConfig } from "~/components";
9+
10+
Add custom methods to the Sandbox class to create reusable, domain-specific operations.
11+
12+
## Overview
13+
14+
You can extend the `Sandbox` class to add custom methods that encapsulate common operations for your application. This is useful for:
15+
16+
- Creating domain-specific APIs (e.g., `runAnalysis()`, `deployApp()`)
17+
- Encapsulating complex multi-step operations
18+
- Adding custom validation or error handling
19+
- Building reusable sandbox behaviors across your Worker
20+
21+
## Create a custom Sandbox class
22+
23+
Define a class that extends `Sandbox` and add your custom methods:
24+
25+
<TypeScriptExample>
26+
```
27+
import { Sandbox } from '@cloudflare/sandbox';
28+
29+
export class CustomSandbox extends Sandbox {
30+
async runAnalysis(dataFile: string): Promise<string> {
31+
// Custom method that runs a multi-step analysis
32+
await this.writeFile('/workspace/data.csv', dataFile);
33+
await this.exec('pip install pandas matplotlib');
34+
35+
const result = await this.exec('python analyze.py');
36+
return result.stdout;
37+
}
38+
39+
async deployApp(code: string): Promise<void> {
40+
// Custom method that deploys an application
41+
await this.writeFile('/workspace/app.js', code);
42+
await this.exec('npm install');
43+
await this.startProcess('node app.js');
44+
}
45+
}
46+
```
47+
</TypeScriptExample>
48+
49+
## Configure your Worker
50+
51+
Update your Worker configuration to use the custom Sandbox class:
52+
53+
### 1. Update wrangler configuration
54+
55+
<WranglerConfig>
56+
```toml
57+
name = "my-worker"
58+
main = "src/index.ts"
59+
60+
[[durable_objects.bindings]]
61+
name = "Sandbox"
62+
class_name = "CustomSandbox"
63+
script_name = "my-worker"
64+
65+
[observability]
66+
enabled = true
67+
```
68+
</WranglerConfig>
69+
70+
### 2. Export the custom class
71+
72+
In your Worker entry point, export your custom Sandbox class:
73+
74+
<TypeScriptExample>
75+
```
76+
import { getSandbox } from '@cloudflare/sandbox';
77+
import { CustomSandbox } from './custom-sandbox';
78+
79+
// Export your custom Sandbox class as a Durable Object
80+
export { CustomSandbox };
81+
82+
interface Env {
83+
Sandbox: DurableObjectNamespace<CustomSandbox>;
84+
}
85+
86+
export default {
87+
async fetch(request: Request, env: Env): Promise<Response> {
88+
// Use getSandbox with your custom type
89+
const sandbox = getSandbox<CustomSandbox>(env.Sandbox, 'user-123');
90+
91+
// Call your custom methods
92+
const result = await sandbox.runAnalysis('data content here');
93+
94+
return Response.json({ result });
95+
}
96+
};
97+
```
98+
</TypeScriptExample>
99+
100+
:::note
101+
When using custom Sandbox classes, specify the type parameter in `getSandbox<CustomSandbox>()` to get proper TypeScript inference for your custom methods.
102+
:::
103+
104+
## Type-safe environment interface
105+
106+
Define a type-safe environment interface for your Worker:
107+
108+
<TypeScriptExample>
109+
```
110+
import { SandboxEnv } from '@cloudflare/sandbox';
111+
import { CustomSandbox } from './custom-sandbox';
112+
113+
// Extend SandboxEnv with your custom type
114+
interface Env extends SandboxEnv<CustomSandbox> {
115+
// Add other bindings as needed
116+
MY_BUCKET: R2Bucket;
117+
MY_KV: KVNamespace;
118+
}
119+
120+
export default {
121+
async fetch(request: Request, env: Env): Promise<Response> {
122+
const sandbox = getSandbox<CustomSandbox>(env.Sandbox, 'user-123');
123+
124+
// TypeScript knows about your custom methods
125+
await sandbox.runAnalysis('data');
126+
await sandbox.deployApp('code');
127+
128+
return new Response('OK');
129+
}
130+
};
131+
```
132+
</TypeScriptExample>
133+
134+
## Best practices
135+
136+
### Keep methods focused
137+
138+
Each custom method should have a single, clear purpose:
139+
140+
<TypeScriptExample>
141+
```
142+
export class CustomSandbox extends Sandbox {
143+
// Good: Focused, reusable method
144+
async installDependencies(packages: string[]): Promise<void> {
145+
await this.exec(`pip install ${packages.join(' ')}`);
146+
}
147+
148+
// Good: Domain-specific operation
149+
async runTests(testFile: string): Promise<boolean> {
150+
const result = await this.exec(`python -m pytest ${testFile}`);
151+
return result.exitCode === 0;
152+
}
153+
}
154+
```
155+
</TypeScriptExample>
156+
157+
### Handle errors appropriately
158+
159+
Add proper error handling in your custom methods:
160+
161+
<TypeScriptExample>
162+
```
163+
export class CustomSandbox extends Sandbox {
164+
async processData(input: string): Promise<string> {
165+
try {
166+
await this.writeFile('/tmp/input.txt', input);
167+
const result = await this.exec('python process.py /tmp/input.txt');
168+
169+
if (result.exitCode !== 0) {
170+
throw new Error(`Processing failed: ${result.stderr}`);
171+
}
172+
173+
return result.stdout;
174+
} catch (error) {
175+
// Add context to errors
176+
throw new Error(`Data processing failed: ${error.message}`);
177+
}
178+
}
179+
}
180+
```
181+
</TypeScriptExample>
182+
183+
### Validate inputs
184+
185+
Add validation to prevent common errors:
186+
187+
<TypeScriptExample>
188+
```
189+
export class CustomSandbox extends Sandbox {
190+
async runScript(scriptPath: string): Promise<void> {
191+
// Validate input
192+
if (!scriptPath.startsWith('/workspace/')) {
193+
throw new Error('Script must be in /workspace directory');
194+
}
195+
196+
// Check file exists before running
197+
const files = await this.listFiles('/workspace');
198+
const fileName = scriptPath.split('/').pop();
199+
200+
if (!files.includes(fileName)) {
201+
throw new Error(`Script not found: ${scriptPath}`);
202+
}
203+
204+
await this.exec(`python ${scriptPath}`);
205+
}
206+
}
207+
```
208+
</TypeScriptExample>
209+
210+
## Complete example
211+
212+
A practical example of a custom Sandbox class for a code execution service:
213+
214+
<TypeScriptExample>
215+
```
216+
import { Sandbox } from '@cloudflare/sandbox';
217+
218+
export interface ExecutionResult {
219+
success: boolean;
220+
output: string;
221+
executionTime: number;
222+
}
223+
224+
export class CodeExecutorSandbox extends Sandbox {
225+
async executeCode(
226+
language: 'python' | 'javascript',
227+
code: string,
228+
timeout: number = 30
229+
): Promise<ExecutionResult> {
230+
const startTime = Date.now();
231+
232+
try {
233+
// Write code to file
234+
const fileName = language === 'python' ? 'script.py' : 'script.js';
235+
await this.writeFile(`/tmp/${fileName}`, code);
236+
237+
// Execute based on language
238+
const command = language === 'python'
239+
? `python /tmp/${fileName}`
240+
: `node /tmp/${fileName}`;
241+
242+
const result = await this.exec(command, { timeout });
243+
244+
return {
245+
success: result.exitCode === 0,
246+
output: result.exitCode === 0 ? result.stdout : result.stderr,
247+
executionTime: Date.now() - startTime
248+
};
249+
} catch (error) {
250+
return {
251+
success: false,
252+
output: error.message,
253+
executionTime: Date.now() - startTime
254+
};
255+
}
256+
}
257+
258+
async installPackages(language: 'python' | 'javascript', packages: string[]): Promise<void> {
259+
const command = language === 'python'
260+
? `pip install ${packages.join(' ')}`
261+
: `npm install ${packages.join(' ')}`;
262+
263+
await this.exec(command);
264+
}
265+
}
266+
```
267+
</TypeScriptExample>
268+
269+
## Related resources
270+
271+
- [Lifecycle API](/sandbox/api/lifecycle/) - Base Sandbox class methods
272+
- [Sessions API](/sandbox/api/sessions/) - Manage execution contexts
273+
- [Configuration reference](/sandbox/configuration/sandbox-options/) - Configure sandbox behavior

0 commit comments

Comments
 (0)