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

# Navigate inside your page

> Move between sub-paths of your own page, read the segments back, and keep a selection in the URL.

Your page owns everything under its own path. `tap.nav.push` moves inside it,
`useTapParams()` reads the segments, and `tap.nav.setSearch` keeps a selection in the URL so
a link reopens what the owner was looking at.

```tsx theme={null}
import { useTap, useTapParams } from "@tappify/extension-sdk";

export default function Explore() {
  const tap = useTap();
  const params = useTapParams();
  const funnelId = params["0"] === "funnels" ? params["1"] : undefined;

  if (funnelId === undefined) {
    return (
      <button onClick={() => tap.nav.push("/explore/funnels/123")}>
        Open funnel 123
      </button>
    );
  }

  return <span>Funnel {funnelId}</span>;
}
```

## What the segments look like

`useTapParams()` hands back the sub-path split for you, keyed by position. There are no named
parameters, because your page declares no route pattern for the host to match.

| Key             | Is                                              |
| --------------- | ----------------------------------------------- |
| `pageId`        | The id of the page contribution the owner is on |
| `path`          | Everything under it, unsplit: `funnels/123`     |
| `"0"`, `"1"`, … | Those same segments, one per key                |

On `/projects/prj_1/ext/funnel-lab/explore/funnels/123` that is
`{ pageId: "explore", path: "funnels/123", "0": "funnels", "1": "123" }`.

## Where you can go

| Call                                 | Accepts                                                                                                                                                  |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tap.nav.push(path)`                 | A sub-path of your own page, or an absolute path under the current project                                                                               |
| `tap.nav.setSearch(params)`          | Query parameters on the current URL. `null` removes one                                                                                                  |
| `tap.nav.openSettings()`             | The owner's page for your install                                                                                                                        |
| `tap.nav.openChat(prompt, context?)` | The assistant, opened with your prompt in the owner's composer for them to send. `context` goes under it on a line naming your extension, capped at 2 KB |
| `tap.nav.openExternal(url)`          | Anywhere outside Tappify, through the host's exit affordance                                                                                             |

A path that starts with `/projects/` is taken as written; anything else is resolved under
`/projects/:projectId/ext/:extensionId`, which is why the sample above begins with the page
id. A target outside the current project does not navigate: the owner gets a toast naming
your extension, and the page stays where it was.

Those five calls are the whole of navigation. The host owns the router, which is what keeps a
failing extension from taking the page with it.

## Leaving Tappify

`openExternal` is the only way out, and the host draws the exit itself: a confirmation naming
your extension and the URL, then a new tab. The expand panel's footer carries the same exit,
labelled with your extension's name.

Two other escapes exist for the same reason — your code cannot reach the host's DOM:

```ts theme={null}
import { useTap } from "@tappify/extension-sdk";

export function useExports(summaryText: string, csv: string) {
  const tap = useTap();

  return {
    copy: () => tap.ui.copy(summaryText),
    download: () =>
      tap.ui.download(new Blob([csv], { type: "text/csv" }), "funnel.csv"),
  };
}
```

Both run in the host's own document, so a download from your widget lands where every other
Tappify download lands, and a copy confirms with a toast.
