Skip to main content
Streaming provides real-time updates as commands execute, processes run, and code generates output. Perfect for long-running operations, live monitoring, and interactive applications.

Why streaming?

Streaming gives you:
  • Immediate feedback - See output as it happens, not after completion
  • Progress tracking - Monitor long-running operations
  • Better UX - Keep users informed instead of waiting
  • Early error detection - Catch failures immediately

Streaming command execution

Stream output from commands with exec() and callbacks:
const sandbox = getSandbox(env.Sandbox, 'my-sandbox');

const result = await sandbox.exec('npm install', {
  stream: true,
  onOutput: (stream, data) => {
    if (stream === 'stdout') {
      console.log('[OUT]', data);
    } else {
      console.error('[ERR]', data);
    }
  },
  onComplete: (result) => {
    console.log(`Finished with exit code ${result.exitCode}`);
  },
  onError: (error) => {
    console.error('Execution failed:', error.message);
  }
});
With streaming enabled, onOutput is called for each chunk of output as it arrives. The final result is still returned from exec() with complete stdout and stderr.

Understanding SSE streams

The SDK uses Server-Sent Events (SSE) for streaming. Each stream emits events with this structure:
interface SSEEvent {
  type: string;      // Event type (stdout, stderr, exit, etc.)
  data: string;      // Event data
  [key: string]: any; // Additional fields depending on event type
}

Parsing SSE streams

For lower-level control, parse SSE streams directly:
import { parseSSEStream } from '@cloudflare/sandbox';

// Get raw stream
const stream = await sandbox.client.commands.executeStream(
  'npm install',
  sessionId
);

// Parse and handle events
for await (const event of parseSSEStream(stream)) {
  switch (event.type) {
    case 'stdout':
      console.log('Output:', event.data);
      break;
    
    case 'stderr':
      console.error('Error:', event.data);
      break;
    
    case 'complete':
      console.log('Exit code:', event.exitCode);
      break;
    
    case 'error':
      console.error('Failed:', event.data);
      break;
  }
}

Streaming process logs

Stream logs from background processes:
import { parseSSEStream } from '@cloudflare/sandbox';

// Start a process
const server = await sandbox.startProcess('npm run dev');

// Stream its logs
const stream = await sandbox.streamProcessLogs(server.id);

for await (const event of parseSSEStream(stream)) {
  switch (event.type) {
    case 'stdout':
      console.log('Server:', event.data);
      break;
    
    case 'stderr':
      console.error('Error:', event.data);
      break;
    
    case 'exit':
      console.log('Server stopped with code:', event.exitCode);
      break;
  }
}
Process log streams continue until the process exits or you close the stream. Perfect for tailing logs or monitoring services.

Streaming file reads

Stream large files in chunks:
import { parseSSEStream } from '@cloudflare/sandbox';

const stream = await sandbox.readFileStream('/workspace/large-file.json');

let content = '';

for await (const event of parseSSEStream(stream)) {
  switch (event.type) {
    case 'metadata':
      console.log('File size:', event.size);
      console.log('File type:', event.type);
      break;
    
    case 'chunk':
      content += event.data;
      console.log(`Read ${content.length} bytes so far...`);
      break;
    
    case 'complete':
      console.log('File fully read:', event.path);
      break;
    
    case 'error':
      console.error('Read failed:', event.message);
      break;
  }
}

const data = JSON.parse(content);

Streaming code execution

Stream output from code interpreter:
const execution = await sandbox.runCode(`
import time

for i in range(10):
    print(f"Step {i}")
    time.sleep(0.5)

print("Done!")
`, {
  onStdout: (output) => {
    console.log('Python output:', output.text);
  },
  onResult: (result) => {
    console.log('Expression result:', result);
  },
  onError: (error) => {
    console.error('Execution error:', error);
  }
});

Stream to HTTP response

Stream output directly to an HTTP response:
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
    
    // Get command stream
    const stream = await sandbox.client.commands.executeStream(
      'npm install',
      await sandbox.ensureDefaultSession()
    );
    
    // Stream to client
    return new Response(stream, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      }
    });
  }
}

Transform streams

Transform SSE streams for custom output:
import { parseSSEStream } from '@cloudflare/sandbox';

// Get raw stream
const stream = await sandbox.streamProcessLogs(processId);

// Create transformed stream
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();

// Parse and transform events
(async () => {
  try {
    for await (const event of parseSSEStream(stream)) {
      // Transform to JSON
      const json = JSON.stringify({
        timestamp: new Date().toISOString(),
        type: event.type,
        data: event.data
      }) + '\n';
      
      await writer.write(new TextEncoder().encode(json));
    }
  } finally {
    await writer.close();
  }
})();

// Return transformed stream
return new Response(readable, {
  headers: { 'Content-Type': 'application/x-ndjson' }
});

Common patterns

Progress reporting

let progress = 0;
const total = 100;

const result = await sandbox.exec('npm install', {
  stream: true,
  onOutput: (stream, data) => {
    // Count installation steps
    if (data.includes('added')) {
      progress++;
      const percent = Math.floor((progress / total) * 100);
      console.log(`Progress: ${percent}%`);
      
      // Update UI
      updateProgressBar(percent);
    }
  }
});

