encoding/url
Taking a web address apart, building and editing its parameters, and resolving relative addresses.
import encoding.url
This package works with whole web addresses. Parse splits a URL such as https://user@shop.example.co.uk:8443/path?page=2#top into a URL struct with the fields scheme, userinfo, host, port, path, raw_query and fragment, and String puts it back together. Around that come helpers to edit query parameters, resolve relative links with Join, and find the registrable domain of a host.
Use it whenever you have a full address. For the a=1&b=2 part on its own, such as a form body, use encoding/query; for percent-encoding a single string, encoding.URLEncode in encoding; to fetch the address, net/http.
Raw and decoded parts
The fields hold the text exactly as it appeared, percent escapes included, so String(Parse(x)) normally gives back x. Read the human form with DecodedPath, DecodedUserinfo or Query. When you set a field yourself, put encoded text in it: escape path segments with EncodePathSegment and build queries with SetQuery.
What trips people up
Parsenever fails. It does no validation: a port that is not a number becomes0, and text without://(includingmailto:sara@example.com) has no scheme or host and lands whole inpath. Checkschemeandhostyourself before trusting an address from a user.- Nothing is lowercased.
HTTPS://Shop.Example.COMkeeps its case inschemeandhost, andDomainreturnsExample.COM. Lowercase withstr.Lowerbefore you compare hosts or domains. - Editing one parameter rewrites the whole query.
RemoveParam,SetParamandKeepParamsdecode the query and encode it again:%20becomes+, characters such as*get escaped, a bareflagbecomesflag=, and repeated keys are grouped together. The meaning stays the same; the text may not. Joinwith only?queryor#fragmentloses the file name of the base, unlike a browser. See the links guide.
Guide: strip tracking parameters
Links copied from email and social media carry utm_ and click id parameters that only serve analytics. Before you store or compare links, parse them, drop those parameters with RemoveParam, clear the fragment and print the result with String. The parameters that stay keep their order.
import encoding.url
func clean(raw: str): str:
mut u := url.Parse(raw)
names := url.ParamNames(u)
each n in names:
if n.starts_with("utm_") or n == "fbclid" or n == "gclid":
_ := url.RemoveParam(u, n)
end
end
u.fragment = ""
ret url.String(u)
end
func main:
println clean("https://shop.example/item/42?color=red&utm_source=news&utm_medium=email#reviews")
println clean("https://blog.example/post?fbclid=IwAR0abc")
println clean("https://example.com/search?q=salam%20lang&utm_campaign=fall&page=2&debug")
endParamNames returns a copy of the names, so removing parameters inside the loop is safe. The fields of URL are plain strings you can set directly, as u.fragment = "" does. The last line shows the rewrite described above: salam%20lang comes out as salam+lang and the bare debug as debug=. Both mean the same to a server, but if you use the cleaned link as a cache or database key, clean every link the same way so equal links give equal text.
Guide: resolve the links of a page
A crawler or a link checker finds relative links like ../api/json.html in a page and needs the full address. Join resolves a reference against the page's own URL the way a browser does: ./ and ../ move through the folders, a leading / starts at the site root, //host keeps the scheme, and a full URL replaces everything.
import encoding.url
func main:
page := url.Parse("https://docs.example.com/guide/start/index.html?lang=en")
hrefs := ["install.html", "./setup/linux.html", "../api/json.html", "/blog/", "https://other.example/x", "//cdn.example.com/app.js"]
each h in hrefs:
println h, "->", url.String(url.Join(page, h))
end
endA reference that is only a query (?lang=fa) or only a fragment (#top) does not keep the file name of the base yet, so resolve those yourself by setting raw_query or fragment on a copy of the base.
Guide: is it the same site?
To allow redirects only within your own site, or to group links by owner, compare the registrable domain instead of the host name. Domain is the part a person registers, such as example.co.uk; PublicSuffix is the part under which anyone can register; Subdomain is what is left on the left. An address made of an IP has no domain, so compare the host name there. Host names are not case sensitive but the package keeps their case, so the program lowercases both addresses before parsing them.
import encoding.url
import str
func same_site(a: str, b: str): bool:
ua := url.Parse(str.Lower(a))
ub := url.Parse(str.Lower(b))
if url.IsIP(ua) or url.IsIP(ub):
ret url.Hostname(ua) == url.Hostname(ub)
end
ret url.Domain(ua) == url.Domain(ub)
end
func main:
u := url.Parse("https://api.eu.shop.example.co.uk:8443/v1/orders")
println "host:", url.Hostname(u), "port:", u.port
println "domain:", url.Domain(u), "suffix:", url.PublicSuffix(u), "sub:", url.Subdomain(u)
println same_site("https://www.example.com/a", "https://login.example.com/b")
println same_site("https://WWW.Example.com/", "https://example.com/cart")
println same_site("https://example.com/", "https://example.org/")
println same_site("http://10.0.0.5:8080/", "http://10.0.0.5/")
endThe package knows the common public suffixes such as com, co.uk and com.au, not the complete Public Suffix List, so hosting platforms that give every customer a subdomain may count as one site. Check those hosts by name when it matters.
Guide: build a URL from user input
When parts of the address come from users, escape each path segment with EncodePathSegment, which also escapes / so a file name cannot add folders. Put the parameters in a QueryMap and attach it with SetQuery; Add allows the same key more than once.
import encoding.url
import encoding.query
func main:
folder := "My Files"
name := "report 2026/Q3.pdf"
mut u := url.Parse("https://files.example.com")
u.path = "/users/sara/" + url.EncodePathSegment(folder) + "/" + url.EncodePathSegment(name)
mut q := query.NewQueryMap()
query.Set(q, "download", "1")
query.Add(q, "tag", "finance")
query.Add(q, "tag", "سهماهه")
url.SetQuery(u, q)
full := url.String(u)
println full
back := url.Parse(full)
println "path:", url.DecodedPath(back)
println "tags:", query.GetAll(back.raw_query, "tag")
endDecodedPath gives the readable path but turns %2F back into /, so it cannot tell a slash inside a name from a folder boundary. Split u.path at / first and decode each segment when you need the names exactly.
Types
URL
struct URL| Name | Type | Description |
|---|---|---|
scheme | str = "" | The scheme, such as https. |
userinfo | str = "" | The user name and password before the @, still encoded. |
host | str = "" | The host name or numeric address. |
port | int = 0 | The port, or zero when none was given. |
path | str = "" | The path, still encoded. |
raw_query | str = "" | The query after the ?, still encoded. |
fragment | str = "" | The part after the #. |
import encoding.url
import encoding.query
func main:
mut u := url.Parse("https://user%20a:pw@shop.example.co.uk:8443/books/%D8%B3%D9%84%D8%A7%D9%85?page=2&sort=new#top")
println u.scheme, u.host, u.port, u.path, u.raw_query, u.fragment
println url.Hostname(u), url.HostPort(u), url.IsIP(u)
println url.Labels(u), url.PublicSuffix(u), url.Domain(u), url.Subdomain(u)
println url.DecodedPath(u), url.DecodedUserinfo(u)
println url.ParamNames(u), query.Size(url.Query(u))
url.SetParam(u, "page", "3")
println url.RemoveParam(u, "sort"), url.String(u)
mut keep := Vector {} as Vector<str>
keep.push("page")
url.KeepParams(u, keep)
mut q := query.NewQueryMap()
query.Add(q, "q", "hello world")
url.SetQuery(u, q)
println url.String(u)
url.ClearQuery(u)
println url.String(u)
println url.String(url.Join(u, "../img/a.png")), url.EncodePathSegment("a b/c")
println url.IsIP(url.Parse("http://127.0.0.1/"))
endFunctions
Hostname
func Hostname(u: URL): strThe example for this item is shown with the ones above: encoding-url-Parse.salam
IsIP
func IsIP(u: URL): bool: (an IPv6 address). The digits are not range checked.The example for this item is shown with the ones above: encoding-url-Parse.salam
Labels
func Labels(u: URL): Vector<str>The example for this item is shown with the ones above: encoding-url-Parse.salam
PublicSuffix
func PublicSuffix(u: URL): strco.uk or ir.The example for this item is shown with the ones above: encoding-url-Parse.salam
Domain
func Domain(u: URL): strexample.co.uk. An IP address or a one-label host such as localhost is returned unchanged. Case is kept.The example for this item is shown with the ones above: encoding-url-Parse.salam
Subdomain
func Subdomain(u: URL): strshop.The example for this item is shown with the ones above: encoding-url-Parse.salam
RemoveParam
func RemoveParam(u &: URL, key: str): boolThe example for this item is shown with the ones above: encoding-url-Parse.salam
SetParam
func SetParam(u &: URL, key: str, val: str)The example for this item is shown with the ones above: encoding-url-Parse.salam
KeepParams
func KeepParams(u &: URL, keep: Vector<str>)The example for this item is shown with the ones above: encoding-url-Parse.salam
ClearQuery
func ClearQuery(u &: URL)The example for this item is shown with the ones above: encoding-url-Parse.salam
ParamNames
func ParamNames(u: URL): Vector<str>The example for this item is shown with the ones above: encoding-url-Parse.salam
Parse
func Parse(raw: str): URL:// (or a leading //) gets a scheme and host, and a port that is not a number becomes 0.The example for this item is shown with the ones above: encoding-url-Parse.salam
HostPort
func HostPort(u: URL): strshop.example.co.uk:8443.The example for this item is shown with the ones above: encoding-url-Parse.salam
String
func String(u: URL): strThe example for this item is shown with the ones above: encoding-url-Parse.salam
DecodedPath
func DecodedPath(u: URL): strThe example for this item is shown with the ones above: encoding-url-Parse.salam
DecodedUserinfo
func DecodedUserinfo(u: URL): strThe example for this item is shown with the ones above: encoding-url-Parse.salam
EncodePathSegment
func EncodePathSegment(s: str): str/ is encoded too.The example for this item is shown with the ones above: encoding-url-Parse.salam
Query
func Query(u: URL): query.QueryMapQueryMap.The example for this item is shown with the ones above: encoding-url-Parse.salam
SetQuery
func SetQuery(u &: URL, q: query.QueryMap)The example for this item is shown with the ones above: encoding-url-Parse.salam
Join
func Join(base: URL, ref: str): URL.. and . in the path are worked out. A reference of only ?query or #fragment drops the base file name.The example for this item is shown with the ones above: encoding-url-Parse.salam