ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
Development
    CORSEnvironment VariablesBranch-Based DeploymentsTestingTroubleshootingGitOps vs TerraformCustom Code
    Local Development
    Guides
      Advanced Path MatchingAPI VersioningOpenAPI Server URLsConvert URLs to OpenAPIOpenAPI Extension DataFormat Validation WarningsPath Modification ScriptsOpenAPI OverlaysCanary Routing for EmployeesGeolocation Backend RoutingUser-Based Backend RoutingTransform Route ParametersProxying Between GatewaysBypass a PolicyTesting GraphQL QueriesHealth ChecksPerformance TestingTroubleshooting Slow ResponsesNon-Standard PortsHandling FormDataS3 Signed URL UploadsCheck IP AddressLazy Load ConfigurationSharing Code Across ProjectsBackstage IntegrationGitHub Action Automation
Policies
Handlers
API Keys
Rate Limiting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Guides

Transform Route Parameters for URL Rewrite

This guide explains how to transform incoming route parameter values in an inbound policy before the URL Rewrite handler uses them to build the upstream URL. This pattern is useful when your public API paths use different naming conventions than your internal backend.

Overview

When you use the URL Rewrite handler, it builds the upstream URL by interpolating values like ${params.resourceType} directly from the incoming route parameters. Sometimes, however, you need to change those values before the rewrite happens. Common scenarios include:

  • Value mapping — translating a public-facing parameter like order to an internal value like customerorder
  • Case normalization — converting Products to products before forwarding
  • Path translation — mapping user-friendly slugs to internal identifiers

The recommended approach is to read the route parameters in an inbound policy, transform them, store the results on context.custom, and reference the transformed values in the URL Rewrite pattern.

Step-by-Step Example

The solution has three parts: an inbound policy that reads request.params and stores transformed values on context.custom, a URL Rewrite handler that references those values using ${context.custom.*} in the rewritePattern, and route configuration that wires the two together.

Imagine your public API exposes a route like /api/:resourceType/:resourceId, but your backend expects the resource type to be prefixed with customer. A request to /api/order/123 should be forwarded to https://backend.example.com/api/customerorder/123.

1. Write the Inbound Policy

Create a custom inbound policy that reads the route parameters, transforms the values, and stores them on context.custom:

