---
description: "Calls a listener whenever the page's URL changes without a page load."
---

# `watchPathname`

Calls a listener whenever the page's URL changes without a page load. It is what [`createWebAgents`](/docs/reference/core/create-web-agents) and [`usePathname`](/docs/reference/react/use-pathname) are built on, so nothing in your app has to be wired to a router. Reach for it when your own code must react to navigation.

## Import

```ts
import { watchPathname } from "@webmcp-agents/core";
```

## Example

```ts title="watch-url.ts"
import { watchPathname } from "@webmcp-agents/core";

// Fires for pushState, replaceState, back, forward and hash changes.
const stop = watchPathname(() => {
  console.log("now at", location.pathname + location.search);
});

// Later: stop();
export { stop };
```

## Options

`watchPathname(listener)`.

| Name | Type | Default | Description |
|---|---|---|---|
| `listener` | `() => void` | required | Called after the URL changed, with no arguments: read `location` inside it. The same function subscribed twice is called once. |

## Returns

An unsubscribe function: `() => void`. When the last watcher leaves, everything it added is removed.

## Notes

- It sees `history.pushState` and `replaceState` (so every client-side router), back and forward, and hash changes. With the Navigation API (`currententrychange`) that is one signal; without it (Safari before the API, Firefox before 147) `history.pushState` and `replaceState` are patched for as long as someone watches, and restored when the last watcher leaves.
- Nothing is listened to or patched while no one watches. On the server it does nothing and returns a no-op.
- Listeners are called just after the navigation call returns, once per burst, never inside it: Next.js changes the URL inside a React insertion effect, where telling React about it is an error.
- It fires for every change, including to the same path with a different query string; compare what you care about.
- It does not give the path a tool's `match` is tested against: a hash router's route (`#/orders`) counts as the path for `match`, not for `location.pathname`.

## See also

- [Tools across your app](/docs/build/tools-across-your-app), the `match` that follows the URL
- [`createWebAgents`](/docs/reference/core/create-web-agents), which watches for you
- [`usePathname`](/docs/reference/react/use-pathname), the same in React
- [`matchPath`](/docs/reference/core/match-path), testing a path against a pattern
