Golang URL Encode Decode: QueryEscape, PathEscape, and Query Params

URL encode and decode strings in Go with net/url: QueryEscape, QueryUnescape, PathEscape, PathUnescape, url.Values, ParseQuery, and url.Parse with RawQuery.

Published

Updated

Read time 6 min read

Reviewed byDeepak Prasad

Golang URL Encode Decode: QueryEscape, PathEscape, and Query Params

Use net/url when you need to put user text or structured data into a URL: query values after ?, path segments between slashes, or an entire URL nested as one query value (for example /go?url=https%3A%2F%2Fexample.com). The usual mistake is picking the wrong escape function or encoding a whole URL when only a fragment should be escaped. For control flow when printing parameters, a for loop works well.

Tested on: Go 1.22, 64-bit Linux. QueryEscape uses + for spaces; PathEscape uses %20 in path segments.


Quick answer: query vs path

  • url.QueryEscape — value will live in the query (?name=value or inside url.Values).
  • url.QueryUnescape — decode strings produced by QueryEscape (also treats + as space).
  • url.PathEscape — value is a single path segment (between / separators).
  • url.PathUnescape — decode strings produced by PathEscape.

Do not apply the same function to every part of a URL. Query rules and path rules differ (especially for spaces).

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	fmt.Println(url.QueryEscape("hello world"))
	fmt.Println(url.QueryUnescape("hello+world"))

	fmt.Println(url.PathEscape("hello world"))
	s, _ := url.PathUnescape("hello%20world")
	fmt.Println(s)
}
Output

You should see hello+world and hello world from query helpers, then hello%20world and hello world from path helpers—note how space encoding differs between query and path.


URL encode and decode in Go

Go implements percent-encoding helpers in net/url. Common entry points:

Function Role
url.QueryEscape Encode one value for a query string
url.QueryUnescape Decode query-shaped encoding; returns error on bad %
url.PathEscape Encode one path segment
url.PathUnescape Decode path segment encoding; returns error on bad %
url.Values.Encode Build key=value&… with correct escaping
url.ParseQuery Parse a raw query string into url.Values
url.Parse Split a full URL into parts you can modify

Rule: encode the piece that sits in that syntactic role, not the entire URL string, unless the whole URL is intentionally stored as one query value.


Encode a string for URL query

Use url.QueryEscape when a string becomes a query parameter value (search text, filters, redirect targets, email fragments, anything with spaces, &, =, ?, /, etc.).

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	q := url.QueryEscape("go lang tutorial")
	fmt.Println("/search?q=" + q)
}
Output

You should see a path like /search?q=go+lang+tutorial with + for spaces.

Encode a full URL as a query parameter value

When a handler looks like /go?url=…, the value of url must be encoded so its own ?, &, and = do not terminate the outer query early.

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	inner := "https://example.com/path?a=1&b=2"
	v := url.Values{}
	v.Set("url", inner)
	fmt.Println("/go?" + v.Encode())
}
Output

Values.Encode escapes values the same way as query encoding. You should get /go? followed by url= and a percent-encoded copy of the inner URL (not a second bare ? inside the outer query).


Decode a URL-encoded query string

url.QueryUnescape inverts QueryEscape: %AB hex pairs become bytes, and + becomes a space. Always check the error on untrusted input—invalid percent sequences fail.

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	s, err := url.QueryUnescape("hello+world%21")
	if err != nil {
		panic(err)
	}
	fmt.Println(s)

	if _, err := url.QueryUnescape("abc%zz"); err != nil {
		fmt.Println("decode error:", err)
	}
}
Output

You should see hello world! then a line reporting an error for abc%zz.


Encode and decode URL path segments

url.PathEscape prepares one segment of a path. Slashes that belong inside the value must be escaped; slashes that separate segments should appear outside the escaped string (split the path, escape each piece).

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	raw := "john/admin@example.com"
	enc := url.PathEscape(raw)
	fmt.Println(enc)
	dec, err := url.PathUnescape(enc)
	if err != nil {
		panic(err)
	}
	fmt.Println(dec)
}
Output

