Skip to main content
Bun includes a built-in file watcher that automatically restarts your application when files change, with support for hot module reloading for supported frameworks.

Quick Start

Run with watch mode:
Watch mode
bun --watch run app.ts
Any changes to imported files trigger an automatic restart.

Watch Mode Features

Auto Restart

Automatically restarts on file changes

Fast Reloads

Optimized for sub-100ms restart times

Smart Detection

Only watches imported files, not entire project

Cross-Platform

Works on macOS, Linux, and Windows

Basic Usage

CLI Flag

Watch flag
# Run script with watch
bun --watch run server.ts

# Run file directly
bun --watch server.ts

# With arguments
bun --watch run app.ts --port=3000

Watch Patterns

Watch specific patterns
# Watch TypeScript files only
bun --watch "src/**/*.ts" run app.ts

# Multiple patterns
bun --watch "src/**/*.{ts,tsx}" run app.ts

Hot Module Reloading

For React and other frameworks, use --hot for hot module reloading:
Hot reload
bun --hot run app.tsx
Hot reload features:
  • Preserves application state
  • Updates modules without full restart
  • React Fast Refresh support
  • Framework-agnostic HMR API
Implementation: src/runtime.zig:149 - Hot module reloading flag

Watch Mode Implementation

Bun’s watcher uses platform-specific APIs:

macOS (kqueue)

Watcher platform (src/Watcher.zig:143-148)
const Platform = switch (Environment.os) {
    .linux => @import("./watcher/INotifyWatcher.zig"),
    .mac => @import("./watcher/KEventWatcher.zig"),
    .windows => WindowsWatcher,
    .wasm => @compileError("Unsupported platform"),
};
kqueue features:
  • File descriptor-based watching
  • Low latency notifications
  • Directory monitoring
Implementation: src/watcher/KEventWatcher.zig

Linux (inotify)

inotify features:
  • Kernel-level file monitoring
  • Efficient event batching
  • Recursive directory watching
Implementation: src/watcher/INotifyWatcher.zig

Windows

ReadDirectoryChangesW features:
  • Native Windows file watching
  • Buffer-based event delivery
  • Directory tree monitoring
Implementation: src/watcher/WindowsWatcher.zig

Watch Events

Watch events are tracked and delivered:
Watch events (src/Watcher.zig:1-49)
pub const Watcher = struct {
    watch_events: []WatchEvent = &.{},
    changed_filepaths: [max_count]?[:0]u8,
    
    platform: Platform,
    watchlist: WatchList,
    watched_count: usize,
    mutex: Mutex,
    
    ctx: *anyopaque,
    onFileUpdate: *const fn (this: *anyopaque, events: []WatchEvent, changed_files: []?[:0]u8, watchlist: WatchList) void,
    onError: *const fn (this: *anyopaque, err: bun.sys.Error) void,
};
Event types:
  • File modified
  • File created
  • File deleted
  • File renamed

Configuration

Watch Ignore Patterns

Ignore specific files/directories:
Ignore patterns
# Ignore node_modules
bun --watch --ignore "node_modules/**" run app.ts

# Multiple ignore patterns
bun --watch \
  --ignore "node_modules/**" \
  --ignore "dist/**" \
  --ignore "*.test.ts" \
  run app.ts

Watch Include Patterns

Only watch specific files:
Include patterns
# Only watch src directory
bun --watch "src/**/*.ts" run app.ts

Programmatic Watcher

Create a watcher programmatically:
Programmatic watcher
import { watch } from "fs";

const watcher = watch("./src", { recursive: true }, (eventType, filename) => {
  console.log(`${eventType}: ${filename}`);
});

// Stop watching
watcher.close();

Custom Watcher

Integrate Bun’s watcher:
Custom watcher integration
import type { Watcher } from "bun";

class MyWatcher {
  static onFileUpdate(
    events: Watcher.Event[],
    changedFiles: string[],
    watchlist: Watcher.ItemList,
  ) {
    console.log("Files changed:", changedFiles);
    
    // Custom reload logic
    for (const file of changedFiles) {
      console.log(`Reloading ${file}`);
    }
  }
}

