Skip to content

UTM Link Builder — Python source

Build campaign tracking URLs with utm_source, utm_medium and utm_campaign parameters. Bulk mode processes a whole list, presets and import round-trip existing tracking URLs, and a validator flags attribution-breaking values — runs entirely in your browser.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""
utm-link-builder - campaign URL builder with canonical utm_* ordering.

Display snippet: ports the core build(baseUrl, params) from the TypeScript
lib (src/lib/utm.ts). The lint/bulk/preset helpers live in the
TypeScript/Go sources.

Semantics: strip any stale utm_* params from the base URL, keep every
unrelated query param in place, then append the given params in canonical
order. Empty-string params are omitted. Returns None for an unparseable
base. Standard library only (urllib.parse).
"""

from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

CANONICAL = [
    ("source", "utm_source"),
    ("medium", "utm_medium"),
    ("campaign", "utm_campaign"),
    ("term", "utm_term"),
    ("content", "utm_content"),
]


def build(base_url, params=None):
    params = params or {}
    if "://" not in base_url:
        base_url = "https://" + base_url  # coerce a scheme-less host
    try:
        parts = urlsplit(base_url)
    except ValueError:
        return None
    if not parts.netloc:
        return None

    # Keep every unrelated query param; drop stale utm_* ones.
    kept = [
        (key, value)
        for key, value in parse_qsl(parts.query, keep_blank_values=True)
        if not key.startswith("utm_")
    ]

    for field, query_key in CANONICAL:
        value = params.get(field)
        if value:  # None and '' are omitted
            kept.append((query_key, value))

    return urlunsplit(
        (parts.scheme, parts.netloc, parts.path, urlencode(kept), parts.fragment)
    )


# Example:
#   build("example.com/page?utm_source=stale&id=7", {"source": "twitter", "medium": "social"})
#   -> 'https://example.com/page?id=7&utm_source=twitter&utm_medium=social'

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →