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

# Funnel Navigation

> Send shoppers into a TagadaPay funnel from your own cart

# Funnel Navigation

`tagada.funnel.navigate()` takes the cart from the loaded checkout session, bootstraps an anonymous CMS session, and returns a redirect URL into a specific funnel step. It's the JS equivalent of the WooCommerce / Shopify checkout plugins — one call from your own site, one URL to redirect to.

<Info>
  **When to use it:**

  * The cart lives in your store (Shopify, WooCommerce, PrestaShop, custom) and you want to route the shopper into a TagadaPay marketing funnel.
  * You want the shopper to enter at a specific funnel step (`step_xxx`) rather than the funnel default.

  **When NOT to use it:**

  * You're building a fully self-hosted checkout page — use [Checkout Flow](/developer-tools/headless-sdk/checkout-flow) directly.
  * You only need a one-shot Tagada-hosted checkout link without a funnel — use `tagada.checkout.createSession()` and follow its `checkoutUrl`.
</Info>

***

## Prerequisites

`accountId` is required on `createHeadlessClient()` — `funnel.navigate()` will throw `TagadaError('missing_account_id')` without it.

A checkout session must be loaded first (`tagada.checkout.loadSession(...)`). The SDK sources the cart, currency, customer email, and promotion code from that session.

<Note>
  **React provider:** `TagadaHeadlessProvider` only forwards `accountId` through the full `config` prop, not the individual named props. Pass `config={{ storeId, accountId, environment }}` if you need funnel navigation in React.
</Note>

***

## Quick Start

<Tabs>
  <Tab title="Vanilla JavaScript">
    ```typescript theme={null}
    import { createHeadlessClient } from '@tagadapay/headless-sdk';

    const tagada = createHeadlessClient({
      storeId: 'store_abc123',
      accountId: 'acc_abc123',
      environment: 'production',
    });

    // 1. Load (or create) a checkout session — populates the cart.
    await tagada.checkout.loadSession(checkoutToken, sessionToken);

    // 2. Get a redirect URL into the funnel.
    const { url } = await tagada.funnel.navigate({
      funnelId: 'fnlv2_abc123',
      stepId:   'step_abc123',
      returnUrl: 'https://merchant.com/thank-you',
    });

    // 3. Send the shopper there.
    window.location.assign(url);
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={null}
    import {
      TagadaHeadlessProvider,
      useHeadlessClient,
    } from '@tagadapay/headless-sdk/react';

    function App() {
      return (
        <TagadaHeadlessProvider
          config={{
            storeId: 'store_abc123',
            accountId: 'acc_abc123',
            environment: 'production',
          }}
        >
          <BuyNowButton />
        </TagadaHeadlessProvider>
      );
    }

    function BuyNowButton() {
      const sdk = useHeadlessClient();

      const handleBuy = async () => {
        await sdk.checkout.loadSession(checkoutToken, sessionToken);
        const { url } = await sdk.funnel.navigate({
          funnelId: 'fnlv2_abc123',
          stepId:   'step_abc123',
          returnUrl: 'https://merchant.com/thank-you',
        });
        window.location.assign(url);
      };

      return <button onClick={handleBuy}>Buy now</button>;
    }
    ```
  </Tab>

  <Tab title="CDN / Script Tag">
    ```html theme={null}
    <script src="https://cdn.jsdelivr.net/npm/@tagadapay/headless-sdk/dist/tagada-headless.min.js"></script>
    <script>
      const tagada = TagadaHeadless.create({
        storeId: 'store_abc123',
        accountId: 'acc_abc123',
        environment: 'production',
      });

      await tagada.checkout.loadSession(checkoutToken, sessionToken);
      const { url } = await tagada.funnel.navigate({
        funnelId: 'fnlv2_abc123',
        stepId:   'step_abc123',
        returnUrl: 'https://merchant.com/thank-you',
      });

      window.location.assign(url);
    </script>
    ```
  </Tab>
</Tabs>

***

## What Happens Under the Hood

`navigate()` runs the same sequence the WooCommerce plugin does, with caching so repeat calls in the same page are cheap:

1. `POST /api/v1/cms/session/anonymous` — creates an anonymous CMS session, returns a token.
2. `POST /api/v1/cms/session/v2/init` — initializes the CMS session and binds a CMS customer id.
3. `POST /api/v1/funnel/initialize` — creates the funnel session for `funnelId` + `stepId`.
4. `POST /api/v1/funnel/navigate` — fires an `INIT_CHECKOUT` event with the cart and returns `{ url }`.

The CMS token, session id, and customer id are cached on the module instance. Calling `navigate()` again in the same page skips steps 1–3.

***

## `navigate()` Input Reference

| Field           | Type                             | Required | Notes                                                                                 |
| --------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------- |
| `funnelId`      | `string`                         | yes      | Funnel to enter (e.g. `fnlv2_xxx`).                                                   |
| `stepId`        | `string`                         | yes      | Funnel step to land on. Used for both `entryStepId` and `funnelStepId`.               |
| `returnUrl`     | `string`                         | no       | Where the funnel should send the shopper back to after checkout.                      |
| `customer`      | `{ email?, currency?, locale? }` | no       | Overrides. Defaults to `session.customer.email` / `session.currency`.                 |
| `discountCodes` | `string[]`                       | no       | Defaults to `[session.promotionCode]` if a promo is applied, otherwise none.          |
| `metadata`      | `Record<string, unknown>`        | no       | Arbitrary metadata forwarded to the funnel-navigate event.                            |
| `currentUrl`    | `string`                         | no       | Page URL recorded by the funnel. Defaults to `globalThis.location?.href` in browsers. |
| `accountId`     | `string`                         | no       | Per-call override. Falls back to `HeadlessConfig.accountId`.                          |

***

## Return Value

```ts theme={null}
{
  url: string;             // Redirect destination.
  funnelSessionId: string; // From /funnel/initialize, useful for logging/debugging.
}
```

The SDK does not perform the redirect — call `window.location.assign(url)`, `router.push(url)`, or whatever your framework uses.

***

## Errors

<Note>
  `funnel.navigate()` throws typed `TagadaError`s:

  * **`no_cart_loaded`** — `sdk.checkout.loadSession(...)` was not called first. The SDK has no cart to send.
  * **`missing_account_id`** — `accountId` was not provided on `createHeadlessClient()` and not passed per call.

  Network and validation failures throw the standard `TagadaNetworkError` / `TagadaValidationError`.
</Note>

***

## SSR & Non-Browser Environments

The SDK reads `globalThis.location?.href` for `currentUrl` when running in a browser. In SSR / Node contexts, pass `currentUrl` explicitly:

```ts theme={null}
await sdk.funnel.navigate({
  funnelId,
  stepId,
  currentUrl: 'https://merchant.com/products/foo',
  returnUrl: 'https://merchant.com/thank-you',
});
```

Because `navigate()` returns `{ url }` rather than redirecting, it composes cleanly with SPA routers (`react-router`, `next/router`) and server-side handoffs.

***

<Tip>
  **Funnel navigation vs. `checkout.createSessionUrl()`:** Use `funnel.navigate()` to enter a marketing funnel (pre-checkout steps, upsell sequencing). Use `checkout.createSessionUrl()` when you just need a self-hosted checkout URL with no funnel logic in front of it.
</Tip>
