Skip to content

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

/* utm-link-builder — campaign URL builder with canonical utm_* ordering. Language: C (C11, stdlib only). Port of src/lib/utm.ts build(); lint/bulk/preset helpers live in the TypeScript/Go sources. No URL parser in the stdlib: query pairs pass through raw (values must be pre-encoded). */

#include <stdio.h>
#include <string.h>

typedef struct { const char *field, *value; } utm_param;

static const char *const CANON[5][2] = { /* field -> canonical query key */
    {"source", "utm_source"}, {"medium", "utm_medium"}, {"campaign", "utm_campaign"},
    {"term", "utm_term"}, {"content", "utm_content"},
};

static const char *lookup(const utm_param *params, size_t n, const char *field) {
    for (size_t i = 0; i < n; i++)
        if (strcmp(params[i].field, field) == 0) return params[i].value;
    return NULL;
}

static char *pushn(char *cur, char *end, const char *s, size_t n) {
    if ((size_t)(end - cur) < n) n = (size_t)(end - cur); /* clamp to buffer */
    memcpy(cur, s, n);
    return cur + n;
}

char *utm_build(const char *base, const utm_param *params, size_t n, char *out, size_t outsz) {
    char *cur = out, *end = out + outsz - 1;
    const char *scheme = strstr(base, "://"), *rest;

    if (scheme) { cur = pushn(cur, end, base, (size_t)(scheme - base) + 3); rest = scheme + 3; }
    else        { cur = pushn(cur, end, "https://", 8); rest = base; } /* coerce scheme-less */

    size_t auth_len = strcspn(rest, "/?#"); /* authority ends at '/', '?' or '#' */
    if (auth_len == 0) return NULL;         /* empty host: unparseable */
    cur = pushn(cur, end, rest, auth_len);

    const char *path = rest + auth_len;
    cur = pushn(cur, end, path, strcspn(path, "?#"));
    const char *query = path + strcspn(path, "?#");

    int wrote = 0;
    if (*query == '?') { /* keep unrelated pairs raw; drop stale utm_* ones */
        const char *q = query + 1, *frag = strchr(q, '#');
        const char *qend = frag ? frag : q + strlen(q);
        const char *pair = q;
        while (pair < qend) {
            const char *amp = memchr(pair, '&', (size_t)(qend - pair));
            const char *next = amp ? amp : qend;
            size_t plen = (size_t)(next - pair);
            const char *eq = memchr(pair, '=', plen);
            size_t key_len = eq ? (size_t)(eq - pair) : plen;
            if (key_len > 0 && !(key_len >= 4 && strncmp(pair, "utm_", 4) == 0)) {
                cur = pushn(cur, end, wrote ? "&" : "?", 1); wrote = 1;
                cur = pushn(cur, end, pair, plen);
                wrote = 1;
            }
            pair = next + 1;
        }
    }
    for (size_t c = 0; c < 5; c++) { /* append in canonical order; empty values skipped */
        const char *value = lookup(params, n, CANON[c][0]);
        if (!value || !*value) continue;
        cur = pushn(cur, end, wrote ? "&" : "?", 1); wrote = 1;
        cur = pushn(cur, end, CANON[c][1], strlen(CANON[c][1]));
        cur = pushn(cur, end, "=", 1);
        cur = pushn(cur, end, value, strlen(value));
        wrote = 1;
    }
    const char *frag = strchr(query, '#');
    if (frag) cur = pushn(cur, end, frag, strlen(frag)); /* "#..." verbatim */
    *cur = '\0';
    return out;
}

int main(void) {
    utm_param params[] = {{"source", "twitter"}, {"medium", "social"}, {"campaign", ""}};
    char out[512];
    printf("%s\n", utm_build("example.com/page?utm_source=stale&id=7", params, 3, out, sizeof out));
    /* -> https://example.com/page?id=7&utm_source=twitter&utm_medium=social */
    return 0;
}

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 →