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 →