Build monitoring

const builds = [];

const build = await sandbox.exec('npm run build', {
  stream: true,
  onOutput: (stream, data) => {
    // Track build steps
    if (data.includes('✓')) {
      builds.push({
        step: data.trim(),
        timestamp: new Date()
      });
    }
    
    // Detect errors
    if (data.toLowerCase().includes('error')) {
      console.error('Build error detected:', data);
      notifyDevelopers(data);
    }
  }
});

return Response.json({ builds });

Live log viewing

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const processId = url.searchParams.get('id');
    
    if (!processId) {
      return new Response('Missing process ID', { status: 400 });
    }
    
    const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
    
    // Stream logs to browser
    const stream = await sandbox.streamProcessLogs(processId);
    
    return new Response(stream, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'X-Accel-Buffering': 'no'
      }
    });
  }
}
Client-side:
const eventSource = new EventSource('/logs?id=process-123');

eventSource.addEventListener('stdout', (event) => {
  const data = JSON.parse(event.data);
  appendToConsole(data.data);
});

eventSource.addEventListener('exit', (event) => {
  const data = JSON.parse(event.data);
  console.log('Process exited:', data.exitCode);
  eventSource.close();
});

Test runner

const tests = [];

const result = await sandbox.exec('npm test', {
  stream: true,
  onOutput: (stream, data) => {
    // Parse test results
    if (data.includes('PASS')) {
      const match = data.match(/PASS\s+(.+)/);
      if (match) {
        tests.push({
          name: match[1],
          status: 'passed',
          timestamp: new Date()
        });
      }
    } else if (data.includes('FAIL')) {
      const match = data.match(/FAIL\s+(.+)/);
      if (match) {
        tests.push({
          name: match[1],
          status: 'failed',
          timestamp: new Date()
        });
      }
    }
  }
});

return Response.json({
  passed: tests.filter(t => t.status === 'passed').length,
  failed: tests.filter(t => t.status === 'failed').length,
  tests
});

Chat streaming

// Stream AI responses that execute code
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sandbox = getSandbox(env.Sandbox, 'my-sandbox');
    
    const { readable, writable } = new TransformStream();
    const writer = writable.getWriter();
    const encoder = new TextEncoder();
    
    // Send SSE events
    const send = async (event: string, data: any) => {
      await writer.write(
        encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
      );
    };
    
    // Process request asynchronously
    (async () => {
      try {
        await send('status', { message: 'Executing code...' });
        
        // Run code with streaming
        const execution = await sandbox.runCode(userCode, {
          onStdout: async (output) => {
            await send('output', { type: 'stdout', text: output.text });
          },
          onResult: async (result) => {
            await send('result', result);
          },
          onError: async (error) => {
            await send('error', error);
          }
        });
        
        await send('complete', { success: !execution.error });
      } catch (error) {
        await send('error', { message: error.message });
      } finally {
        await writer.close();
      }
    })();
    
    return new Response(readable, {
      headers: {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache'
      }
    });
  }
}

Error handling

Handle stream errors gracefully:
try {
  const stream = await sandbox.streamProcessLogs(processId);
  
  for await (const event of parseSSEStream(stream)) {
    if (event.type === 'error') {
      console.error('Stream error:', event.data);
      break;
    }
    
    // Process event...
  }
} catch (error) {
  console.error('Stream failed:', error.message);
  
  // Check if process still exists
  const process = await sandbox.getProcess(processId);
  if (!process) {
    console.log('Process no longer exists');
  }
}

Best practices

1
Always use streaming for long operations
2
Stream output for any command that might take more than a few seconds:
3
// Good - stream long operations
await sandbox.exec('npm install', { stream: true, onOutput: ... });

// Bad - waiting with no feedback
await sandbox.exec('npm install');
4
Handle stream completion
5
Always handle the completion event to know when operations finish:
6
await sandbox.exec(command, {
  stream: true,
  onOutput: (stream, data) => {
    // Show progress
  },
  onComplete: (result) => {
    // Handle completion
    if (!result.success) {
      console.error('Failed with exit code:', result.exitCode);
    }
  }
});
7
Close streams when done
8
If you stop processing a stream early, close it:
9
const stream = await sandbox.streamProcessLogs(processId);

for await (const event of parseSSEStream(stream)) {
  if (event.data.includes('ERROR')) {
    // Found what we need, stop streaming
    stream.cancel();
    break;
  }
}
10
Buffer for UI updates
11
Batch rapid updates to avoid overwhelming the UI:
12
let buffer = '';
let lastUpdate = Date.now();

await sandbox.exec(command, {
  stream: true,
  onOutput: (stream, data) => {
    buffer += data;
    
    // Update UI every 100ms
    const now = Date.now();
    if (now - lastUpdate > 100) {
      updateUI(buffer);
      buffer = '';
      lastUpdate = now;
    }
  }
});

Next steps

Executing commands

Learn about command execution options

Running processes

Manage long-running background processes

Code interpreter

Stream code execution output

Examples

See complete streaming examples

Build docs developers (and LLMs) love