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

# Plugin Examples

> Real-world funnel plugin examples

# Plugin Examples

Complete, production-ready examples for common funnel patterns.

<Info>
  **Looking for full working plugins?** We maintain **five open-source checkout plugins** (editorial, neon, luxe, solar, arcade)
  on GitHub — each a complete, deployable plugin with checkout + thank-you pages, all MIT-licensed.

  <CardGroup cols={2}>
    <Card title="Open-Source Examples" icon="github" href="/developer-tools/sdk/open-source-examples">
      Gallery of the five variants with screenshots and direct links to each plugin's source.
    </Card>

    <Card title="github.com/TagadaPay/plugins" icon="code-branch" href="https://github.com/TagadaPay/plugins">
      Clone the monorepo, run `pnpm dev`, fork, rebrand, ship.
    </Card>
  </CardGroup>
</Info>

<Tip>
  **Need to initialize a checkout programmatically?** Check out the [Initialize Checkout guide](/developer-tools/sdk/init-checkout-example) to learn how to create checkout sessions with line items.
</Tip>

The snippets below walk through **funnel patterns** (2-step, 3-step, conditional, form-wizard) in focused
stand-alone code blocks. For **end-to-end plugins with UI**, head to the [Open-Source Examples](/developer-tools/sdk/open-source-examples) page.

***

## Simple Checkout Funnel

### 2-Step: Checkout → Thank You

The most basic funnel with order data flow.

#### Manifest

```json theme={null}
{
  "name": "Simple Checkout",
  "pluginId": "simple-checkout",
  "version": "1.0.0",
  "mode": "direct-mode",
  "pages": [
    {
      "path": "/checkout",
      "features": [{ "type": "checkout" }]
    },
    {
      "path": "/thankyou/:orderId",
      "features": [{
        "type": "thankyou",
        "requirements": [{
          "resource": "order",
          "params": [{ "name": "id", "type": "path" }]
        }]
      }]
    }
  ]
}
```

#### Checkout Page

```tsx theme={null}
import { useFunnel, useCheckout, useCurrency } from '@tagadapay/plugin-sdk/v2';

export default function CheckoutPage() {
  const funnel = useFunnel();
  const { data: checkout } = useCheckout();
  const { formatMoney } = useCurrency();

  const handlePayment = async (paymentResult) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          order: {
            id: paymentResult.orderId,
            amount: checkout.total,
            currency: checkout.currency
          }
        }
      }
    });
  };
  
  return <div>{/* Your UI */}</div>;
}
```

#### Thank You Page

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