modules/transform-params.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export default async function ( request: ZuploRequest, context: ZuploContext, options: any, policyName: string, ): Promise<ZuploRequest | Response> { // Read the original route parameter const resourceType = request.params.resourceType; // Transform the value — prefix with "customer" const transformedResourceType = `customer${resourceType}`; // Store the transformed value on context.custom context.custom.transformedResourceType = transformedResourceType; context.log.info({ message: "Transformed route parameter", original: resourceType, transformed: transformedResourceType, }); return request; }

2. Register the Policy

Add the policy to config/policies.json:

config/policies.json
{ "policies": [ { "name": "transform-params", "policyType": "custom-code-inbound", "handler": { "export": "default", "module": "$import(./modules/transform-params)" } } ] }

3. Configure the Route

Define the route in config/routes.oas.json with the inbound policy and a URL Rewrite handler that references context.custom:

config/routes.oas.json
{ "paths": { "/api/{resourceType}/{resourceId}": { "x-zuplo-path": { "pathMode": "open-api" }, "get": { "summary": "Get resource by type and ID", "x-zuplo-route": { "corsPolicy": "none", "handler": { "export": "urlRewriteHandler", "module": "$import(@zuplo/runtime)", "options": { "rewritePattern": "https://backend.example.com/api/${context.custom.transformedResourceType}/${params.resourceId}" } }, "policies": { "inbound": ["transform-params"] } } } } } }

With this configuration, a request to /api/order/123 flows through the pipeline as follows:

  1. The route matches with params.resourceType = "order" and params.resourceId = "123"
  2. The transform-params inbound policy runs and sets context.custom.transformedResourceType = "customerorder"
  3. The URL Rewrite handler builds the upstream URL: https://backend.example.com/api/customerorder/123

Common Pitfall: Modifying request.params Directly

Do not try to transform route parameters by constructing a new ZuploRequest with modified params and expecting the URL Rewrite handler to pick them up.

A common first attempt is to create a new ZuploRequest with different params:

Code
// ⚠️ This approach does NOT work as expected with URL Rewrite const newRequest = new ZuploRequest(request, { params: { ...request.params, resourceType: "customerorder", }, }); return newRequest;

In practice, the URL Rewrite handler evaluates ${params.*} against the route-level parameters rather than the request object returned by a policy. This means the rewritten URL may contain undefined segments instead of your transformed values. Use context.custom for reliable interpolation of transformed values — the URL Rewrite handler's rewritePattern fully supports ${context.custom.*}, and values set in an inbound policy are available when the handler runs.

Variations

Using a Lookup Map

For more complex mappings where the transformation is not a simple string operation, use a lookup object:

modules/transform-params-map.ts
import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime"; // Map public resource types to internal names const RESOURCE_TYPE_MAP: Record<string, string> = { order: "customerorder", invoice: "billing-invoice", profile: "user-profile", subscription: "recurring-plan", }; export default async function ( request: ZuploRequest, context: ZuploContext, options: any, policyName: string, ): Promise<ZuploRequest | Response> { const resourceType = request.params.resourceType; const mappedType = RESOURCE_TYPE_MAP[resourceType]; if (!mappedType) { return HttpProblems.notFound(request, context, { detail: `Unknown resource type: ${resourceType}`, }); } context.custom.transformedResourceType = mappedType; return request; }

Transforming Multiple Parameters

You can transform any number of route parameters and store each on context.custom. Reference them individually in the rewrite pattern:

modules/transform-multiple-params.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export default async function ( request: ZuploRequest, context: ZuploContext, options: any, policyName: string, ): Promise<ZuploRequest | Response> { // Normalize casing context.custom.version = request.params.version?.toLowerCase(); // Map resource type context.custom.resource = request.params.resource === "users" ? "customers" : request.params.resource; return request; }

Then use both values in the rewrite pattern:

Code
{ "rewritePattern": "https://backend.example.com/${context.custom.version}/${context.custom.resource}/${params.id}" }

Combining with Body Transformation

If your API also needs to transform values in the request body alongside route parameters, you can handle both in the same inbound policy. Create a new ZuploRequest with a modified body while storing the route parameter transformations on context.custom:

modules/transform-params-and-body.ts
import { ZuploContext, ZuploRequest } from "@zuplo/runtime"; export default async function ( request: ZuploRequest, context: ZuploContext, options: any, policyName: string, ): Promise<ZuploRequest | Response> { // Transform route parameter context.custom.transformedResourceType = `customer${request.params.resourceType}`; // Transform the request body if present if (request.headers.get("content-type")?.includes("application/json")) { const body = await request.json(); // Map fields in the body to match the backend schema const transformedBody = { ...body, type: context.custom.transformedResourceType, }; // Return a new request with the modified body return new ZuploRequest(request, { body: JSON.stringify(transformedBody), }); } return request; }

Best Practices

  • Use descriptive keys on context.custom — names like context.custom.transformedResourceType are easier to debug than generic keys like context.custom.value
  • Log transformations — use context.log to record original and transformed values so you can trace issues in production
  • Validate before transforming — return an appropriate error response (using HttpProblems) if a parameter value is unexpected, rather than forwarding bad data to your backend
  • Keep the policy focused — if your transformation logic is complex, consider splitting it into a separate utility module imported by the policy

Next Steps

  • URL Rewrite Handler — full reference for rewrite patterns and available interpolation variables
  • Custom Code Patterns — common patterns for writing inbound policies, outbound policies, and handlers
  • ZuploContext — reference for context.custom and other context properties
  • ZuploRequest — reference for request.params and constructing new requests
  • User-Based Backend Routing — a related pattern using context.custom with URL Rewrite for routing by user identity
Edit this page
Last modified on June 22, 2026
User-Based Backend RoutingProxying Between Gateways
On this page
  • Overview
  • Step-by-Step Example
    • 1. Write the Inbound Policy
    • 2. Register the Policy
    • 3. Configure the Route
  • Common Pitfall: Modifying request.params Directly
  • Variations
    • Using a Lookup Map
    • Transforming Multiple Parameters
    • Combining with Body Transformation
  • Best Practices
  • Next Steps
TypeScript
JSON
JSON
TypeScript
TypeScript
TypeScript
JSON
TypeScript