Watch filesystem changes

Last updated Sep 30, 2026 Note

This page documents Sandbox SDK 0.x for existing applications. For new applications, refer to Sandboxes. To move an existing application to @cloudflare/sandbox 1.0, refer to Migrate from Sandbox SDK 0.x.

This guide shows you how to monitor filesystem changes in real-time using the Sandbox SDK's file watching API. File watching is useful for building development tools, automated workflows, and applications that react to file changes as they happen.

The watch() method returns an SSE (Server-Sent Events) stream that you consume with parseSSEStream(). Each event in the stream describes a filesystem change.

Basic file watching

Start by watching a directory for any changes:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/src");

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event") {
        console.log(`${event.eventType}: ${event.path}`);
        console.log(`Is directory: ${event.isDirectory}`);
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/src");

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (event.type === "event") {
        console.log(`${event.eventType}: ${event.path}`);
        console.log(`Is directory: ${event.isDirectory}`);
    }
}

The stream emits four lifecycle event types:

  • watching — Watch established, includes the watchId
  • event — A filesystem change occurred
  • error — The watch encountered an error
  • stopped — The watch was stopped

Filesystem change events (event.eventType) include:

  • create — File or directory was created
  • modify — File content changed
  • delete — File or directory was removed
  • move_from / move_to — File or directory was moved or renamed
  • attrib — File attributes changed (permissions, timestamps)

Filter by file type

Use include patterns to watch only specific file types:

import { parseSSEStream } from "@cloudflare/sandbox";

// Only watch TypeScript and JavaScript files
const stream = await sandbox.watch("/workspace/src", {
    include: ["*.ts", "*.tsx", "*.js", "*.jsx"],
});

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event") {
        console.log(`${event.eventType}: ${event.path}`);
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

// Only watch TypeScript and JavaScript files
const stream = await sandbox.watch("/workspace/src", {
    include: ["*.ts", "*.tsx", "*.js", "*.jsx"],
});

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (event.type === "event") {
        console.log(`${event.eventType}: ${event.path}`);
    }
}

Common include patterns:

  • *.ts — TypeScript files
  • *.js — JavaScript files
  • *.json — JSON configuration files
  • *.md — Markdown documentation
  • package*.json — Package files specifically

Exclude directories

Use exclude patterns to skip certain directories or files:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace", {
    exclude: ["node_modules", "dist", "*.log", ".git", "*.tmp"],
});

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event") {
        console.log(`Change detected: ${event.path}`);
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace", {
    exclude: ["node_modules", "dist", "*.log", ".git", "*.tmp"],
});

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (event.type === "event") {
        console.log(`Change detected: ${event.path}`);
    }
}

Default exclusions

The following patterns are excluded by default: .git, node_modules, .DS_Store. You can override this by providing your own exclude array.

Build responsive development tools

Auto-rebuild on changes

Trigger builds automatically when source files are modified:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/src", {
    include: ["*.ts", "*.tsx"],
});

let buildInProgress = false;

for await (const event of parseSSEStream(stream)) {
    if (
        event.type === "event" &&
        event.eventType === "modify" &&
        !buildInProgress
    ) {
        buildInProgress = true;
        console.log(`File changed: ${event.path}, rebuilding...`);

try {
            const result = await sandbox.exec("npm run build");
            if (result.success) {
                console.log("Build completed successfully");
            } else {
                console.error("Build failed:", result.stderr);
            }
        } catch (error) {
            console.error("Build error:", error);
        } finally {
            buildInProgress = false;
        }
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/src", {
    include: ["*.ts", "*.tsx"],
});

let buildInProgress = false;

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (
        event.type === "event" &&
        event.eventType === "modify" &&
        !buildInProgress
    ) {
        buildInProgress = true;
        console.log(`File changed: ${event.path}, rebuilding...`);

try {
            const result = await sandbox.exec("npm run build");
            if (result.success) {
                console.log("Build completed successfully");
            } else {
                console.error("Build failed:", result.stderr);
            }
        } catch (error) {
            console.error("Build error:", error);
        } finally {
            buildInProgress = false;
        }
    }
}

Auto-run tests on change