export default function ThankYouPage() {
  const funnel = useFunnel();
  const order = funnel.context?.resources?.order;

  return (
    <div>
      <h1>Thank You!</h1>
      <p>Order #{order.id} confirmed</p>
    </div>
  );
}
```

***

## Upsell Funnel

### 3-Step: Checkout → Upsell → Thank You

Track multiple orders with named resources.

#### Manifest

```json theme={null}
{
  "pages": [
    {
      "path": "/checkout",
      "features": [{ "type": "checkout" }]
    },
    {
      "path": "/upsell/:orderId",
      "features": [{
        "type": "upsell",
        "requirements": [{
          "resource": "order",
          "params": [{ "name": "id", "type": "path" }]
        }]
      }]
    },
    {
      "path": "/thankyou",
      "features": [{ "type": "thankyou" }]
    }
  ]
}
```

#### Checkout Page

```tsx theme={null}
export default function CheckoutPage() {
  const funnel = useFunnel();
  
  const handlePayment = async (result) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          // Generic (for next step)
          order: { id: result.orderId, amount: 100 },
          // Named (persists through funnel)
          mainOrder: { id: result.orderId, amount: 100 },
          customer: { id: result.customerId, email: result.email }
        }
      }
    });
  };
  
  return <div>{/* Checkout UI */}</div>;
}
```

#### Upsell Page

```tsx theme={null}
export default function UpsellPage() {
  const funnel = useFunnel();
  
  // Access main order
  const mainOrder = funnel.context?.resources?.mainOrder;

  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          // New order (overwrites generic)
          order: { id: 'upsell_123', amount: 50 },
          // Named upsell order
          upsellOrder: { id: 'upsell_123', amount: 50 }
        }
      }
    });
  };

  const handleDecline = async () => {
    await funnel.next({
      type: 'offer_declined'
    });
  };

  return (
    <div>
      <h1>Special Offer!</h1>
      <p>You ordered: {mainOrder.id}</p>
      <button onClick={handleAccept}>Accept $50 Upsell</button>
      <button onClick={handleDecline}>No Thanks</button>
    </div>
  );
}
```

#### Thank You Page

```tsx theme={null}
export default function ThankYouPage() {
  const funnel = useFunnel();
  
  // Access both orders
  const mainOrder = funnel.context?.resources?.mainOrder;
  const upsellOrder = funnel.context?.resources?.upsellOrder;
  
  const totalRevenue = (mainOrder?.amount || 0) + (upsellOrder?.amount || 0);

  return (
    <div>
      <h1>Thank You!</h1>
      <p>Main Order: ${mainOrder.amount}</p>
      {upsellOrder && <p>Upsell: ${upsellOrder.amount}</p>}
      <p>Total: ${totalRevenue}</p>
    </div>
  );
}
```

***

## Multi-Offer Funnel

### 4-Step: Checkout → Upsell 1 → Upsell 2 → Thank You

Use collections for multiple offers.

#### Checkout Page

```tsx theme={null}
export default function CheckoutPage() {
  const funnel = useFunnel();
  
  const handlePayment = async (result) => {
    await funnel.next({
      type: 'payment_success',
      data: {
        resources: {
          order: { id: result.orderId, amount: 100, type: 'main' },
          mainOrder: { id: result.orderId, amount: 100, type: 'main' },
          // Initialize orders collection
          orders: [
            { id: result.orderId, amount: 100, type: 'main' }
          ]
        }
      }
    });
  };

  return <div>{/* Checkout UI */}</div>;
}
```

#### Upsell Page 1

```tsx theme={null}
export default function Upsell1Page() {
  const funnel = useFunnel();
  const existingOrders = funnel.context?.resources?.orders || [];

  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          order: { id: 'up1_123', amount: 50, type: 'upsell1' },
          upsell1Order: { id: 'up1_123', amount: 50, type: 'upsell1' },
          // Add to collection
          orders: [
            ...existingOrders,
            { id: 'up1_123', amount: 50, type: 'upsell1' }
      ]
        }
      }
    });
  };

  return <div>{/* Upsell 1 UI */}</div>;
}
```

#### Upsell Page 2

```tsx theme={null}
export default function Upsell2Page() {
  const funnel = useFunnel();
  const existingOrders = funnel.context?.resources?.orders || [];
  
  const handleAccept = async () => {
    await funnel.next({
      type: 'offer_accepted',
      data: {
        resources: {
          order: { id: 'up2_456', amount: 30, type: 'upsell2' },
          upsell2Order: { id: 'up2_456', amount: 30, type: 'upsell2' },
          // Add to collection
          orders: [
            ...existingOrders,
            { id: 'up2_456', amount: 30, type: 'upsell2' }
          ]
        }
      }
    });
  };
  
  return <div>{/* Upsell 2 UI */}</div>;
}
```

#### Thank You Page

```tsx theme={null}
export default function ThankYouPage() {
  const funnel = useFunnel();
  const orders = funnel.context?.resources?.orders || [];
  
  const totalRevenue = orders.reduce((sum, order) => sum + order.amount, 0);

  return (
    <div>
      <h1>Thank You!</h1>
      <h2>Your Orders:</h2>
      {orders.map(order => (
        <div key={order.id}>
          <p>{order.type}: ${order.amount}</p>
        </div>
      ))}
      <h3>Total Revenue: ${totalRevenue}</h3>
    </div>
  );
}
```

***

## Conditional Funnel

### Dynamic: Checkout → \[Upsell OR Thank You]

Navigate to different steps based on conditions.

#### Checkout Page

```tsx theme={null}
export default function CheckoutPage() {
  const funnel = useFunnel();
  const { data: checkout } = useCheckout();
  
  const handlePayment = async (result) => {
    const isHighValue = checkout.total > 100;
    
    await funnel.next({
      // Event type determines next step (configured in CRM)
      type: isHighValue ? 'high_value_purchase' : 'standard_purchase',
      data: {
        resources: {
          order: { id: result.orderId, amount: checkout.total },
          customer: { id: result.customerId }
        }
      }
    });
  };
  
  return <div>{/* Checkout UI */}</div>;
}
```

<Tip>
  The CRM's funnel configuration determines which page loads next based on the event type. High-value purchases go to upsell, standard purchases go straight to thank you.
</Tip>

***

## Form Wizard

### Multi-Step Form: Step 1 → Step 2 → Step 3 → Submit

Collect data across multiple pages.

#### Step 1: Personal Info

```tsx theme={null}
export default function Step1() {
  const funnel = useFunnel();
  const [formData, setFormData] = useState({ name: '', email: '' });
  
  const handleNext = async () => {
    await funnel.next({
      type: 'step_completed',
      data: {
        resources: {
          formData: {
            id: 'form_1',
            step1: formData
          }
        }
      }
    });
  };
  
  return <div>{/* Form fields */}</div>;
}
```

#### Step 2: Address

```tsx theme={null}
export default function Step2() {
  const funnel = useFunnel();
  const existingData = funnel.context?.resources?.formData;
  const [address, setAddress] = useState({ street: '', city: '' });
  
  const handleNext = async () => {
    await funnel.next({
      type: 'step_completed',
      data: {
        resources: {
          formData: {
            id: 'form_1',
            ...existingData,
            step2: address
          }
        }
      }
    });
  };
  
  return <div>{/* Address fields */}</div>;
}
```

#### Step 3: Review & Submit

```tsx theme={null}
export default function Step3() {
  const funnel = useFunnel();
  const formData = funnel.context?.resources?.formData;
  
  const handleSubmit = async () => {
    // Submit to API
    const result = await api.post('/submit', formData);
    
    await funnel.next({
      type: 'form_submitted',
      data: {
        resources: {
          submission: {
            id: result.id,
            status: 'completed'
          }
        }
      }
    });
  };

  return (
    <div>
      <h1>Review Your Information</h1>
      <p>Name: {formData.step1.name}</p>
      <p>Email: {formData.step1.email}</p>
      <p>Address: {formData.step2.street}, {formData.step2.city}</p>
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}
```

***

## Error Handling

### Handle Payment Failures

```tsx theme={null}
export default function CheckoutPage() {
  const funnel = useFunnel();
  const [error, setError] = useState(null);
  
  const handlePayment = async (paymentData) => {
    try {
      const result = await processPayment(paymentData);
      
      if (result.success) {
        await funnel.next({
          type: 'payment_success',
          data: {
            resources: {
              order: { id: result.orderId }
            }
          }
        });
      } else {
        // Stay on page, show error
        setError(result.error);
}
    } catch (error) {
      setError('Payment failed. Please try again.');
      
      // Optionally track failure
      await funnel.next({
        type: 'payment_failed',
        data: {
          resources: {
            error: {
              id: 'err_' + Date.now(),
              message: error.message
            }
          }
        }
      });
    }
  };

  return (
    <div>
      {error && <div className="error">{error}</div>}
      {/* Checkout form */}
    </div>
  );
}
```

***

## TypeScript Support

### Typed Resources

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

interface Order extends FunnelResourceData {
  amount: number;
  currency: string;
  items: OrderItem[];
}

interface Customer extends FunnelResourceData {
  email: string;
  name: string;
}

interface MyResources {
  order?: Order;
  customer?: Customer;
  mainOrder?: Order;
  upsellOrder?: Order;
}

// Use with funnel
export default function CheckoutPage() {
  const funnel = useFunnel<MyResources>();
  
  // Full type safety!
  const order = funnel.context?.resources?.order;
  const amount = order?.amount; // TypeScript knows this is a number
  
  return <div>{/* ... */}</div>;
}
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Always Use Named Resources for Important Data" icon="check">
    ```tsx theme={null}
    // ✅ Good - Both generic and named
    resources: {
      order: { id: 'ord_123' },      // Hot context
      mainOrder: { id: 'ord_123' }   // Persistent
    }

    // ❌ Bad - Only generic (can be lost)
    resources: {
      order: { id: 'ord_123' }
    }
    ```
  </Accordion>

  <Accordion title="Include IDs in All Resources" icon="check">
    ```tsx theme={null}
    // ✅ Good
    resources: {
      order: { id: 'ord_123', amount: 100 }
    }

    // ❌ Bad
    resources: {
      order: { amount: 100 } // No ID!
    }
    ```
  </Accordion>

  <Accordion title="Use Collections for Multiple Items" icon="check">
    ```tsx theme={null}
    // ✅ Good
    resources: {
      orders: [
        { id: 'ord_1', amount: 100 },
        { id: 'ord_2', amount: 50 }
      ]
    }

    // ❌ Bad
    resources: {
      order1: { id: 'ord_1' },
      order2: { id: 'ord_2' }
    }
    ```
  </Accordion>

  <Accordion title="Handle Loading States" icon="check">
    ```tsx theme={null}
    // ✅ Good
    const { data, isLoading } = useCheckout();
    if (isLoading) return <Spinner />;

    // ❌ Bad
    const { data } = useCheckout();
    return <div>{data.total}</div>; // Crash if loading!
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/developer-tools/sdk/api">
    Complete hook documentation
  </Card>

  <Card title="Funnel Resources" icon="database" href="/developer-tools/sdk/funnel-resources">
    Advanced resource patterns
  </Card>

  <Card title="Best Practices" icon="star" href="/developer-tools/sdk/best-practices">
    Production tips and patterns
  </Card>

  <Card title="Deployment" icon="rocket" href="/developer-tools/sdk/deployment">
    Deploy your plugin
  </Card>
</CardGroup>
