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
Complete, production-ready examples for common funnel patterns.
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.
Open-Source Examples Gallery of the five variants with screenshots and direct links to each plugin’s source.
github.com/TagadaPay/plugins Clone the monorepo, run pnpm dev, fork, rebrand, ship.
Need to initialize a checkout programmatically? Check out the Initialize Checkout guide to learn how to create checkout sessions with line items.
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 page.
Simple Checkout Funnel
2-Step: Checkout → Thank You
The most basic funnel with order data flow.
Manifest
{
"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
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
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
{
"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
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
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
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
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
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
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
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
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 > ;
}
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.
Collect data across multiple pages.
Step 1: Personal Info
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
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
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
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
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
Always Use Named Resources for Important Data
// ✅ 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' }
}
Include IDs in All Resources
// ✅ Good
resources : {
order : { id : 'ord_123' , amount : 100 }
}
// ❌ Bad
resources : {
order : { amount : 100 } // No ID!
}
Use Collections for Multiple Items
// ✅ Good
resources : {
orders : [
{ id: 'ord_1' , amount: 100 },
{ id: 'ord_2' , amount: 50 }
]
}
// ❌ Bad
resources : {
order1 : { id : 'ord_1' },
order2 : { id : 'ord_2' }
}
// ✅ Good
const { data , isLoading } = useCheckout ();
if ( isLoading ) return < Spinner /> ;
// ❌ Bad
const { data } = useCheckout ();
return < div > { data . total } </ div > ; // Crash if loading!
Next Steps
API Reference Complete hook documentation
Funnel Resources Advanced resource patterns
Best Practices Production tips and patterns
Deployment Deploy your plugin