diff --git a/README.md b/README.md
index 3f92dfb..98043ff 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Smart Configuration Loader.
 - Reads config from the nearest `package.json` file
 - [Extends configurations](https://github.com/unjs/c12#extending-configuration) from multiple local or git sources
 - Overwrite with [environment-specific configuration](#environment-specific-configuration)
+- Config watcher with auto reload
 
 ## Usage
 
@@ -36,10 +37,10 @@ Import:
 
 ```js
 // ESM
-import { loadConfig } from "c12";
+import { loadConfig, watchConfig } from "c12";
 
 // CommonJS
-const { loadConfig } = require("c12");
+const { loadConfig, watchConfig } = require("c12");
 ```
 
 Load configuration:
@@ -234,6 +235,29 @@ c12 tries to match [`envName`](#envname) and override environment config if spec
 }
 ```
 
+## Watching Configuration
+
+you can use `watchConfig` instead of `loadConfig` to load config and watch for changes, add and removals in all expected configuration paths and auto reload with new config,
+
+```ts
+import { watchConfig } from "c12";
+
+const config = watchConfig({
+  cwd: ".",
+  // chokidarOptions: {}, // Default is { ignoreInitial: true }
+  // debounce: 200 // Default is 100. You can set to fale to disable debounced watcher
+  onChange: ({ config, oldConfig, path, type }) => {
+    console.log("[watcher]", type, path);
+  },
+});
+
+console.log("initial config", config.config, config.layers);
+console.log("watching config files:", config.watchingFiles);
+
+// When exiting process
+await config.unwatch();
+```
+
 ## 💻 Development
 
 - Clone this repository
diff --git a/package.json b/package.json
index fe03419..31a5449 100644
--- a/package.json
+++ b/package.json
@@ -30,12 +30,14 @@
     "test:types": "tsc --noEmit"
   },
   "dependencies": {
+    "chokidar": "^3.5.3",
     "defu": "^6.1.2",
     "dotenv": "^16.0.3",
     "giget": "^1.1.2",
     "jiti": "^1.18.2",
     "mlly": "^1.2.0",
     "pathe": "^1.1.0",
+    "perfect-debounce": "^0.1.3",
     "pkg-types": "^1.0.2",
     "rc9": "^2.1.0"
   },
diff --git a/test/test.ts b/playground/load.ts
similarity index 89%
rename from test/test.ts
rename to playground/load.ts
index a46cabf..42d8864 100644
--- a/test/test.ts
+++ b/playground/load.ts
@@ -4,7 +4,7 @@ import { loadConfig } from "../src";
 const r = (path: string) => fileURLToPath(new URL(path, import.meta.url));
 
 async function main() {
-  const fixtureDir = r("./fixture");
+  const fixtureDir = r("../test/fixture");
   const config = await loadConfig({ cwd: fixtureDir, dotenv: true });
   console.log(config);
 }
diff --git a/playground/watch.ts b/playground/watch.ts
new file mode 100644
index 0000000..f710cef
--- /dev/null
+++ b/playground/watch.ts
@@ -0,0 +1,27 @@
+import { fileURLToPath } from "node:url";
+import { watchConfig } from "../src";
+
+const r = (path: string) => fileURLToPath(new URL(path, import.meta.url));
+
+async function main() {
+  const fixtureDir = r("../test/fixture");
+  const config = await watchConfig({
+    cwd: fixtureDir,
+    dotenv: true,
+    packageJson: ["c12", "c12-alt"],
+    globalRc: true,
+    envName: "test",
+    extend: {
+      extendKey: ["theme", "extends"],
+    },
+    onChange: ({ config, path, type }) => {
+      console.log("[watcher]", type, path);
+      console.log(config.config);
+    },
+  });
+  console.log("initial config", config.config, config.layers);
+  console.log("watching config files:", config.watchingFiles);
+}
+
+// eslint-disable-next-line unicorn/prefer-top-level-await
+main().catch(console.error);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e9c4a61..50452ea 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,6 +1,9 @@
 lockfileVersion: '6.0'
 
 dependencies:
+  chokidar:
+    specifier: ^3.5.3
+    version: 3.5.3
   defu:
     specifier: ^6.1.2
     version: 6.1.2
@@ -19,6 +22,9 @@ dependencies:
   pathe:
     specifier: ^1.1.0
     version: 1.1.0
+  perfect-debounce:
+    specifier: ^0.1.3
+    version: 0.1.3
   pkg-types:
     specifier: ^1.0.2
     version: 1.0.2
@@ -972,6 +978,14 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
+  /anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+    dev: false
+
   /argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
     dev: true
@@ -1037,6 +1051,11 @@ packages:
     engines: {node: '>=0.6'}
     dev: true
 
+  /binary-extensions@2.2.0:
+    resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+    engines: {node: '>=8'}
+    dev: false
+
   /blueimp-md5@2.19.0:
     resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
     dev: true
@@ -1066,7 +1085,6 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       fill-range: 7.0.1
-    dev: true
 
   /browserslist@4.21.5:
     resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
@@ -1213,6 +1231,21 @@ packages:
     resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
     dev: true
 
+  /chokidar@3.5.3:
+    resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+    engines: {node: '>= 8.10.0'}
+    dependencies:
+      anymatch: 3.1.3
+      braces: 3.0.2
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: false
+
   /chownr@2.0.0:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
@@ -1990,7 +2023,6 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       to-regex-range: 5.0.1
-    dev: true
 
   /find-up@4.1.0:
     resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
@@ -2062,7 +2094,6 @@ packages:
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
     os: [darwin]
     requiresBuild: true
-    dev: true
     optional: true
 
   /function-bind@1.1.1:
@@ -2141,7 +2172,6 @@ packages:
     engines: {node: '>= 6'}
     dependencies:
       is-glob: 4.0.3
-    dev: true
 
   /glob-parent@6.0.2:
     resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
@@ -2372,6 +2402,13 @@ packages:
       has-bigints: 1.0.2
     dev: true
 
+  /is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+    dependencies:
+      binary-extensions: 2.2.0
+    dev: false
+
   /is-boolean-object@1.1.2:
     resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
     engines: {node: '>= 0.4'}
@@ -2420,7 +2457,6 @@ packages:
   /is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
@@ -2432,7 +2468,6 @@ packages:
     engines: {node: '>=0.10.0'}
     dependencies:
       is-extglob: 2.1.1
-    dev: true
 
   /is-inside-container@1.0.0:
     resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
@@ -2461,7 +2496,6 @@ packages:
   /is-number@7.0.0:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
-    dev: true
 
   /is-path-inside@3.0.3:
     resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
@@ -2861,6 +2895,11 @@ packages:
       validate-npm-package-license: 3.0.4
     dev: true
 
+  /normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+    dev: false
+
   /npm-run-path@4.0.1:
     resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
     engines: {node: '>=8'}
@@ -3055,6 +3094,10 @@ packages:
     resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
     dev: true
 
+  /perfect-debounce@0.1.3:
+    resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==}
+    dev: false
+
   /picocolors@1.0.0:
     resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
     dev: true
@@ -3062,7 +3105,6 @@ packages:
   /picomatch@2.3.1:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
-    dev: true
 
   /pkg-types@1.0.2:
     resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==}
@@ -3149,6 +3191,13 @@ packages:
       type-fest: 0.6.0
     dev: true
 
+  /readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+    dependencies:
+      picomatch: 2.3.1
+    dev: false
+
   /regexp-tree@0.1.24:
     resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==}
     hasBin: true
@@ -3519,7 +3568,6 @@ packages:
     engines: {node: '>=8.0'}
     dependencies:
       is-number: 7.0.0
-    dev: true
 
   /tsconfig-paths@3.14.2:
     resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==}
diff --git a/src/index.ts b/src/index.ts
index a4aa8cb..f274ee9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,4 @@
 export * from "./dotenv";
 export * from "./loader";
 export * from "./types";
+export * from "./watch";
diff --git a/src/watch.ts b/src/watch.ts
new file mode 100644
index 0000000..80d4b99
--- /dev/null
+++ b/src/watch.ts
@@ -0,0 +1,110 @@
+import { watch, WatchOptions } from "chokidar";
+import { debounce } from "perfect-debounce";
+import { resolve } from "pathe";
+import type {
+  UserInputConfig,
+  ConfigLayerMeta,
+  ResolvedConfig,
+  LoadConfigOptions,
+} from "./types";
+import { loadConfig } from "./loader";
+
+export type ConfigWatcher<
+  T extends UserInputConfig = UserInputConfig,
+  MT extends ConfigLayerMeta = ConfigLayerMeta
+> = ResolvedConfig<T, MT> & {
+  watchingFiles: string[];
+  unwatch: () => Promise<void>;
+};
+
+export type WatchConfigOptions<
+  T extends UserInputConfig = UserInputConfig,
+  MT extends ConfigLayerMeta = ConfigLayerMeta
+> = {
+  chokidarOptions?: WatchOptions;
+  debounce?: false | number;
+  onChange?: (payload: {
+    type: "created" | "updated" | "removed";
+    path: string;
+    config: ResolvedConfig<T, MT>;
+    oldConfig: ResolvedConfig<T, MT>;
+  }) => void;
+};
+
+const eventMap = {
+  add: "created",
+  change: "updated",
+  unlink: "removed",
+} as const;
+
+export async function watchConfig<
+  T extends UserInputConfig = UserInputConfig,
+  MT extends ConfigLayerMeta = ConfigLayerMeta
+>(
+  options: LoadConfigOptions<T, MT> & WatchConfigOptions
+): Promise<ConfigWatcher<T, MT>> {
+  let config = await loadConfig<T, MT>(options);
+
+  const configName = options.name || "config";
+  const watchingFiles = [
+    ...new Set(
+      (config.layers || [])
+        .filter((l) => l.cwd)
+        .flatMap((l) => [
+          ...["ts", "js", "mjs", "cjs", "cts", "mts", "json"].map((ext) =>
+            resolve(l.cwd!, (options.name || "config") + "." + ext)
+          ),
+          l.source && resolve(l.cwd!, l.source),
+          // TODO: Support watching rc from home and workspace
+          options.rcFile &&
+            resolve(
+              l.cwd!,
+              typeof options.rcFile === "string"
+                ? options.rcFile
+                : `.${configName}rc`
+            ),
+          options.packageJson && resolve(l.cwd!, "package.json"),
+        ])
+        .filter(Boolean)
+    ),
+  ] as string[];
+
+  const _fswatcher = watch(watchingFiles, {
+    ignoreInitial: true,
+    ...options.chokidarOptions,
+  });
+
+  const onChange = async (event: string, path: string) => {
+    const type = eventMap[event as keyof typeof eventMap];
+    if (!type) {
+      return;
+    }
+    const oldConfig = config;
+    config = await loadConfig(options);
+    if (options.onChange) {
+      options.onChange({ type, path, config, oldConfig });
+    }
+  };
+
+  if (options.debounce !== false) {
+    _fswatcher.on("all", debounce(onChange, options.debounce));
+  } else {
+    _fswatcher.on("all", onChange);
+  }
+
+  const utils: Partial<ConfigWatcher<T, MT>> = {
+    watchingFiles,
+    unwatch: async () => {
+      await _fswatcher.close();
+    },
+  };
+
+  return new Proxy<ConfigWatcher<T, MT>>(utils as ConfigWatcher<T, MT>, {
+    get(_, prop) {
+      if (prop in utils) {
+        return utils[prop as keyof typeof utils];
+      }
+      return config[prop as keyof ResolvedConfig<T, MT>];
+    },
+  });
+}
diff --git a/tsconfig.json b/tsconfig.json
index 7d08257..9bdae7c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,6 +6,5 @@
     "esModuleInterop": true,
     "strict": true
   },
-  "include": ["src", "test"],
   "exclude": ["node_modules"]
 }