Path Remapping Guide for React Plugins
Feature Overview Path remapping allows you to customize external URLs while maintaining your plugin’s internal routing logic. Perfect for SEO-optimized URLs, branded paths, and multi-market operations.
Framework React SDK v2 - Requires React with TanStack Query
React is Required : This guide covers path remapping for React-based TagadaPay plugins. All code examples use React hooks and patterns from the TagadaPay SDK v2.
What is Path Remapping?
Path remapping lets you expose custom, SEO-friendly URLs to your users while your plugin continues to use its original internal paths internally.
Example Scenario
Your plugin defines these internal paths:
/checkout
/checkout/step1
/checkout/step2
But you want customers to see:
/buy-now
/buy-now/shipping
/buy-now/payment
Path remapping makes this possible! ✨
SEO Benefits Use keyword-rich URLs like /buy-now instead of /checkout
Brand Consistency Match URLs to your brand language across markets
A/B Testing Test different URL structures without changing plugin code
Multi-Market Localized URLs: /acheter (FR) vs /kaufen (DE) vs /checkout (EN)
How It Works
You configure the mapping in CRM
Tell TagadaPay: “When users visit /custom/:id, route to plugin’s /hello-with-param/:myparam”
TagadaPay injects configuration
The platform automatically injects the mapping as a meta tag in your plugin’s HTML
SDK handles routing
The SDK transparently handles URL matching and parameter extraction
Your plugin works unchanged
Your code continues to use internal paths - no modifications needed!
Architecture
Configuration
In Plugin Manifest
Mark which pages support remapping:
{
"pluginId" : "my-checkout-plugin" ,
"pages" : [
{
"path" : "/" ,
"remappable" : false // Static landing page
},
{
"path" : "/checkout" ,
"remappable" : true // ✅ Can be remapped
},
{
"path" : "/checkout/:step" ,
"remappable" : true // ✅ Can be remapped
},
{
"path" : "/thankyou/:orderId" ,
"remappable" : true // ✅ Can be remapped
}
]
}
In CRM (Done by Merchant)
Merchants configure remapping when mounting your plugin:
// CRM UI allows configuring:
{
from : "/checkout" , // Your plugin's original path
to : "/buy-now" , // Custom external URL
matcher : "^/buy-now.*" // Route matcher pattern
}
SDK Usage
Basic Routing with shouldMatchRoute()
import { shouldMatchRoute } from '@tagadapay/plugin-sdk/v2' ;
import { Route , Routes } from 'react-router-dom' ;
function App () {
return (
< Routes >
< Route path = "/*" element = { < RemappableRoutes /> } />
</ Routes >
);
}
function RemappableRoutes () {
// SDK automatically handles remapping!
if ( shouldMatchRoute ( '/checkout' )) {
return < CheckoutPage /> ;
}
if ( shouldMatchRoute ( '/thankyou/:orderId' )) {
return < ThankYouPage /> ;
}
return < NotFound /> ;
}
What happens:
User visits: /buy-now (external URL)
SDK knows: “This is actually /checkout”
shouldMatchRoute('/checkout') returns true ✅
Your component renders correctly!
New in v2.7.14 : matchRoute() extracts URL parameters from remapped paths automatically!
import { matchRoute } from '@tagadapay/plugin-sdk/v2' ;
function RemappableRoutes () {
const location = useLocation ();
// Check and extract params in one call
const thankYouMatch = matchRoute ( '/thankyou/:orderId' );
if ( thankYouMatch . matched ) {
// ✅ Params are automatically extracted!
return < ThankYouPage orderId = { thankYouMatch . params . orderId } /> ;
}
const checkoutMatch = matchRoute ( '/checkout/:step' );
if ( checkoutMatch . matched ) {
return < CheckoutPage step = { checkoutMatch . params . step } /> ;
}
return < NotFound /> ;
}
Example:
External URL: /order-confirmation/ORD-12345
Internal path: /thankyou/:orderId
Result: { matched: true, params: { orderId: 'ORD-12345' } }
import { getPathInfo } from '@tagadapay/plugin-sdk/v2' ;
function PathDebugger () {
const pathInfo = getPathInfo ();
return (
< div >
< h2 > Path Information </ h2 >
< dl >
< dt > External URL (what user sees): </ dt >
< dd > { pathInfo . externalPath } </ dd >
< dt > Internal Path (plugin's perspective): </ dt >
< dd > { pathInfo . internalPath || 'No remapping' } </ dd >
< dt > Is Remapped: </ dt >
< dd > { pathInfo . isRemapped ? 'Yes' : 'No' } </ dd >
< dt > Query Parameters: </ dt >
< dd > { pathInfo . query . toString () } </ dd >
</ dl >
</ div >
);
}
Real-World Examples
Example 1: E-commerce Checkout
Plugin defines:
/checkout → Main checkout page
/checkout/shipping → Shipping step
/checkout/payment → Payment step
/checkout/confirm → Review step
Merchant wants:
/secure-checkout → Main checkout page
/secure-checkout/delivery → Shipping step
/secure-checkout/pay → Payment step
/secure-checkout/review → Review step
Your plugin code (unchanged!):
function CheckoutRoutes () {
const shippingMatch = matchRoute ( '/checkout/shipping' );
const paymentMatch = matchRoute ( '/checkout/payment' );
const confirmMatch = matchRoute ( '/checkout/confirm' );
if ( shippingMatch . matched ) return < ShippingPage /> ;
if ( paymentMatch . matched ) return < PaymentPage /> ;
if ( confirmMatch . matched ) return < ConfirmPage /> ;
return < CheckoutLandingPage /> ;
}
Result: Works perfectly with both URL structures! ✅
Example 2: Thank You Page with Order ID
Plugin expects:
Merchant wants:
Your plugin code:
function ThankYouRoutes () {
const match = matchRoute ( '/thankyou/:orderId' );
if ( match . matched ) {
// ✅ Works with both /thankyou/123 and /order-complete/123
return < ThankYouPage orderId = { match . params . orderId } /> ;
}
return < NotFound /> ;
}
Example 3: Multi-Market Localization
Plugin internal paths:
/checkout → Universal checkout logic
Different markets:
🇺🇸 English: /checkout
🇫🇷 French: /paiement
🇩🇪 German: /kasse
🇪🇸 Spanish: /comprar
Your plugin:
// Same code works for all markets!
function App () {
if ( shouldMatchRoute ( '/checkout' )) {
return < CheckoutPage /> ;
}
}
Each market configures their preferred external URL in the CRM. Your plugin works everywhere! 🌍
Example 4: Product Pages with Slugs
Plugin:
SEO-Friendly URLs:
/p/amazing-wireless-headphones
/p/ultra-comfy-sneakers
/p/premium-coffee-beans
Implementation:
function ProductRoutes () {
const match = matchRoute ( '/product/:productId' );
if ( match . matched ) {
// ✅ productId = "amazing-wireless-headphones"
return < ProductPage slug = { match . params . productId } /> ;
}
return < ProductGrid /> ;
}
Parameter Name Rules
Important: When remapping paths with parameters, parameter names must match between external and internal paths.
✅ Valid Remapping
{
"from" : "/hello-with-param/:myparam" ,
"to" : "/custom/:myparam"
}
Both use :myparam → Works! ✅
❌ Invalid Remapping
{
"from" : "/hello-with-param/:myparam" ,
"to" : "/custom/:id"
}
Parameter names don’t match → CRM will reject this ❌
Why? This ensures reliable parameter extraction without complex name mapping logic.
Development & Testing
Local Development
Test path remapping in your local environment using localStorage:
// Open browser console and run:
localStorage . setItem ( 'tagadapay-remap' , JSON . stringify ({
externalPath: '/custom/:myparam' ,
internalPath: '/hello-with-param/:myparam'
}));
// Then visit: http://localhost:5173/custom/test123
// Your plugin receives it as: /hello-with-param/test123
Clear remapping:
localStorage . removeItem ( 'tagadapay-remap' );
Query Parameter Method
Alternative testing method:
http://localhost:5173/hello-with-param/123?__remap=/my-custom-path/123
Debugging
Add a debug component to see what’s happening:
import { getPathInfo , matchRoute } from '@tagadapay/plugin-sdk/v2' ;
function PathRemapDebugger () {
const pathInfo = getPathInfo ();
const match = matchRoute ( '/your-internal-path/:param' );
return (
< div className = "debug-panel" >
< h3 > 🔍 Path Remap Debug Info </ h3 >
< div >
< strong > Current URL: </ strong > { window . location . pathname }
</ div >
< div >
< strong > Is Remapped: </ strong > { pathInfo . isRemapped ? '✅ Yes' : '❌ No' }
</ div >
{ pathInfo . isRemapped && (
<>
< div >
< strong > External Path: </ strong > { pathInfo . externalPath }
</ div >
< div >
< strong > Internal Path: </ strong > { pathInfo . internalPath }
</ div >
</>
) }
< div >
< strong > Route Match: </ strong > { match . matched ? '✅ Matched' : '❌ No match' }
</ div >
{ match . matched && match . params && (
< div >
< strong > Extracted Params: </ strong >
< pre > { JSON . stringify ( match . params , null , 2 ) } </ pre >
</ div >
) }
</ div >
);
}
Production Behavior
In production, TagadaPay automatically injects remapping configuration:
< head >
< meta name = "tagadapay-path-remap"
content = "%7B%22external%22%3A%22%2Fcustom%2F%3Amyparam%22%2C%22internal%22%3A%22%2Fhello-with-param%2F%3Amyparam%22%7D" >
</ head >
Decoded:
{
"external" : "/custom/:myparam" ,
"internal" : "/hello-with-param/:myparam"
}
The SDK reads this automatically - no action needed from you!
CDN Distribution
Remapped plugins are served from CDN subdomains:
Original: https://store-123.cdn.tagadapay.com/checkout
Remapped: https://store-123.cdn.tagadapay.com/buy-now
(Both serve the same plugin, SDK handles the routing)
Best Practices
1. Always Use SDK Functions
Don’t parse window.location manually! Always use SDK functions for path matching.
❌ Don’t do this:
// This won't work with remapping!
function CheckoutPage () {
const isCheckout = window . location . pathname === '/checkout' ;
// ...
}
✅ Do this:
// This works with remapping!
function CheckoutPage () {
const isCheckout = shouldMatchRoute ( '/checkout' );
// ...
}
2. Mark Pages as Remappable
In your plugin.manifest.json:
{
"pages" : [
{
"path" : "/admin-panel" ,
"remappable" : false // Internal tool, no need for remapping
},
{
"path" : "/checkout" ,
"remappable" : true // Customer-facing, allow remapping
}
]
}
3. Keep Internal Paths Descriptive
Even if merchants remap them, keep your internal paths clear:
Good:
/checkout
/checkout/shipping
/product/:productId
/thankyou/:orderId
Avoid:
4. Handle Both Scenarios
Your plugin should work whether remapping is active or not:
function App () {
// Works with:
// - /checkout (no remapping)
// - /buy-now (remapped to /checkout)
// - /secure-payment (remapped to /checkout)
if ( shouldMatchRoute ( '/checkout' )) {
return < CheckoutPage /> ;
}
return < HomePage /> ;
}
5. Test Both Paths
During development, test your plugin with:
Original paths : http://localhost:5173/checkout
Remapped paths : Use localStorage to simulate remapping
API Reference
shouldMatchRoute(internalPath: string): boolean
Check if the current URL matches the given internal path.
Parameters:
internalPath: Your plugin’s internal path pattern (e.g., /checkout, /product/:id)
Returns:
boolean: true if current URL matches (considering remapping), false otherwise
Example:
if ( shouldMatchRoute ( '/checkout/:step' )) {
return < CheckoutPage /> ;
}
matchRoute(internalPath: string): RouteMatchResult
Check if route matches AND extract URL parameters.
Parameters:
internalPath: Your plugin’s internal path pattern
Returns:
{
matched : boolean ;
params : Record < string , string > ;
}
Example:
const result = matchRoute ( '/product/:productId' );
// result.matched = true
// result.params = { productId: 'wireless-headphones' }
if ( result . matched ) {
return < ProductPage productId = { result . params . productId } /> ;
}
getPathInfo(): PathInfo
Get detailed information about the current path and remapping status.
Returns:
{
externalPath : string ; // URL user sees
internalPath : string | null ; // Your plugin's path (if remapped)
isRemapped : boolean ; // Whether remapping is active
query : URLSearchParams ; // Query parameters
hash : string ; // URL hash
}
Example:
const info = getPathInfo ();
if ( info . isRemapped ) {
console . log ( `User sees: ${ info . externalPath } ` );
console . log ( `Plugin receives: ${ info . internalPath } ` );
}
getInternalPath(): string | null
Get the current internal path (or null if no remapping).
Returns:
string: Internal path if remapping is active
null: If no remapping is active
Example:
const internalPath = getInternalPath ();
if ( internalPath ) {
console . log ( 'Remapping active! Internal path:' , internalPath );
} else {
console . log ( 'No remapping - using original paths' );
}
Troubleshooting
Parameters are empty
Problem: matchRoute() returns { matched: true, params: {} }
Solution: Ensure parameter names match in your manifest and CRM configuration:
// ✅ Correct
{
"from" : "/product/:productId" ,
"to" : "/p/:productId"
}
// ❌ Wrong
{
"from" : "/product/:productId" ,
"to" : "/p/:id" // Different parameter name!
}
Route not matching
Problem: shouldMatchRoute() returns false when it should match
Checklist:
Is the page marked as remappable: true in your manifest?
Is the internal path correct (check with getPathInfo())?
Are you using the SDK function (not window.location)?
Try adding logging: console.log(getPathInfo())
Works locally but not in production
Problem: Remapping works in development but fails on CDN
Solution:
Check that meta tag is injected: View page source and search for tagadapay-path-remap
Rebuild your plugin: pnpm build
Redeploy: npx @tagadapay/cli deploy
Clear browser cache and CDN cache
Remapping not working locally
Problem: localStorage remapping doesn’t work
Solution:
// 1. Clear any existing config
localStorage . clear ();
// 2. Set new config (ensure it's valid JSON)
localStorage . setItem ( 'tagadapay-remap' , JSON . stringify ({
externalPath: '/your-external/:param' ,
internalPath: '/your-internal/:param'
}));
// 3. Reload the page (hard refresh: Cmd+Shift+R or Ctrl+Shift+R)
Migration Guide
From Manual Routing
Before (manual routing):
function App () {
const location = useLocation ();
// ❌ Doesn't work with remapping
if ( location . pathname === '/checkout' ) {
return < CheckoutPage /> ;
}
}
After (SDK routing):
import { shouldMatchRoute } from '@tagadapay/plugin-sdk/v2' ;
function App () {
// ✅ Works with remapping!
if ( shouldMatchRoute ( '/checkout' )) {
return < CheckoutPage /> ;
}
}
From React Router Params
Before:
import { useParams } from 'react-router-dom' ;
function ProductPage () {
const { productId } = useParams (); // ❌ Might be empty with remapping
return < Product id = { productId } /> ;
}
After:
import { matchRoute } from '@tagadapay/plugin-sdk/v2' ;
function ProductRoutes () {
const match = matchRoute ( '/product/:productId' );
if ( match . matched ) {
// ✅ Always works, even with remapping
return < ProductPage id = { match . params . productId } /> ;
}
return < NotFound /> ;
}
Advanced Patterns
Nested Routes with Remapping
function App () {
const checkoutMatch = matchRoute ( '/checkout/:step' );
if ( checkoutMatch . matched ) {
return < CheckoutFlow step = { checkoutMatch . params . step } /> ;
}
return < HomePage /> ;
}
function CheckoutFlow ({ step } : { step : string }) {
switch ( step ) {
case 'shipping' :
return < ShippingStep /> ;
case 'payment' :
return < PaymentStep /> ;
case 'review' :
return < ReviewStep /> ;
default :
return < CheckoutLanding /> ;
}
}
Conditional Remapping Display
function PathBreadcrumbs () {
const pathInfo = getPathInfo ();
if ( pathInfo . isRemapped ) {
return (
< nav >
< span > You are here: { pathInfo . externalPath } </ span >
< small > (Internal: { pathInfo . internalPath } ) </ small >
</ nav >
);
}
return < nav > { window . location . pathname } </ nav > ;
}
import { Helmet } from 'react-helmet' ;
function CheckoutPage () {
const pathInfo = getPathInfo ();
// Use external path for canonical URL (what user/Google sees)
const canonicalUrl = `https://example.com ${ pathInfo . externalPath } ` ;
return (
<>
< Helmet >
< link rel = "canonical" href = { canonicalUrl } />
< meta property = "og:url" content = { canonicalUrl } />
</ Helmet >
< div > Checkout content... </ div >
</>
);
}
Next Steps
Need Help?