React Fast Refresh

Enable React Fast Refresh for hot reloading:
React Fast Refresh
bun --hot run app.tsx
Features:
  • Preserves component state
  • Updates on save
  • Error overlay
  • Automatic boundary detection
React Fast Refresh example
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// Edit this component - state is preserved!
Implementation: src/runtime.zig:145-147 - React Fast Refresh feature

Performance

Debouncing

Bun debounces file changes to avoid excessive reloads:
  • Default debounce: 50ms
  • Batches multiple changes: Single reload for rapid file saves
  • Smart filtering: Ignores non-imported files

Optimization Tips

  1. Ignore large directories
    bun --watch --ignore "node_modules/**" run app.ts
    
  2. Watch specific patterns
    bun --watch "src/**/*.{ts,tsx}" run app.ts
    
  3. Minimize file I/O in watch callbacks

Development Workflows

Backend Server

Backend watch
bun --watch run server.ts
Express server
import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
Edit routes → automatic server restart.

Frontend Development

Frontend watch
bun --hot run dev.tsx
React app
import { createRoot } from "react-dom/client";
import App from "./App";

const root = createRoot(document.getElementById("root")!);
root.render(<App />);
Edit components → hot module reload.

Tests

Test watch
bun --watch test
Runs tests on file changes.

Watcher Trace

Debug watcher events:
Watcher trace
BUN_WATCHER_TRACE=1 bun --watch run app.ts
Outputs:
  • Files being watched
  • Change events
  • Reload triggers
Implementation: src/Watcher.zig:98-108 - Trace initialization

Error Handling

Watcher Errors

Handle watcher initialization errors:
Error handling
try {
  // Start watcher
} catch (err) {
  if (err.code === "ENOSPC") {
    console.error("Too many files to watch. Increase fs.inotify.max_user_watches");
  }
}

Linux: Increase Watch Limit

Increase inotify limit
# Temporary
sudo sysctl fs.inotify.max_user_watches=524288

# Permanent
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Advanced Features

Custom Watch Callback

Watch callback
import { Watcher } from "bun";

const watcher = new Watcher({
  onUpdate(events) {
    console.log(`${events.length} files changed`);
    // Custom reload logic
  },
  onError(error) {
    console.error("Watcher error:", error);
  },
});

Selective Watching

Only watch specific modules:
Selective watching
import { watch } from "fs";

const watchedModules = new Set(["./src/app.ts", "./src/config.ts"]);

watch("./src", { recursive: true }, (event, filename) => {
  if (watchedModules.has(filename)) {
    console.log(`Watched file changed: ${filename}`);
  }
});

Integration Examples

Express + Watch

Express watch
import express from "express";

const app = express();
let server: any;

function startServer() {
  server = app.listen(3000, () => {
    console.log("Server started");
  });
}

function stopServer() {
  server?.close();
}

startServer();

// Graceful reload on watch
process.on("SIGTERM", () => {
  stopServer();
});

Next.js-like Dev Server

Dev server
import { watch } from "fs";
import { build } from "bun";

async function buildAndServe() {
  await build({
    entrypoints: ["./src/index.tsx"],
    outdir: "./dist",
  });
  
  Bun.serve({
    port: 3000,
    fetch: () => new Response(Bun.file("./dist/index.html")),
  });
}

await buildAndServe();

watch("./src", { recursive: true }, async () => {
  console.log("Rebuilding...");
  await buildAndServe();
});

Troubleshooting

Watch Not Detecting Changes

  1. Check file is imported: Watcher only tracks imported files
  2. Check ignore patterns: File might be ignored
  3. Check watch limit (Linux): Increase fs.inotify.max_user_watches

High CPU Usage

  1. Ignore large directories: --ignore "node_modules/**"
  2. Reduce watch scope: --watch "src/**/*.ts"
  3. Increase debounce: Custom watcher with longer debounce

Slow Reloads

  1. Reduce imports: Minimize dependency graph
  2. Use hot reload: --hot instead of --watch
  3. Profile startup: Measure import time

Build docs developers (and LLMs) love