> ## Documentation Index
> Fetch the complete documentation index at: https://configurate.crystaworld.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Watch Config Files for External and Internal Changes

> Use watchExternal() and onChange() to react to config changes made by external processes or your own application, and clean up listeners when done.

Your application often needs to know the moment a config value changes — whether the change originated inside your own code or was made by an external tool editing the file directly on disk. tauri-plugin-configurate exposes two dedicated methods for this: `onChange` listens for operations your app performs, and `watchExternal` hands you a live feed of changes made by other processes. Both return cleanup functions so you can remove listeners precisely when they are no longer needed.

## onChange(callback)

`onChange` registers a listener that fires whenever your application performs any write operation on this config target. The operations it covers are: `create`, `save`, `patch`, `delete`, `reset`, and `import`. It does **not** fire for changes made by external processes — use `watchExternal` for that.

The method is **async** and returns a `Promise<() => void>`. You must `await` it to get the unlisten function; the unlisten function itself is synchronous. Call it when you no longer need the listener.

```typescript theme={null}
const unlisten = await config.onChange((event) => {
  console.log(`Config ${event.operation} on ${event.fileName}`);
});

// Stop listening when done
unlisten();
```

`onChange` is useful for keeping derived UI state in sync — for example, marking a settings form as "saved" after a `save` or `patch` fires.

## watchExternal(callback)

`watchExternal` registers an OS-level file-system watcher on the config file and calls your callback whenever an external process modifies it. The callback receives a `ConfigChangeEvent` whose `operation` field is always `"external_change"`.

The method returns an **async** stop function. You must `await` it to cleanly unregister both the JS event listener and the Rust-side file watcher.

```typescript theme={null}
const stopWatching = await config.watchExternal((event) => {
  console.log(`External change detected: ${event.operation}`);
});

// Stop watching when done
await stopWatching();
```

<Warning>
  `watchExternal` is supported for **file-based providers only** (JSON, YAML, TOML, Binary). Calling it on a SQLite-backed config throws an error immediately. Always use `watchExternal` in a `try/catch` block if the provider type is not known at compile time.
</Warning>

## ConfigChangeEvent

Both `onChange` and `watchExternal` pass a `ConfigChangeEvent` to your callback:

```typescript theme={null}
interface ConfigChangeEvent {
  fileName: string;   // Config file name (e.g. "app.json")
  operation: string;  // "create" | "save" | "patch" | "delete" | "reset" | "import" | "external_change"
  targetId: string;   // Unique identifier for this Configurate instance
}
```

The `targetId` is derived from the combination of `fileName`, `baseDir`, provider kind, and path options. It lets you safely share a single global event bus and route events to the right handler when you have multiple `Configurate` instances.

## Typical pattern: auto-reload on external change

A common use case is reloading the in-memory config automatically when an external editor modifies the file — for example, a developer tweaking settings during development, or a companion CLI tool writing a config update.

```typescript theme={null}
const stopWatching = await config.watchExternal(async () => {
  const { data } = await config.load().run();
  applySettings(data);
});
```

Because the callback is invoked with the raw `ConfigChangeEvent`, you can also inspect `event.fileName` or `event.targetId` before deciding whether to reload.

## Cleaning up listeners

<Steps>
  <Step title="Store the cleanup function">
    Assign the return value of `onChange` or `watchExternal` to a variable in the scope that owns the listener's lifetime.

    ```typescript theme={null}
    let unlisten: (() => void) | null = null;
    let stopWatching: (() => Promise<void>) | null = null;

    unlisten = await config.onChange(handleChange);
    stopWatching = await config.watchExternal(handleExternal);
    ```
  </Step>

  <Step title="Call cleanup on unmount or teardown">
    In a React component, call cleanup inside the `useEffect` return. In a Svelte component, use `onDestroy`. In plain TypeScript, call them whenever the owning object is disposed.

    ```typescript theme={null}
    // React example
    useEffect(() => {
      let unlisten: (() => void) | undefined;

      config.onChange((event) => {
        setLastOp(event.operation);
      }).then((fn) => { unlisten = fn; });

      return () => { unlisten?.(); };
    }, []);
    ```
  </Step>

  <Step title="Await the watchExternal stop function">
    Unlike `onChange`, the stop function returned by `watchExternal` is async. Always `await` it so the Rust-side watcher is properly unregistered before the component or window closes.

    ```typescript theme={null}
    // Svelte example
    import { onDestroy } from "svelte";

    let stopWatching: (() => Promise<void>) | undefined;

    config.watchExternal(handleExternal).then((fn) => {
      stopWatching = fn;
    });

    onDestroy(async () => {
      await stopWatching?.();
    });
    ```
  </Step>
</Steps>

<Tip>
  Always clean up both `onChange` and `watchExternal` listeners when their owning component unmounts or their owning object is destroyed. Uncleaned listeners hold references to the Tauri event system and can cause memory leaks or ghost callbacks in long-running applications.
</Tip>