The first line is the escaped segment; the second line should match raw again.


Build query parameters in Go

Prefer url.Values over string concatenation like "?q=" + userInput. Encode sorts keys, joins with &, and escapes names and values.

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Set("q", "go url encode")
	v.Set("page", "1")
	fmt.Println(v.Encode())
}
Output

You should see something like page=1&q=go+url+encode (order is sorted by key).


Decode query parameters

url.ParseQuery turns a raw query string (without leading ?) into url.Values.

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	m, err := url.ParseQuery("q=go+url+encode&page=1")
	if err != nil {
		panic(err)
	}
	fmt.Println(m.Get("q"), m.Get("page"))
}
Output

In HTTP handlers, r.URL.Query() already returns url.Values for the request query string—you often skip ParseQuery unless you are parsing an isolated string. For *http.Request, clients, and handlers in general, see Golang HTTP.


Parse and modify a full URL

url.Parse accepts a full URL string. Read parameters with Query(), change them, then assign RawQuery from Encode() again so existing keys stay consistent and escaping stays correct.

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	u, err := url.Parse("https://api.example.com/items?page=1&q=old")
	if err != nil {
		panic(err)
	}
	q := u.Query()
	q.Set("page", "2")
	q.Set("q", "go/url")
	u.RawQuery = q.Encode()
	fmt.Println(u.String())
}
Output

You should see page=2, updated q, and a safely encoded slash in the query value.


QueryEscape vs PathEscape

Topic QueryEscape PathEscape
Typical place After ?key= Inside /path/segments/
Space + %20
Slash / %2F (in value) %2F when slash is part of one segment’s data
Decode with QueryUnescape PathUnescape

Same word, different encoding:

go
package main

import (
	"fmt"
	"net/url"
)

func main() {
	s := "hello world"
	fmt.Println("query:", url.QueryEscape(s))
	fmt.Println("path: ", url.PathEscape(s))
}
Output

You should see hello+world versus hello%20world.


Common mistakes

Encoding the entire URL when only one part should be encoded

Parse first, then escape query values or path segments individually. Only QueryEscape(entireURL) when that whole string is stored as one query value (redirect parameters).

Using QueryEscape for path segments

Use PathEscape per segment unless you deliberately want query-style + spaces in a path (you usually do not).

Using PathEscape for query values

Use QueryEscape or url.Values for ?… parameters.

Manually concatenating query strings

Avoid "/search?q=" + input. Use url.Values or at least QueryEscape on the value side.

Double encoding

Encoding an already-encoded token turns % into %25, producing values like %252F. Decode before re-encoding when you are normalizing user input.

Ignoring decode errors

QueryUnescape and PathUnescape return error for invalid percent encoding—do not discard that on external input.


Go URL encode decode cheat sheet

Goal Use
Encode query value url.QueryEscape(s)
Decode query value url.QueryUnescape(s)
Encode path segment url.PathEscape(s)
Decode path segment url.PathUnescape(s)
Build many query keys url.Values + Encode()
Parse raw query string url.ParseQuery(raw)
Read request query r.URL.Query()
Parse full URL url.Parse(raw)
Update query safely q := u.Query(); q.Set(...); u.RawQuery = q.Encode()
Put full URL in ?url= v.Set("url", fullURL); v.Encode() (or QueryEscape(fullURL) when building manually)

Which function should I use?

Value sits in… Function
?q=value QueryEscape / Values
/users/name segment PathEscape
?redirect=https://… Encode only the inner URL as the parameter value
Raw a=1&b=2 string ParseQuery

Summary

net/url separates query encoding (QueryEscape / QueryUnescape, + for space) from path segment encoding (PathEscape / PathUnescape, %20 for space). Build multi-key queries with url.Values and Encode, read them with ParseQuery or URL.Query, and change full URLs with Parse plus RawQuery = u.Query().Encode(). Encode a whole URL only when it is the value of a parameter (for example /go?url=…) so inner ? and & do not break the outer URL. Check error from unescape and parse helpers, and avoid double-encoding.


References

Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …