blog.thms.uk

Read and write URLs in JavaScript

Recently I had create a form where users could supply a URL and then append utm tags for tracking in their Analytics package. I also wanted to show the final, tagged URL to the user.

Here is what the form looked like:

<form id="form">
    <label for="url">URL</label>
    <input type="url" id="url" name="url">
  
    <label for="utm_source">UTM Source</label>
    <input type="text" id="utm_source" name="utm_source">
  
    <label for="utm_medium">UTM Medium</label>
    <input type="text" id="utm_medium" name="utm_medium">
</form>
<p>Your final URL will be <span id="preview"></span></p>

You might be tempted to just concatenate the final url like so:

const url = document.getElementById('url').value;
const utmSource = document.getElementById('utm_source').value;
const utmMedium = document.getElementById('utm_medium').value;
document.getElementByid('preview').innerHtml = `${url}?utm_source=${utmSource}&utm_medium=${utmMedium}`;

Simples!

Not so fast: what if the url provided already contains query parameters? You'd have two ? in that URL. Sure, that's easy enough to solve with .includes('?').

But, what if the input string already contains a utm_source or utm_medium parameter? You'd need some regular expression to detect and handle this and prevent duplication.

Oh, and don't forget to url encode your query parameters.

Thankfully, there is a better way to do this in 2023: The URL API has been around for a while, and according to caniuse.com support is good.

So, here is a much better way of dealing with this:

const url = new URL(document.getElementById('url').value);
url.searchParams.set('utm_source', document.getElementById('utm_source').value);
url.searchParams.set('utm_medium', document.getElementById('utm_medium').value);  
document.getElementById('preview').innerHTML = url.toString();

Any existing query parameters in the provided URL will simply be retained.

Your query parameters will be encoded automatically too.

If the URL contains a utm_source or utm_medium parameter, it'll be overridden to prevent duplication, which is what I wanted here.

If you wanted to keep the parameters from the input url instead, you could just check like this:

if(!url.searchParams.has('utm_source')) {
    url.searchParams.set('utm_source', document.getElementById('utm_source').value);
}

Or, if you wanted to allow duplicates you could append values:

url.searchParams.append('utm_source', document.getElementById('utm_source').value);

This is far neater and less error prone than our first attempt.

Gotchas

One thing to look out for, is that new URL(value) will throw an error, if value isn't a valid URL, including for relative URLs. URLs without a protocol (such as www.example.com/foo/bar) will be considered invalid URLs, so you'll definitely want some error handling here. (You can provide a base URL as second parameter, but that's not useful for my use case.)

url.searchParams is of type URLSearchParams which has a few more useful methods.