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: 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 │
└─────────────────────────────────────────────────┘
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 ). With the SDK, your page becomes a fully integrated checkout experience with session tracking, payments, multi-step funnels, and more.
What You’ll Build
A two-page checkout plugin:
Checkout page — displays a payment form, collects user info, processes payment
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 — run node --version to checkTagadaPay account Sign up at 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.
React (Recommended)
Vanilla JavaScript
Best for rich, interactive UIs. Uses React hooks to access checkout data, funnel state, and more. 1. Create the project 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: {
"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"
}
}
What do these fields mean?
Field Purpose mode: "direct-mode"The plugin renders as a full page (not inside an iframe) pagesEach entry is a URL your plugin handles features.typeWhat kind of page it is — checkout, thankyou, offer, etc. requirementsWhat data the page needs (checkout session, order, etc.) remappable: trueAllows merchants to customize the URL path in their funnel router.matcherWhich URLs this plugin should handle (.* = all)
3. Wrap your app with TagadaProvider Replace the contents of src/main.tsx: // 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: // 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: // 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' } } > ✓ </ 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: // 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 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. No framework needed. Uses the standalone SDK with plain TypeScript and DOM manipulation. 1. Create the project 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: {
"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: <! 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: // 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: body { font-family : system-ui , sans-serif ; max-width : 600 px ; margin : 0 auto ; padding : 2 rem ; }
.section { background : #f5f5f5 ; padding : 1 rem ; border-radius : 8 px ; margin : 1.5 rem 0 ; }
.total-row { display : flex ; justify-content : space-between ; font-weight : bold ; margin-top : 1 rem ; }
form { display : flex ; flex-direction : column ; gap : 1 rem ; margin-top : 2 rem ; }
label { display : flex ; flex-direction : column ; gap : 0.5 rem ; }
input { padding : 0.75 rem ; border : 1 px solid #ddd ; border-radius : 4 px ; font-size : 1 rem ; }
button { padding : 1 rem ; background : #0070f3 ; color : white ; border : none ; border-radius : 4 px ; font-size : 1 rem ; cursor : pointer ; }
button :hover { background : #0051cc ; }
button :disabled { background : #ccc ; cursor : not-allowed ; }
.error-message { color : #d32f2f ; margin-top : 1 rem ; }
.loading { text-align : center ; padding : 4 rem ; color : #666 ; }
6. Test locally npm run dev
# Open http://localhost:5173
Build & Deploy
Once your plugin works locally, deploy it to TagadaPay so it runs on the global edge CDN.
Build for production
This creates a dist/ folder with your compiled HTML, JS, and CSS.
Deploy with the CLI
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.
Use it in a funnel
Go to Funnels in your TagadaPay dashboard
Create a new funnel (or edit an existing one)
Add a Checkout step → select your plugin → pick the /checkout page
Add a Thank You step → select your plugin → pick /thankyou/:orderId
Save and test your funnel
You can also deploy via the REST API or upload a ZIP from the dashboard. The CLI is just the fastest option.
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
useFunnel() returns undefined
Your app is not wrapped with <TagadaProvider>. Make sure main.tsx looks like: < TagadaProvider >
< App />
</ TagadaProvider >
Data is empty on the thank you page
Make sure you pass data inside the resources key: await funnel . next ({
data: {
resources: { // ← must be inside "resources"
order: { ... },
customer: { ... }
}
}
});
On the next page, access it via funnel.context?.resources?.order.
Redirect not working after funnel.next()
Check the console for errors after funnel.next() resolves
Make sure the funnel has a next step configured in the dashboard
In local dev, the SDK may not redirect if there’s no real funnel — this is expected
const result = await funnel . next ({ ... });
console . log ( 'Navigation result:' , result );
TypeScript: cannot find module '@tagadapay/plugin-sdk/v2'
Make sure you import from the correct path: // React hooks
import { useFunnel } from '@tagadapay/plugin-sdk/v2' ;
// Standalone (vanilla JS)
import { createTagadaClient } from '@tagadapay/plugin-sdk/v2/standalone' ;
Next Steps
Build a Multi-Step Funnel Add an upsell page between checkout and thank you, learn static resources
Plugin Manifest Guide Deep dive into pages, features, requirements, and configuration presets
Funnel Resources Pass complex data (orders, customers, offers) between steps
API Reference All available hooks and methods
Host Without SDK Deploy static HTML and run A/B tests without the SDK
CLI Reference Automate deploys with the command-line tool