Fermin Perdomo

Senior Full Stack Engineer | PHP | JavaScript

How to Safely Build Dynamic Tracking Links with Query Parameters in JavaScript

Fermin Perdomo
July 31, 2025

When working with affiliate platforms like Everflow, it's common to dynamically append tracking parameters (e.g., sub1, sub2) to a base tracking link.

At first glance, this seems straightforward: simply concatenate the parameters with an ampersand (&). But if you’re not careful, you could end up with broken links, double question marks (??), or missing values. In this post, we’ll explore a safe, modern way to build URLs with query parameters using JavaScript’s URL and URLSearchParams APIs.

The Common Pitfall: Manual String Concatenation

A simple implementation might look like this:


const params = Object.entries(everflow) .filter(([key, value]) => key.startsWith('sub') && value) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join('&'); const everflowTrackingLinkFinalWithSubs = params ? `${everflowTrackingLink.trackingLink}?${params}` : everflowTrackingLink.trackingLink; console.log(everflowTrackingLinkFinalWithSubs);

This works most of the time, but it has a few problems:

1. Double ? issue:
If the base URL already has query parameters, you end up with something like:

https://example.com?campaign=123?sub1=value

That second ? is invalid.

2. Messy encoding:
Manually adding & and = can lead to improperly encoded characters if you forget encodeURIComponent().

3. Loses readability:
Adding sorting or filtering logic quickly becomes hard to maintain.


The Better Way: Using the URL API

JavaScript’s URL class makes building query strings much safer and cleaner:


const url = new URL(everflowTrackingLink.trackingLink); Object.entries(everflow) .filter(([key, value]) => key.startsWith('sub') && value != null && value !== '') .forEach(([key, value]) => { url.searchParams.set(key, String(value)); }); const everflowTrackingLinkFinalWithSubs = url.toString(); console.log('Final Everflow URL:', everflowTrackingLinkFinalWithSubs);

Why is this better?

  • Automatically merges existing query parameters (no double ? issues).
  • Handles URL encoding for you.
  • Easier to maintain and extend.

Adding Fetch with Timeout and Error Handling

If you need to call the tracking link (e.g., for redirection or server-side logging), wrap it in a function with proper error handling:


async function fetchEverflowTrackingLink(everflowTrackingLink, everflow) { const url = new URL(everflowTrackingLink.trackingLink); Object.entries(everflow) .filter(([key, value]) => key.startsWith('sub') && value != null && value !== '') .forEach(([key, value]) => url.searchParams.set(key, String(value))); console.log('Everflow Tracking URL:', url.toString()); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); try { const response = await fetch(url.toString(), { signal: controller.signal }); console.log('Everflow Response:', response.status); if (!response.ok) { console.error('Everflow returned an error:', response.status, response.statusText); } } catch (error) { console.error('Fetch failed:', error); } finally { clearTimeout(timeout); } }

Key improvements:

  • Timeout: Prevents the request from hanging forever using AbortController.
  • Response check: Logs non-200 responses so you can debug.
  • Resiliency: Catches any errors without crashing the app.

Sorting or Whitelisting Parameters (Optional)

If you care about parameter order (sub1, sub2, sub3), you can add a sort step:


Object.entries(everflow) .filter(([key, value]) => key.startsWith('sub') && value) .sort(([a], [b]) => { const numA = parseInt(a.replace('sub', ''), 10); const numB = parseInt(b.replace('sub', ''), 10); return numA - numB; }) .forEach(([key, value]) => url.searchParams.set(key, String(value)));

You could also whitelist valid keys (e.g., only sub1–sub10) to avoid accidentally leaking extra parameters.

Why This Matters

  • Stability: Broken URLs can cost revenue in affiliate tracking.
  • Security: Proper encoding prevents injection attacks.
  • Maintainability: The URL API is self-documenting and easy to modify.

If you’re still concatenating query strings manually, switching to this approach will save you hours of debugging later.

Final Thoughts

This small improvement makes your code cleaner, safer, and easier to debug. The URL API is supported in modern browsers and Node.js, so there’s no reason not to use it.

Pro tip: If you’re building server-side integrations, you can also use redirect: 'manual' in fetch() to capture the redirect location Everflow returns, which is often critical for analytics.

What about you?

Do you have other tricks for building tracking URLs? Drop them in the comments below!


Reactions

Loading reactions...
Log in to react to this post.

Comments

Please login to leave a comment.

Newsletter