Skip to main content
  1. Tutorials/

How to URL-decode in Go with net/url (parse, query, path)

··3 mins

URL encoding (percent-encoding) makes URLs safe to transmit. To decode an encoded URL in Go, use the net/url package. The most common tools are url.Parse() for full URLs and url.QueryUnescape() / url.PathUnescape() for individual components.

If you want to URL encode a path, a query, or the whole URL, see URL Encode in Go post.

A typical URL consists of the following components:

scheme://host:port/path?query

Decode a full URL with url.Parse #

The url.Parse() function takes a URL string and returns a url.URL with decoded fields.

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    // decode URL by url.Parse
    parsedURL, err := url.Parse("https://example.com/foo+bar%21?query=ab%2Bc&query2=de%24f")
    if err != nil {
        log.Fatal(err)
        return
    }

    fmt.Printf("scheme: %s\n", parsedURL.Scheme)
    fmt.Printf("host: %s\n", parsedURL.Host)
    fmt.Printf("path: %s\n", parsedURL.Path)
    fmt.Println("query args:")
    for key, values := range parsedURL.Query() {
        fmt.Printf("  %s = %s\n", key, values[0])
    }
}

Result:

scheme: https
host: example.com
path: /foo+bar!
query args:
  query = ab+c
  query2 = de$f

Decode path and query components #

Use these helpers when you only have a piece of a URL:

See the example to compare these functions:

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    // decode path by url.PathUnescape
    path := "foo+bar%21"
    unescapedPath, err := url.PathUnescape(path)
    if err != nil {
        log.Fatal(err)
        return
    }
    fmt.Printf("unescaped path: %s\n", unescapedPath)

    // decode query by url.QueryUnescape
    query := "query=ab%2Bc&query2=de%24f"
    unescapedQuery, err := url.QueryUnescape(query)
    if err != nil {
        log.Fatal(err)
        return
    }
    fmt.Printf("unescaped query: %s\n", unescapedQuery)

    // decode query and parse by url.ParseQuery
    parsedQuery, err := url.ParseQuery(query)
    if err != nil {
        log.Fatal(err)
        return
    }
    fmt.Println("parsed query args:")
    for key, values := range parsedQuery {
        fmt.Printf("  %s = %s\n", key, values[0])
    }
}

Result:

unescaped path: foo+bar!
unescaped query: query=ab+c&query2=de$f
parsed query args:
  query = ab+c
  query2 = de$f

Common mistakes #

❌ Using QueryUnescape on a full URL:

decoded, _ := url.QueryUnescape("https://example.com/?q=a%2Bb")

✅ Use url.Parse for full URLs and read the components:

parsed, _ := url.Parse("https://example.com/?q=a%2Bb")
value := parsed.Query().Get("q")

❌ Confusing path and query decoding:

path := url.QueryUnescape("foo+bar%21") // "+" becomes space

✅ Use PathUnescape for path segments:

path, _ := url.PathUnescape("foo+bar%21")

Best practices #

  • Decode only the part you control (path vs query vs full URL).
  • Use url.Parse for full URLs to avoid subtle encoding bugs.
  • Expect multiple values per query key; use Get or iterate.

Tested with Go 1.25+ | Last verified: December 2025 🎉