> ## 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.

# Quick Start Guide

> Build and deploy your first TagadaPay plugin in 15 minutes

# Quick Start: Build Your First Plugin

**Time**: \~15 minutes | **Difficulty**: Beginner

***

## What Is a Plugin?

A **plugin** is a web page (HTML/CSS/JS) that runs on TagadaPay's hosting infrastructure. Unlike a regular website, a plugin is connected to TagadaPay's backend — it gets automatic session management, funnel navigation, checkout data, and analytics.

```
┌─────────────────────────────────────────────────┐
│  You build a page (React, Vanilla JS, anything) │
│                      ↓                          │
│  TagadaPay hosts it on its global edge CDN      │
│                      ↓                          │
│  The Plugin SDK connects your page to:          │
│    • Sessions & authentication                  │
│    • Checkout data (products, prices, cart)      │
│    • Funnel navigation (checkout → thank you)   │
│    • A/B testing, analytics, custom domains      │
└─────────────────────────────────────────────────┘
```

<Info>
  **Plugin SDK = your bridge to TagadaPay.** Without it, your page is just static HTML on a CDN (which is fine — see the [Hosting & A/B Test guide](/developer-tools/hosting/deploy-and-ab-test)). With the SDK, your page becomes a fully integrated checkout experience with session tracking, payments, multi-step funnels, and more.
</Info>

***

## What You'll Build

A two-page checkout plugin:

1. **Checkout page** — displays a payment form, collects user info, processes payment
2. **Thank you page** — shows order confirmation with data from the checkout step

The SDK handles session creation, data passing between pages, and automatic redirects.

***

## Prerequisites

You need three things before starting:

