|
| 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