Skip to content

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

// utm-link-builder — campaign URL builder with canonical utm_* ordering. Kotlin (1.9+, JRE only). Port of src/lib/utm.ts build(); lint/bulk/preset helpers live in the TypeScript/Go sources. Kept query pairs pass through verbatim; new utm values are form-encoded.

import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

private val CANONICAL = listOf(
    "source" to "utm_source", "medium" to "utm_medium", "campaign" to "utm_campaign",
    "term" to "utm_term", "content" to "utm_content",
)

fun build(baseUrl: String, params: Map<String, String>): String? {
    val text = if ("://" in baseUrl) baseUrl else "https://$baseUrl" // coerce scheme-less
    val uri = runCatching { URI.create(text) }.getOrNull() ?: return null
    val host = uri.host ?: return null // unparseable / no host

    val query = buildString {
        uri.rawQuery?.split('&')?.forEach { pair -> // keep unrelated, drop stale utm_*
            val key = pair.substringBefore('=')
            if (key.isNotEmpty() && !key.startsWith("utm_")) {
                if (isNotEmpty()) append('&')
                append(pair)
            }
        }
        CANONICAL.forEach { (field, queryKey) ->
            val value = params[field]
            if (!value.isNullOrEmpty()) {
                if (isNotEmpty()) append('&')
                append(queryKey).append('=')
                    .append(URLEncoder.encode(value, StandardCharsets.UTF_8))
            }
        }
    }

    val authority = if (uri.port != -1) "$host:${uri.port}" else host
    return buildString {
        append(uri.scheme).append("://").append(authority)
        append(uri.rawPath ?: "")
        if (query.isNotEmpty()) append('?').append(query)
        uri.rawFragment?.let { append('#').append(it) }
    }
}

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 →