Plugin Manifest Guide
The plugin.manifest.json is the contract between your plugin and the TagadaPay platform. It defines what your plugin does, what data it needs, and what data it provides to other steps in a funnel.
Why the Manifest Matters
The manifest is critical for the Funnel Orchestrator to work correctly:
- Parameter Propagation: Tells the orchestrator what data your plugin needs (requirements) and what data it provides (resources)
- Type Safety: Ensures data flows correctly between funnel steps
- URL Building: Defines pages and their paths for routing
- Feature Detection: Allows the platform to discover what your plugin can do
Without a proper manifest, the orchestrator cannot resolve parameters or route users correctly through your
funnel.
Basic Structure
{
"name": "My Awesome Plugin",
"pluginId": "my-plugin",
"description": "A brief description of what your plugin does",
"version": "1.0.0",
"author": "Your Name",
"mode": "direct-mode",
"category": "checkout",
"tags": ["checkout", "payment"],
"configuration": {
"schema": "./config/schema.json",
"uiSchema": "./config/ui-schema.json",
"presets": []
},
"pages": [],
"router": {
"basePath": "/",
"matcher": ".*",
"excluder": null
},
"requirements": {
"sdk": "^2.2.0",
"sdkVersion": "v2"
}
}
Key Fields
- name: Display name shown in the CRM
- pluginId: Unique identifier for your plugin
- mode:
"direct-mode" for modern V2 plugins
- category: Plugin category (checkout, upsell, thankyou, custom)
- configuration: References to configuration schemas and presets
- router: Routing configuration for the plugin
- requirements: SDK version requirements
Configuration System
The configuration system allows you to define customizable settings for your plugin that can be edited in the CRM.
Configuration Structure
{
"configuration": {
"schema": "./config/schema.json",
"uiSchema": "./config/ui-schema.json",
"presets": [
{
"id": "default",
"name": "Default",
"description": "Default checkout configuration",
"config": "./config/default.config.json"
},
{
"id": "custom-theme",
"name": "Custom Theme",
"description": "Custom branded theme",
"config": "./config/custom-theme.config.json"
}
]
}
}
Configuration Files
schema.json - JSON Schema
Defines the structure and validation rules for your plugin configuration:
{
"type": "object",
"properties": {
"branding": {
"type": "object",
"properties": {
"primaryColor": {
"type": "string",
"format": "color"
}
}
}
}
}
ui-schema.json - UI Hints
Provides hints for rendering the configuration editor in the CRM:
{
"branding": {
"ui:order": ["primaryColor", "logo"],
"primaryColor": {
"ui:widget": "color"
}
}
}
Preset Configs
Preset configuration files contain complete plugin configurations that users can select in the CRM:
{
"branding": {
"primaryColor": "#3B82F6",
"logo": "https://example.com/logo.png"
},
"layout": {
"template": "modern"
}
}
The CRM automatically loads and validates these configurations when deploying your plugin.
Static Resources System
Static resources allow you to configure plugin-specific data (like offer IDs, product IDs, etc.) in the CRM’s funnel editor that can be accessed at runtime.
What Are Static Resources?
Static resources are configuration values that:
- Are defined in the funnel step configuration in the CRM
- Can be different per deployment/funnel
- Are accessible via
context.static in your plugin code
- Don’t require URL parameters
Defining Static Resources in Manifest
Use type: "static" in your requirements:
{
"path": "/offer",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
]
}
]
}
This tells the orchestrator: “This page needs an offer ID from static configuration.”
Accessing Static Resources in Code
import { useFunnel } from '@tagadapay/plugin-sdk/v2';
function OfferPage() {
const { context } = useFunnel();
// Access static resources
const offerId = context?.static?.offer?.id;
return <div>Offer ID: {offerId}</div>;
}
Local Development: resources.static.json
For local development, create /config/resources.static.json to mock static resources:
{
"offer": {
"id": "offer_ad2e64728dd8"
},
"product": {
"id": "product_abc123"
}
}
Important: This file is ONLY loaded in local development (localhost, *.localhost, ngrok). Values from
the backend (CRM) always take precedence. Do NOT deploy this file to production.
Example Use Cases
Single Offer Upsell Page
{
"path": "/upsell-special",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [{ "name": "id", "type": "context" }]
},
{
"resource": "offer",
"from": [{ "name": "id", "type": "static" }]
}
]
}
]
}
Configure in CRM: Set offer.id = "offer_abc123" for this step. The page will always show this specific offer.
Multi-Product Bundle Page
{
"path": "/bundle",
"features": [
{
"type": "custom",
"requirements": [
{
"resource": "products",
"from": [{ "name": "ids", "type": "static" }]
}
]
}
]
}
Configure in CRM: Set products.ids = ["prod_1", "prod_2", "prod_3"]. Your plugin receives all product IDs.
Pages Definition
Pages define the routes your plugin exposes. The orchestrator uses this to build URLs and route users.
Page Structure
{
"path": "/checkout",
"features": [
{
"type": "checkout",
"requirements": []
}
],
"remappable": true
}
Key Fields
- path: URL path for this page (can include path parameters like
:orderId)
- features: Array of features this page provides
- remappable: Whether this page can be remapped to a different URL in the funnel editor
Resource Requirement Types
The modern manifest uses a flexible requirements array with a from field that specifies where data comes from:
1. Query Parameters (type: "query")
Data passed in the URL query string:
{
"path": "/checkout",
"features": [
{
"type": "checkout",
"requirements": [
{
"resource": "checkoutSession",
"from": [
{
"name": "checkoutToken",
"type": "query"
}
]
}
]
}
],
"remappable": true
}
Result: /checkout?checkoutToken=tok_abc123
2. Path Parameters (type: "path")
Data embedded in the URL path:
{
"path": "/thankyou/:orderId",
"features": [
{
"type": "thankyou",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "path"
}
]
}
]
}
],
"remappable": true
}
Result: /thankyou/ord_abc123
3. Context Resources (type: "context")
Data from previous funnel steps (stored in session):
{
"path": "/offer",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
]
}
],
"remappable": true
}
The orchestrator automatically resolves order.id from the session and includes it in the URL.
4. Static Resources (type: "static")
Data configured in the CRM funnel editor (not in URL):
{
"path": "/offerfromstatic",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
},
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
]
}
],
"remappable": true
}
Accessing in code:
const { context } = useFunnel();
const offerId = context?.static?.offer?.id; // From CRM configuration
const orderId = context?.order?.id; // From session
Multi-Page Example (Complete Funnel)
{
"pages": [
{
"path": "/checkout",
"features": [
{
"type": "checkout",
"requirements": [
{
"resource": "checkoutSession",
"from": [
{
"name": "checkoutToken",
"type": "query"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/offer",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
},
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/thankyou/:orderId",
"features": [
{
"type": "thankyou",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "path"
}
]
}
]
}
],
"remappable": true
}
]
}
Requirements: What Your Plugin Needs
Requirements tell the orchestrator what data your plugin needs to function. The orchestrator will:
- Check if this data exists based on the
type (query, path, context, static)
- Resolve it from the appropriate source
- Make it available to your plugin via the SDK
Requirement Structure
Each requirement has:
- resource: The name of the resource (e.g.,
"order", "offer", "checkoutSession")
- from: Array of sources that can provide this data
{
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
]
}
Data Flow by Type
Query Parameters (type: "query")
{
"resource": "checkoutSession",
"from": [
{
"name": "checkoutToken",
"type": "query"
}
]
}
URL: /checkout?checkoutToken=tok_abc123
Access: Automatically handled by SDK, creates checkout session
Path Parameters (type: "path")
{
"resource": "order",
"from": [
{
"name": "id",
"type": "path"
}
]
}
URL: /thankyou/:orderId → /thankyou/ord_abc123
Access: Via context.order.id in your plugin
Context Resources (type: "context")
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
Source: Previous funnel steps (stored in session)
Access: Via context.order.id in your plugin
Static Resources (type: "static")
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
Source: CRM funnel editor configuration
Access: Via context.static.offer.id in your plugin
The orchestrator automatically resolves all requirements before loading your page, ensuring all required
data is available.
Multiple Requirements
A page can have multiple requirements from different sources:
{
"path": "/offerfromstatic",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
},
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
]
}
]
}
This page needs:
- An
order.id from the session (previous checkout step)
- An
offer.id from static configuration (CRM editor)
Resource Production: What Your Plugin Provides
Your plugin can produce resources that become available to subsequent funnel steps. While not explicitly declared in the modern manifest format, you emit resources via the SDK.
Emitting Resources
When your plugin completes an action, emit resources via the funnel SDK:
import { useFunnel } from '@tagadapay/plugin-sdk/v2';
function CheckoutPage() {
const funnel = useFunnel();
const handleCheckoutComplete = async (orderData) => {
// Navigate to next step with resources
await funnel.next({
resources: {
order: {
id: orderData.id,
total: orderData.total,
customer: {
email: orderData.customer.email,
id: orderData.customer.id
}
}
}
});
};
return <CheckoutForm onComplete={handleCheckoutComplete} />;
}
The orchestrator automatically stores these resources in the session and makes them available to subsequent
steps.
Accessing Resources in Next Steps
Resources emitted by previous steps are available via context:
function ThankYouPage() {
const { context } = useFunnel();
// Access order from previous checkout step
const orderId = context?.order?.id;
const orderTotal = context?.order?.total;
const customerEmail = context?.order?.customer?.email;
return (
<div>
<h1>Thank you for your order!</h1>
<p>Order ID: {orderId}</p>
<p>Total: ${orderTotal}</p>
<p>Confirmation sent to: {customerEmail}</p>
</div>
);
}
Resource Naming Best Practices
Use semantic, descriptive names:
// ✅ Good: Clear intent
resources: {
order: { id: 'ord_123' },
mainOrder: { id: 'ord_123' }, // For persistence
upsellOrder: { id: 'ord_456' }
}
// ❌ Bad: Unclear naming
resources: {
order1: { id: 'ord_123' },
order2: { id: 'ord_456' }
}
Remappable Pages
The remappable property determines whether a page can be remapped to a different URL in the funnel editor.
{
"path": "/checkout",
"features": [
{
"type": "checkout",
"requirements": []
}
],
"remappable": true
}
When to Use Remappable
remappable: true - Most pages should be remappable:
- Checkout pages
- Upsell pages
- Thank you pages
- Offer pages
This allows users to customize URLs in the funnel editor (e.g., /checkout → /buy-now).
remappable: false - Use for special routes:
- Wildcard routes (
/club/*)
- API endpoints
- Utility pages
{
"path": "/club/*",
"features": [
{
"type": "custom",
"requirements": [
{
"resource": "club"
}
]
}
],
"remappable": false
}
When a page is remapped, the SDK’s path remapping system automatically handles the URL translation. See the
Path Remapping Guide for details.
How the Orchestrator Uses the Manifest
1. Parameter Resolution
When navigating to a step, the orchestrator:
// Step URL template: /thankyou/:orderId
// Step requirements: { orderId: "string" }
// Orchestrator checks session for "orderId"
// If not found, looks for "order.id" in resources
// If found, resolves: /thankyou/ord_123
2. URL Building
The orchestrator builds URLs using:
- Mount Point ID: The funnel alias (e.g.,
my-funnel--store123.cdn.tagadapay.com)
- Page Path: From manifest (e.g.,
/thankyou/:orderId)
- Path Parameters: Resolved based on requirement type (query, path, context)
- Static Resources: Loaded from CRM configuration (not in URL)
Examples:
// Query parameter requirement
// URL: /checkout?checkoutToken=tok_abc123
// Path parameter requirement
// URL: /thankyou/ord_123
// Context requirement
// Orchestrator resolves from session, adds to URL: /offer?orderId=ord_123
// Static requirement
// Loaded from CRM config, accessed via context.static (not in URL)
3. Resource Propagation
The orchestrator maintains a session with all resources:
// After checkout completes
context = {
order: { id: 'ord_123', total: 99.99 },
static: {
/* CRM configuration */
},
};
// After upsell completes
context = {
order: { id: 'ord_123', total: 99.99 },
upsellOrder: { id: 'ord_456', total: 29.99 },
static: {
/* CRM configuration */
},
};
// All resources available to subsequent steps via useFunnel()!
Resource Naming Strategy
Use Descriptive Names
Choose names that clearly indicate what the resource represents:
// ✅ Good: Clear and semantic
resources: {
order: { id: 'ord_123' }, // Current/hot order
mainOrder: { id: 'ord_123' }, // Original checkout order
upsellOrder: { id: 'ord_456' }, // Upsell order
downsellOrder: { id: 'ord_789' } // Downsell order
}
// ❌ Bad: Unclear naming
resources: {
order: { id: 'ord_123' },
order2: { id: 'ord_456' },
order3: { id: 'ord_789' }
}
Generic vs Specific Resources
Generic Resources (Hot Context):
- Use common names like
order, product, customer
- Represent the “current” item in focus
- Can be overwritten by subsequent steps
// Checkout emits
resources: {
order: {
id: 'ord_123';
}
}
// Upsell also emits (overwrites)
resources: {
order: {
id: 'ord_456';
}
}
Specific Resources (Persistent):
- Use unique names like
mainOrder, upsellOrder
- Never get overwritten
- Available for the entire funnel session
// Better: Both generic and specific
resources: {
order: { id: 'ord_123' }, // Hot - for next step
mainOrder: { id: 'ord_123' } // Persistent - for later
}
Advanced Patterns
Multiple Path Parameters
Combine multiple path parameters with context requirements:
{
"path": "/order/:orderId/upsell/:productId",
"features": [
{
"type": "upsell",
"requirements": [
{
"resource": "order",
"from": [{ "name": "id", "type": "path" }]
},
{
"resource": "product",
"from": [{ "name": "id", "type": "path" }]
}
]
}
]
}
Result: /order/ord_123/upsell/prod_456
Combining Query and Context
Mix query parameters (user input) with context (session data):
{
"path": "/thankyou",
"features": [
{
"type": "thankyou",
"requirements": [
{
"resource": "order",
"from": [{ "name": "id", "type": "context" }]
},
{
"resource": "utm",
"from": [{ "name": "source", "type": "query" }]
}
]
}
]
}
Result: /thankyou?orderId=ord_123&utm_source=facebook
Complete Example: Multi-Step Funnel
{
"name": "Native Checkout",
"pluginId": "native-checkout",
"description": "Modern checkout with multiple page types",
"version": "2.0.0",
"author": "TagadaPay Team",
"mode": "direct-mode",
"category": "checkout",
"tags": ["checkout", "native", "v2"],
"configuration": {
"schema": "./config/schema.json",
"uiSchema": "./config/ui-schema.json",
"presets": [
{
"id": "default",
"name": "Default",
"description": "Default checkout configuration",
"config": "./config/default.config.json"
}
]
},
"pages": [
{
"path": "/checkout",
"features": [
{
"type": "checkout",
"requirements": [
{
"resource": "checkoutSession",
"from": [
{
"name": "checkoutToken",
"type": "query"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/offer",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/offerfromstatic",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
},
{
"resource": "offer",
"from": [
{
"name": "id",
"type": "static"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/thankyou/:orderId",
"features": [
{
"type": "thankyou",
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "path"
}
]
}
]
}
],
"remappable": true
},
{
"path": "/club/*",
"features": [
{
"type": "custom",
"requirements": [
{
"resource": "club"
}
]
}
],
"remappable": false
}
],
"router": {
"basePath": "/",
"matcher": ".*",
"excluder": null
},
"requirements": {
"sdk": "^2.2.0",
"sdkVersion": "v2"
}
}
How Data Flows Through This Funnel
Step 1: Checkout
// User navigates to: /checkout?checkoutToken=tok_123
// Requirement: checkoutSession from query parameter
// SDK automatically creates checkout session
// On completion, plugin emits resources:
funnel.next({
resources: {
order: {
id: 'ord_123',
total: 99.99,
customer: { email: '[email protected]', id: 'cus_456' },
},
},
});
Step 2: Dynamic Offer (Context)
// Orchestrator navigates to: /offer?orderId=ord_123
// Requirement: order.id from context (previous step)
// Orchestrator resolves: context.order.id = 'ord_123'
// In plugin:
const { context } = useFunnel();
const orderId = context?.order?.id; // 'ord_123'
// On completion:
funnel.next({
resources: {
upsellOrder: { id: 'ord_456', total: 29.99 },
},
});
Step 3: Static Offer (Static + Context)
// Orchestrator navigates to: /offerfromstatic?orderId=ord_123
// Requirements:
// 1. order.id from context (previous checkout)
// 2. offer.id from static (CRM configuration)
// In plugin:
const { context } = useFunnel();
const orderId = context?.order?.id; // 'ord_123' from session
const offerId = context?.static?.offer?.id; // 'offer_abc' from CRM
// Show specific offer configured in CRM for this order
Step 4: Thank You
// Orchestrator navigates to: /thankyou/ord_123
// Requirement: order.id as path parameter
// Orchestrator resolves from context and embeds in URL
// In plugin:
const { context } = useFunnel();
const order = context?.order; // Full order data available
const upsellOrder = context?.upsellOrder; // Also available!
// All previous resources are accessible
The orchestrator handles ALL parameter resolution, URL building, and resource propagation automatically!
Key Takeaways
- Query parameters (
type: "query") - User provides in URL
- Path parameters (
type: "path") - Orchestrator embeds in URL from context
- Context resources (
type: "context") - From previous steps, available in session
- Static resources (
type: "static") - From CRM configuration, not in URL
All resources from all previous steps remain available in context for the entire funnel session.
Validation & Best Practices
✅ Always Specify Resource Type
Be explicit about where data comes from:
{
"requirements": [
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
]
}
Clear type specification helps the orchestrator resolve data correctly.
❌ Don’t Mix Resource Types Incorrectly
// ❌ Bad: Using query for session data
{
"resource": "order",
"from": [
{
"name": "id",
"type": "query" // Wrong! order.id should be from context
}
]
}
// ✅ Good: Use context for session data
{
"resource": "order",
"from": [
{
"name": "id",
"type": "context"
}
]
}
✅ Use Static Resources for Configuration
Use static resources for values that should be configured in the CRM:
{
"path": "/special-offer",
"features": [
{
"type": "offer",
"requirements": [
{
"resource": "offer",
"from": [{ "name": "id", "type": "static" }]
},
{
"resource": "order",
"from": [{ "name": "id", "type": "context" }]
}
]
}
]
}
This allows different funnels to show different offers without code changes.
✅ Make Pages Remappable
Most pages should be remappable for flexibility:
{
"path": "/checkout",
"features": [{ "type": "checkout", "requirements": [] }],
"remappable": true
}
Only set remappable: false for wildcard routes or special functionality.
✅ Use Semantic Resource Names
// ✅ Good: Clear intent
resources: {
mainOrder: { id: 'ord_123' },
upsellOrder: { id: 'ord_456' },
customer: { email: '[email protected]' }
}
// ❌ Bad: Unclear
resources: {
order1: { id: 'ord_123' },
order2: { id: 'ord_456' },
data: { email: '[email protected]' }
}
✅ Test with resources.static.json
For local development, create /config/resources.static.json:
{
"offer": {
"id": "offer_test123"
},
"product": {
"id": "prod_test456"
}
}
This lets you test static resource pages without configuring them in the CRM.
Debugging
Debug Context and Resources
Log the full context to see what’s available:
import { useFunnel } from '@tagadapay/plugin-sdk/v2';
function MyPage() {
const { context } = useFunnel();
// Debug: See all available data
console.log('[MyPage] Full context:', context);
console.log('[MyPage] Order:', context?.order);
console.log('[MyPage] Static resources:', context?.static);
return <div>...</div>;
}
Debug Static Resources
Check if your static resources are loading correctly:
const { context } = useFunnel();
console.log('[Debug] Static offer ID:', context?.static?.offer?.id);
console.log('[Debug] Is localhost?', window.location.hostname === 'localhost');
Remember: resources.static.json only works in development (localhost, *.localhost, ngrok). In production,
static resources come from the CRM configuration.
Check Requirement Types
Verify requirements are resolving correctly:
// Query parameter (checkoutToken)
console.log('[Debug] Query checkoutToken:', new URLSearchParams(window.location.search).get('checkoutToken'));
// Path parameter (orderId)
console.log('[Debug] Path orderId:', window.location.pathname.split('/').pop());
// Context resource (order from previous step)
console.log('[Debug] Context order:', context?.order);
// Static resource (from CRM config)
console.log('[Debug] Static offer:', context?.static?.offer);
Backend Logs
Check backend logs for orchestrator decisions:
[Orchestrator] Resolving requirements for step: /thankyou/:orderId
[Orchestrator] Requirement: order.id from type:path
[Orchestrator] Found in context.order.id: ord_123
[Orchestrator] Built URL: /thankyou/ord_123
[Orchestrator] Resolving requirements for step: /offerfromstatic
[Orchestrator] Requirement: order.id from type:context -> ord_123
[Orchestrator] Requirement: offer.id from type:static -> offer_abc
[Orchestrator] Static resources loaded from CRM config
Summary
The manifest is your plugin’s contract with the platform:
Core Concepts
- Configuration System - Define customizable settings with schemas and presets
- Static Resources - Configure plugin-specific data in the CRM (accessible via
context.static)
- Define Pages - Specify routes, features, and whether they’re remappable
- Declare Requirements - Specify data needs using
type (query, path, context, static)
- Emit Resources - Produce data via
funnel.next() for subsequent steps
- Trust the Orchestrator - It handles parameter resolution, URL building, and resource propagation
Requirement Types Quick Reference
| Type | Source | In URL? | Access Via |
|---|
query | URL query params | ✅ Yes (query param) | Auto-handled by SDK |
path | URL path segments | ✅ Yes (path param) | context.{resource} |
context | Previous steps | 🔄 Optional (via session) | context.{resource} |
static | CRM configuration | ❌ No (server-side only) | context.static.{resource} |
Context Resources Explained:
- Context resources are stored in the funnel session on the server
- They’re available via
context.{resource} without needing to be in the URL
- The orchestrator may optionally include them in the URL for specific use cases (like when a path requires
:orderId)
Development Files
/config/schema.json - Configuration structure
/config/ui-schema.json - CRM editor UI hints
/config/*.config.json - Preset configurations
/config/resources.static.json - Local dev only - Mock static resources
A well-defined manifest = Seamless funnel orchestration with static resources ✨
Next Steps