Skip to main content

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

1

You configure the mapping in CRM

Tell TagadaPay: “When users visit /custom/:id, route to plugin’s /hello-with-param/:myparam
2

TagadaPay injects configuration

The platform automatically injects the mapping as a meta tag in your plugin’s HTML
3

SDK handles routing

The SDK transparently handles URL matching and parameter extraction
4

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!

Parameter Extraction with matchRoute()

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' } }

Getting Path Information with getPathInfo()

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:
/thankyou/:orderId
Merchant wants:
/order-complete/:orderId
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:
/product/:productId
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 :myparamWorks!

❌ 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

Automatic Meta Tag Injection

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:
/c
/s1
/p/:id
/ty/:oid

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:
  1. Original paths: http://localhost:5173/checkout
  2. 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:
  1. Is the page marked as remappable: true in your manifest?
  2. Is the internal path correct (check with getPathInfo())?
  3. Are you using the SDK function (not window.location)?
  4. Try adding logging: console.log(getPathInfo())

Works locally but not in production

Problem: Remapping works in development but fails on CDN Solution:
  1. Check that meta tag is injected: View page source and search for tagadapay-path-remap
  2. Rebuild your plugin: pnpm build
  3. Redeploy: npx @tagadapay/cli deploy
  4. 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>;
}

SEO Meta Tags with Remapping

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?