Skip to main content
tauri-plugin-configurate provides two complementary watching mechanisms: one for changes made by external processes (other apps, CLI tools, editors), and one for changes triggered by your own app’s operations. Both deliver a ConfigChangeEvent payload describing what changed and how.

watchExternal

config.watchExternal(callback) registers a filesystem watcher that fires whenever the config file is modified by a process outside your Tauri app. This is useful for synced config files, developer tooling, or any scenario where a config can change without going through your app’s own API.
const stopWatching = await config.watchExternal((event) => {
  console.log(`External change detected on: ${event.fileName}`);
  console.log(`Operation: ${event.operation}`); // always "external_change"
  console.log(`Target ID: ${event.targetId}`);
});
watchExternal returns an async function. Call it to stop watching and release the underlying file watcher:
// Stop watching later
await stopWatching();
watchExternal is only supported by file-based providers (JSON, YAML, TOML, Binary). Calling it on a Configurate instance that uses SqliteProvider will throw an error. SQLite handles durability internally and does not expose a file-watcher interface.

Reloading on external change

A common pattern is to reload the full config whenever an external change is detected:
const stopWatching = await config.watchExternal(async () => {
  const latest = await config.load().unlock(KEYRING);
  console.log("Config reloaded:", latest.data);
});

onChange

config.onChange(callback) registers a listener for any change triggered by your own app’s operations. The callback fires after each successful create, save, patch, delete, reset, or import.
const unlisten = await config.onChange((event) => {
  console.log(`${event.operation} on ${event.fileName}`);
});
onChange returns a synchronous function. Call it to stop listening:
// Stop listening later
unlisten();

Which operations trigger onChange

Operationevent.operation value
config.create(...)"create"
config.save(...)"save"
config.patch(...)"patch"
config.delete(...)"delete"
config.reset(...)"reset"
config.importFrom(...)"import"
External filesystem change"external_change"
onChange fires for all of the operations listed above, including external changes. watchExternal only fires for "external_change" events. If you want to react to both external and in-app changes in one callback, use onChange.

ConfigChangeEvent shape

Both watchExternal and onChange deliver a ConfigChangeEvent:
interface ConfigChangeEvent {
  fileName: string;   // The config file name, e.g. "app.json"
  operation: string;  // One of: "create" | "save" | "patch" | "delete" | "reset" | "import" | "external_change"
  targetId: string;   // Internal identifier scoped to this Configurate instance's location
}
targetId is a stable, opaque string derived from the Configurate constructor options (base directory, provider kind, file name, and path options). It is what the plugin uses internally to route events to the correct listener when multiple Configurate instances coexist.

Stopping watchers and listeners

Both watching mechanisms return a cleanup function. Call it when the component or module that registered the watcher is torn down to prevent memory leaks.
// Returns Promise<() => Promise<void>>
const stopWatching = await config.watchExternal((event) => {
  // ...
});

// Unregisters the Tauri event listener and calls unwatch_file on the Rust side
await stopWatching();

Full example: combined watcher

The following example registers both watchers and tears them down together when you call stop():
src/lib/config-watcher.ts
import { config, KEYRING } from "./config";

export async function startWatching() {
  const stopExternal = await config.watchExternal(async (event) => {
    console.log(`External edit to ${event.fileName} — reloading`);
    const latest = await config.load().unlock(KEYRING);
    // dispatch latest.data to your state management layer
  });

  const unlistenInApp = await config.onChange((event) => {
    console.log(`In-app ${event.operation} on ${event.fileName}`);
  });

  return async function stop() {
    await stopExternal();
    unlistenInApp();
  };
}

Build docs developers (and LLMs) love