Re-run tests when test files are modified:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/tests", {
    include: ["*.test.ts", "*.spec.ts"],
});

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event" && event.eventType === "modify") {
        console.log(`Test file changed: ${event.path}`);
        const result = await sandbox.exec(`npm test -- ${event.path}`);
        console.log(result.success ? "Tests passed" : "Tests failed");
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/tests", {
    include: ["*.test.ts", "*.spec.ts"],
});

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (event.type === "event" && event.eventType === "modify") {
        console.log(`Test file changed: ${event.path}`);
        const result = await sandbox.exec(`npm test -- ${event.path}`);
        console.log(result.success ? "Tests passed" : "Tests failed");
    }
}

Incremental indexing

Re-index only changed files instead of rescanning an entire directory tree:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/docs", {
    include: ["*.md", "*.mdx"],
});

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event") {
        switch (event.eventType) {
            case "create":
            case "modify":
                console.log(`Indexing ${event.path}...`);
                await indexFile(event.path);
                break;
            case "delete":
                console.log(`Removing ${event.path} from index...`);
                await removeFromIndex(event.path);
                break;
        }
    }
}
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/docs", {
    include: ["*.md", "*.mdx"],
});

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
    if (event.type === "event") {
        switch (event.eventType) {
            case "create":
            case "modify":
                console.log(`Indexing ${event.path}...`);
                await indexFile(event.path);
                break;
            case "delete":
                console.log(`Removing ${event.path} from index...`);
                await removeFromIndex(event.path);
                break;
        }
    }
}

Advanced patterns

Process events with a helper function

Extract event processing into a reusable function that handles stream lifecycle:

import { parseSSEStream } from "@cloudflare/sandbox";

async function watchFiles(sandbox, path, options, handler) {
    const stream = await sandbox.watch(path, options);

for await (const event of parseSSEStream(stream)) {
        switch (event.type) {
            case "watching":
                console.log(`Watching ${event.path}`);
                break;
            case "event":
                await handler(event.eventType, event.path, event.isDirectory);
                break;
            case "error":
                console.error(`Watch error: ${event.error}`);
                break;
            case "stopped":
                console.log(`Watch stopped: ${event.reason}`);
                return;
        }
    }
}

// Usage
await watchFiles(
    sandbox,
    "/workspace/src",
    { include: ["*.ts"] },
    async (eventType, filePath) => {
        console.log(`${eventType}: ${filePath}`);
    },
);
import { parseSSEStream } from "@cloudflare/sandbox";
import type { FileWatchSSEEvent } from "@cloudflare/sandbox";

async function watchFiles(
    sandbox: any,
    path: string,
    options: { include?: string[]; exclude?: string[] },
    handler: (
        eventType: string,
        filePath: string,
        isDirectory: boolean,
    ) => Promise<void>,
) {
    const stream = await sandbox.watch(path, options);

for await (const event of parseSSEStream<FileWatchSSEEvent>(stream)) {
        switch (event.type) {
            case "watching":
                console.log(`Watching ${event.path}`);
                break;
            case "event":
                await handler(event.eventType, event.path, event.isDirectory);
                break;
            case "error":
                console.error(`Watch error: ${event.error}`);
                break;
            case "stopped":
                console.log(`Watch stopped: ${event.reason}`);
                return;
        }
    }
}

// Usage
await watchFiles(
    sandbox,
    "/workspace/src",
    { include: ["*.ts"] },
    async (eventType, filePath) => {
        console.log(`${eventType}: ${filePath}`);
    },
);

Debounced file operations

Avoid excessive operations by collecting changes before processing:

import { parseSSEStream } from "@cloudflare/sandbox";

const stream = await sandbox.watch("/workspace/src");
const changedFiles = new Set();
let debounceTimeout = null;

for await (const event of parseSSEStream(stream)) {
    if (event.type === "event") {
        changedFiles.add(event.path);

if (debounceTimeout) {
            clearTimeout(debounceTimeout);
        }

debounceTimeout = setTimeout(async () => {
            console.log(`Processing ${changedFiles.size} changed files...`);
            for (const filePath of changedFiles) {
                await processFile(filePath);
            }
            changedFiles.clear();
            debounceTimeout = null;
        }, 1000);
    }
}

```ts import { parseSSEStream } from "@cloudflare/sandbox"; import type { FileWatchSSEEvent } from "@cloudflare/sandbox";