Sandbox usage
After the quickstart works, most Sandbox code falls into four patterns: define a typed payload, validate input, call runSandbox(), and handle the result object.
Define payload and result types
Export payload and result types from the sandbox definition when route code needs them.
import { defineSandbox } from '@vitehub/sandbox'
export type ReleaseNotesPayload = {
notes?: string
}
export type ReleaseNotesResult = {
summary: string
items: string[]
}
export default defineSandbox(async (payload: ReleaseNotesPayload = {}): Promise<ReleaseNotesResult> => {
const items = (payload.notes || '').split('\n').filter(Boolean)
return { summary: items[0] || '', items }
})
Keep call sites provider-neutral
The route should not know whether Cloudflare or Vercel is running the sandbox.
const result = await runSandbox('release-notes', payload)
Provider details belong in config:
sandbox: {
provider: 'cloudflare',
binding: 'SANDBOX',
}
sandbox: {
provider: 'vercel',
runtime: 'node22',
}
Pass context separately from payload
Use payload for the data the sandbox operates on. Use context for caller metadata.
const result = await runSandbox('release-notes', payload, {
context: {
requestId: event.context.requestId,
actor: 'docs-admin',
},
})
The sandbox receives that context as its second argument:
export default defineSandbox(async (payload: ReleaseNotesPayload = {}, context = {}) => {
return {
requestId: context.requestId,
summary: payload.notes?.split('\n')[0] || '',
}
})
Handle errors before reading values
runSandbox() returns a result object. Check it before reading value.
const result = await runSandbox('release-notes', payload)
if (result.isErr()) {
throw createError({
statusCode: 500,
statusMessage: result.error.message,
})
}
return { result: result.value }
This keeps route code explicit and prevents provider/runtime failures from looking like successful application responses.
Reuse a provider sandbox
Pass sandboxId when a provider supports stable sandbox identity:
const result = await runSandbox('release-notes', payload, {
sandboxId: 'release-notes-writer',
})
Use this only when reuse is intentional. Without a sandboxId, Cloudflare execution receives a generated identity for each call.
Set definition options
Definition options travel with the sandbox:
export default defineSandbox(async (payload: ReleaseNotesPayload = {}) => {
return { summary: payload.notes || '' }
}, {
timeout: 30_000,
env: {
NODE_ENV: 'production',
},
})
Use runtime only when the default launcher is not enough:
export default defineSandbox(handler, {
runtime: {
command: 'node',
args: ['entry.mjs'],
},
})
Next steps
- Use Run a sandbox for route examples.
- Use Validate payloads before passing user input to a sandbox.
- Use Runtime API for exact option names.