| Requirement           | How to get it                                                                    |
| --------------------- | -------------------------------------------------------------------------------- |
| **Node.js 18+**       | [nodejs.org](https://nodejs.org) — run `node --version` to check                 |
| **TagadaPay account** | Sign up at [app.tagadapay.com](https://app.tagadapay.com) and create a **store** |
| **API key**           | Dashboard → Settings → API Keys → generate one                                   |

***

## Choose Your Framework

Pick the tab that matches how you want to build. Both produce the same result — a deployable plugin.

<Tabs>
  <Tab title="React (Recommended)">
    Best for rich, interactive UIs. Uses React hooks to access checkout data, funnel state, and more.

    ### 1. Create the project

    ```bash theme={null}
    npm create vite@latest my-checkout -- --template react-ts
    cd my-checkout
    npm install @tagadapay/plugin-sdk wouter
    npm install
    ```

    `wouter` is a lightweight router (3 KB) — the same one TagadaPay's own checkout template uses.

    ### 2. Create the plugin manifest

    The **manifest** tells TagadaPay what pages your plugin has and what data each page needs. Create `plugin.manifest.json` in your project root:

    ```json theme={null}
    {
      "name": "My Checkout",
      "pluginId": "my-checkout",
      "version": "1.0.0",
      "description": "A simple checkout plugin",
      "mode": "direct-mode",
      "category": "checkout",
      "pages": [
        {
          "path": "/checkout",
          "features": [
            {
              "type": "checkout",
              "requirements": [
                {
                  "resource": "checkoutSession",
                  "from": [{ "name": "checkoutToken", "type": "query" }]
                }
              ]
            }
          ],
          "remappable": true
        },
        {
          "path": "/thankyou/:orderId",
          "features": [
            {
              "type": "thankyou",
              "requirements": [
                {
                  "resource": "order",
                  "from": [{ "name": "id", "type": "path" }]
                }
              ]
            }
          ],
          "remappable": true
        }
      ],
      "router": {
        "basePath": "/",
        "matcher": ".*",
        "excluder": null
      },
      "requirements": {
        "sdk": "^2.2.0",
        "sdkVersion": "v2"
      }
    }
    ```

    <AccordionGroup>
      <Accordion title="What do these fields mean?" icon="book">
        | Field                 | Purpose                                                         |
        | --------------------- | --------------------------------------------------------------- |
        | `mode: "direct-mode"` | The plugin renders as a full page (not inside an iframe)        |
        | `pages`               | Each entry is a URL your plugin handles                         |
        | `features.type`       | What kind of page it is — `checkout`, `thankyou`, `offer`, etc. |
        | `requirements`        | What data the page needs (checkout session, order, etc.)        |
        | `remappable: true`    | Allows merchants to customize the URL path in their funnel      |
        | `router.matcher`      | Which URLs this plugin should handle (`.*` = all)               |
      </Accordion>
    </AccordionGroup>

    ### 3. Wrap your app with TagadaProvider

    Replace the contents of `src/main.tsx`:

    ```tsx theme={null}
    // src/main.tsx
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { TagadaProvider } from '@tagadapay/plugin-sdk/v2';
    import App from './App';
    import './index.css';

    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <TagadaProvider>
          <App />
        </TagadaProvider>
      </React.StrictMode>
    );
    ```

    `TagadaProvider` is the single entry point for the SDK. By wrapping your app, you get:

    * Automatic session creation
    * Funnel initialization (no setup code needed)
    * Access to all hooks (`useFunnel`, `useCheckout`, `useCurrency`, etc.)
    * Auto-redirect after `funnel.next()`

    ### 4. Build the checkout page

    Create `src/pages/CheckoutPage.tsx`:

    ```tsx theme={null}
    // src/pages/CheckoutPage.tsx
    import { useState } from 'react';
    import { useFunnel, useCheckout, useCurrency } from '@tagadapay/plugin-sdk/v2';

    export default function CheckoutPage() {
      const funnel = useFunnel();
      const { data: checkout, isLoading } = useCheckout();
      const { formatMoney } = useCurrency();
      
      const [formData, setFormData] = useState({
        email: '',
        name: '',
        cardNumber: '4242424242424242'
      });
      
      const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();

        // After processing payment, tell the funnel to move to the next step.
        // The SDK auto-redirects to the thank-you page.
        await funnel.next({
          type: 'payment_success',
          data: {
            resources: {
              order: {
                id: `ord_${Date.now()}`,
                amount: checkout?.total || 4900,
                currency: checkout?.currency || 'USD',
              },
              customer: {
                id: `cus_${Date.now()}`,
                email: formData.email,
                name: formData.name,
              },
              payment: {
                id: `pay_${Date.now()}`,
                status: 'succeeded',
              },
            },
          },
        });
      };
      
      if (isLoading || !funnel.context) {
        return <div>Loading checkout...</div>;
      }
      
      return (
        <div style={{ maxWidth: 600, margin: '0 auto', padding: '2rem' }}>
          <h1>Checkout</h1>
          
          {checkout && (
            <div style={{ background: '#f5f5f5', padding: '1rem', marginBottom: '2rem' }}>
              <h2>Order Summary</h2>
              <p><strong>Total:</strong> {formatMoney(checkout.total, checkout.currency)}</p>
            </div>
          )}
          
          <form onSubmit={handleSubmit}>
            <div style={{ marginBottom: '1rem' }}>
              <label>Email</label>
              <input
                type="email" value={formData.email}
                onChange={(e) => setFormData({ ...formData, email: e.target.value })}
                style={{ width: '100%', padding: '0.75rem', marginTop: '0.5rem' }}
                required
              />
            </div>
            <div style={{ marginBottom: '1rem' }}>
              <label>Name</label>
              <input
                type="text" value={formData.name}
                onChange={(e) => setFormData({ ...formData, name: e.target.value })}
                style={{ width: '100%', padding: '0.75rem', marginTop: '0.5rem' }}
                required
              />
            </div>
            <div style={{ marginBottom: '1rem' }}>
              <label>Card Number (Test: 4242...)</label>
              <input
                type="text" value={formData.cardNumber}
                onChange={(e) => setFormData({ ...formData, cardNumber: e.target.value })}
                style={{ width: '100%', padding: '0.75rem', marginTop: '0.5rem' }}
                required
              />
            </div>
            <button type="submit" style={{
              width: '100%', padding: '1rem', background: '#0070f3',
              color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer'
            }}>
              Pay {formatMoney(checkout?.total || 4900, checkout?.currency || 'USD')}
            </button>
          </form>
        </div>
      );
    }
    ```

    Key concept: `funnel.next()` sends data to the next step **and** auto-redirects the browser. The `resources` you pass (`order`, `customer`, `payment`) become available on the next page via `funnel.context.resources`.

    ### 5. Build the thank you page

    Create `src/pages/ThankYouPage.tsx`:

    ```tsx theme={null}
    // src/pages/ThankYouPage.tsx
    import { useFunnel, useCurrency } from '@tagadapay/plugin-sdk/v2';
    import { useParams } from 'wouter';

    export default function ThankYouPage() {
      const { orderId } = useParams<{ orderId: string }>();
      const funnel = useFunnel();
      const { formatMoney } = useCurrency();
      
      const order = funnel.context?.resources?.order;
      const customer = funnel.context?.resources?.customer;
      
      if (!funnel.context || !order) {
        return <div>Loading...</div>;
      }
      
      return (
        <div style={{ maxWidth: 600, margin: '0 auto', padding: '2rem', textAlign: 'center' }}>
          <div style={{ fontSize: '4rem' }}>&#10003;</div>
          <h1>Thank You for Your Purchase!</h1>
          <div style={{ background: '#f5f5f5', padding: '1.5rem', marginTop: '2rem', textAlign: 'left' }}>
            <p><strong>Order #:</strong> {order.id}</p>
            <p><strong>Amount:</strong> {formatMoney(order.amount, order.currency)}</p>
            <p><strong>Email:</strong> {customer?.email}</p>
          </div>
          <p style={{ marginTop: '2rem' }}>
            A confirmation email has been sent to {customer?.email}
          </p>
        </div>
      );
    }
    ```

    Notice how `order` and `customer` come from `funnel.context.resources` — exactly what was passed in `funnel.next()` on the checkout page. The SDK handles the data transfer automatically.

    ### 6. Set up routing

    Replace `src/App.tsx`:

    ```tsx theme={null}
    // src/App.tsx
    import { Route, Switch } from 'wouter';
    import CheckoutPage from './pages/CheckoutPage';
    import ThankYouPage from './pages/ThankYouPage';

    export default function App() {
      return (
        <Switch>
          <Route path="/checkout" component={CheckoutPage} />
          <Route path="/thankyou/:orderId" component={ThankYouPage} />
          <Route path="/" component={CheckoutPage} />
        </Switch>
      );
    }
    ```

    ### 7. Test locally

    ```bash theme={null}
    npm run dev
    # Open http://localhost:5173/checkout
    ```

    In local development, the SDK automatically enables **debug mode** — it creates a mock session and loads test data so you can develop without a live TagadaPay backend.
  </Tab>

  <Tab title="Vanilla JavaScript">
    No framework needed. Uses the standalone SDK with plain TypeScript and DOM manipulation.

    ### 1. Create the project

    ```bash theme={null}
    npm create vite@latest my-checkout -- --template vanilla-ts
    cd my-checkout
    npm install @tagadapay/plugin-sdk
    npm install
    ```

    ### 2. Create the plugin manifest

    Create `plugin.manifest.json` in your project root:

    ```json theme={null}
    {
      "name": "Vanilla Checkout",
      "pluginId": "vanilla-checkout",
      "version": "1.0.0",
      "description": "A minimal checkout with the standalone SDK",
      "mode": "direct-mode",
      "category": "checkout",
      "pages": [
        {
          "path": "/checkout",
          "features": [
            {
              "type": "checkout",
              "requirements": [
                {
                  "resource": "checkoutSession",
                  "from": [{ "name": "checkoutToken", "type": "query" }]
                }
              ]
            }
          ],
          "remappable": true
        }
      ],
      "router": {
        "basePath": "/",
        "matcher": ".*",
        "excluder": null
      },
      "requirements": {
        "sdk": "^2.2.0",
        "sdkVersion": "v2"
      }
    }
    ```

    ### 3. Write the HTML

    Replace `index.html`:

    ```html theme={null}
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Checkout | TagadaPay</title>
      </head>
      <body>
        <div id="app">
          <div class="checkout-container">
            <h1>Checkout</h1>
            <div id="loading" class="loading">Initializing checkout...</div>

            <div id="checkout-content" style="display: none">
              <div class="section">
                <h2>Order Summary</h2>
                <div id="order-items"></div>
                <div class="total-row">
                  <span>Total:</span>
                  <span id="total-amount">--</span>
                </div>
              </div>

              <form id="checkout-form">
                <label>Email: <input type="email" id="email" required /></label>
                <label>Name: <input type="text" id="name" required /></label>
                <button type="submit" id="pay-button">Pay Now</button>
              </form>

              <div id="error-message" class="error-message"></div>
            </div>
          </div>
        </div>
        <script type="module" src="/src/main.ts"></script>
      </body>
    </html>
    ```

    ### 4. Initialize the SDK

    Replace `src/main.ts`:

    ```typescript theme={null}
    // src/main.ts
    import './style.css';
    import {
      createTagadaClient,
      FunnelActionType,
      type TagadaState,
    } from '@tagadapay/plugin-sdk/v2/standalone';

    const loadingEl = document.getElementById('loading')!;
    const contentEl = document.getElementById('checkout-content')!;
    const payBtn = document.getElementById('pay-button')! as HTMLButtonElement;
    const emailInput = document.getElementById('email')! as HTMLInputElement;
    const nameInput = document.getElementById('name')! as HTMLInputElement;
    const errorEl = document.getElementById('error-message')!;
    const totalEl = document.getElementById('total-amount')!;
    const itemsEl = document.getElementById('order-items')!;

    let globalClient: ReturnType<typeof createTagadaClient> | null = null;

    function init() {
      try {
        const client = createTagadaClient({
          features: { funnel: true },
        });
        globalClient = client;

        // The funnel auto-initializes when features.funnel is true.
        // Subscribe to funnel state to know when it's ready.
        if (client.funnel) {
          client.funnel.subscribe((funnelState) => {
            if (funnelState.isInitialized && funnelState.context) {
              renderCheckout(funnelState.context, client.getState());
            } else if (funnelState.error) {
              showError(funnelState.error.message);
            }
          });
        }
      } catch (err) {
        showError(err instanceof Error ? err.message : 'SDK failed');
      }
    }

    function renderCheckout(context: Record<string, unknown>, state: TagadaState) {
      loadingEl.style.display = 'none';
      contentEl.style.display = 'block';

      const total = 4900; // $49.00
      itemsEl.innerHTML = `
        <div style="display:flex;justify-content:space-between">
          <span>1x Test Product</span><span>$49.00</span>
        </div>`;
      totalEl.textContent = `$${(total / 100).toFixed(2)}`;

      const form = document.getElementById('checkout-form')! as HTMLFormElement;
      form.onsubmit = async (e) => {
        e.preventDefault();
        payBtn.disabled = true;
        payBtn.textContent = 'Processing...';

        try {
          if (!globalClient?.funnel) throw new Error('Funnel not available');

          await globalClient.funnel.navigate({
            type: FunnelActionType.PAYMENT_SUCCESS,
            data: {
              payment: { id: `pay_${Date.now()}`, status: 'succeeded' },
              order: { id: `ord_${Date.now()}`, amount: total, currency: 'USD' },
            },
          });
        } catch (err) {
          payBtn.disabled = false;
          payBtn.textContent = 'Pay Now';
          showError('Payment failed');
        }
      };
    }

    function showError(msg: string) {
      loadingEl.style.display = 'none';
      errorEl.textContent = `Error: ${msg}`;
    }

    init();
    ```

    Key difference from React: instead of `TagadaProvider` and hooks, you create a `client` and subscribe to state changes. The funnel auto-initializes — no manual `autoInitialize()` call needed.

    ### 5. Add styles

    Replace `src/style.css`:

    ```css theme={null}
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 2rem; }
    .section { background: #f5f5f5; padding: 1rem; border-radius: 8px; margin: 1.5rem 0; }
    .total-row { display: flex; justify-content: space-between; font-weight: bold; margin-top: 1rem; }
    form { display: flex; flex-direction: column; gap: 1rem; margin-top: 2rem; }
    label { display: flex; flex-direction: column; gap: 0.5rem; }
    input { padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; }
    button { padding: 1rem; background: #0070f3; color: white; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer; }
    button:hover { background: #0051cc; }
    button:disabled { background: #ccc; cursor: not-allowed; }
    .error-message { color: #d32f2f; margin-top: 1rem; }
    .loading { text-align: center; padding: 4rem; color: #666; }
    ```

    ### 6. Test locally

    ```bash theme={null}
    npm run dev
    # Open http://localhost:5173
    ```
  </Tab>
</Tabs>

***

## Build & Deploy

Once your plugin works locally, deploy it to TagadaPay so it runs on the global edge CDN.

<Steps>
  <Step title="Build for production">
    ```bash theme={null}
    npm run build
    ```

    This creates a `dist/` folder with your compiled HTML, JS, and CSS.
  </Step>

  <Step title="Deploy with the CLI">
    ```bash theme={null}
    npm install -g @tagadapay/plugin-cli

    tgdcli login
    tgdcli deploy
    ```

    The CLI reads your `plugin.manifest.json`, zips your `dist/` folder, and uploads everything to TagadaPay. Follow the prompts to select your store.
  </Step>

  <Step title="Use it in a funnel">
    1. Go to **Funnels** in your [TagadaPay dashboard](https://app.tagadapay.com)
    2. Create a new funnel (or edit an existing one)
    3. Add a **Checkout** step → select your plugin → pick the `/checkout` page
    4. Add a **Thank You** step → select your plugin → pick `/thankyou/:orderId`
    5. **Save** and test your funnel
  </Step>
</Steps>

<Tip>
  You can also deploy via the [REST API](/developer-tools/hosting/deploy-and-ab-test) or upload a ZIP from the dashboard. The CLI is just the fastest option.
</Tip>

***

## What the SDK Handles for You

You don't need to write code for any of this — the SDK does it automatically:

| Feature              | What happens                                                                        |
| -------------------- | ----------------------------------------------------------------------------------- |
| **Session**          | An anonymous session is created on first visit and persisted across page loads      |
| **Funnel init**      | The funnel context (current step, data from previous steps) is loaded automatically |
| **Redirects**        | After `funnel.next()`, the browser redirects to the next step URL                   |
| **Debug mode**       | Enabled automatically on `localhost` — uses mock data so you can develop offline    |
| **Config injection** | `window.__TAGADA_PLUGIN_CONFIG__` is injected with your instance config at runtime  |

***

## Common Issues

<AccordionGroup>
  <Accordion title="useFunnel() returns undefined" icon="circle-exclamation">
    Your app is not wrapped with `<TagadaProvider>`. Make sure `main.tsx` looks like:

    ```tsx theme={null}
    <TagadaProvider>
      <App />
    </TagadaProvider>
    ```
  </Accordion>

  <Accordion title="Data is empty on the thank you page" icon="circle-exclamation">
    Make sure you pass data inside the `resources` key:

    ```tsx theme={null}
    await funnel.next({
      data: {
        resources: {     // ← must be inside "resources"
          order: { ... },
          customer: { ... }
        }
      }
    });
    ```

    On the next page, access it via `funnel.context?.resources?.order`.
  </Accordion>

  <Accordion title="Redirect not working after funnel.next()" icon="circle-exclamation">
    1. Check the console for errors after `funnel.next()` resolves
    2. Make sure the funnel has a next step configured in the dashboard
    3. In local dev, the SDK may not redirect if there's no real funnel — this is expected

    ```tsx theme={null}
    const result = await funnel.next({ ... });
    console.log('Navigation result:', result);
    ```
  </Accordion>

  <Accordion title="TypeScript: cannot find module '@tagadapay/plugin-sdk/v2'" icon="circle-exclamation">
    Make sure you import from the correct path:

    ```tsx theme={null}
    // React hooks
    import { useFunnel } from '@tagadapay/plugin-sdk/v2';

    // Standalone (vanilla JS)
    import { createTagadaClient } from '@tagadapay/plugin-sdk/v2/standalone';
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Build a Multi-Step Funnel" icon="graduation-cap" href="/developer-tools/sdk/tutorial">
    Add an upsell page between checkout and thank you, learn static resources
  </Card>

  <Card title="Plugin Manifest Guide" icon="file-code" href="/developer-tools/sdk/manifest">
    Deep dive into pages, features, requirements, and configuration presets
  </Card>

  <Card title="Funnel Resources" icon="database" href="/developer-tools/sdk/funnel-resources">
    Pass complex data (orders, customers, offers) between steps
  </Card>

  <Card title="API Reference" icon="book" href="/developer-tools/sdk/api">
    All available hooks and methods
  </Card>

  <Card title="Host Without SDK" icon="flask-vial" href="/developer-tools/hosting/deploy-and-ab-test">
    Deploy static HTML and run A/B tests without the SDK
  </Card>

  <Card title="CLI Reference" icon="terminal" href="/developer-tools/cli/introduction">
    Automate deploys with the command-line tool
  </Card>
</CardGroup>
