Skip to content

UTM Link Builder — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.

<?php
/**
 * 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 null for an
 * unparseable base. Standard library only.
 */

const CANONICAL = [
    'source'   => 'utm_source',
    'medium'   => 'utm_medium',
    'campaign' => 'utm_campaign',
    'term'     => 'utm_term',
    'content'  => 'utm_content',
];

function build(string $baseUrl, array $params = []): ?string
{
    if (strpos($baseUrl, '://') === false) {
        $baseUrl = 'https://' . $baseUrl; // coerce a scheme-less host
    }
    $parts = parse_url($baseUrl);
    if ($parts === false || !isset($parts['host'])) {
        return null;
    }

    // Keep every unrelated query param; drop stale utm_* ones.
    $kept = [];
    foreach (explode('&', $parts['query'] ?? '') as $pair) {
        if ($pair === '') {
            continue;
        }
        $key = explode('=', $pair, 2)[0];
        if (strpos($key, 'utm_') !== 0) {
            $kept[] = $pair;
        }
    }

    foreach (CANONICAL as $field => $queryKey) {
        $value = $params[$field] ?? '';
        if ($value !== '') {
            $kept[] = $queryKey . '=' . rawurlencode($value);
        }
    }

    $out = $parts['scheme'] . '://' . $parts['host'] . ($parts['path'] ?? '');
    if ($kept !== []) {
        $out .= '?' . implode('&', $kept);
    }
    if (isset($parts['fragment'])) {
        $out .= '#' . $parts['fragment'];
    }
    return $out;
}

// 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 →