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.
QueryEscapeuses+for spaces;PathEscapeuses%20in path segments.
Quick answer: query vs path
url.QueryEscape— value will live in the query (?name=valueor insideurl.Values).url.QueryUnescape— decode strings produced byQueryEscape(also treats+as space).url.PathEscape— value is a single path segment (between/separators).url.PathUnescape— decode strings produced byPathEscape.
Do not apply the same function to every part of a URL. Query rules and path rules differ (especially for spaces).
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)
}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.).
package main
import (
"fmt"
"net/url"
)
func main() {
q := url.QueryEscape("go lang tutorial")
fmt.Println("/search?q=" + q)
}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.
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())
}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.
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)
}
}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).
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)
}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.
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())
}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.
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"))
}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.
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())
}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:
package main
import (
"fmt"
"net/url"
)
func main() {
s := "hello world"
fmt.Println("query:", url.QueryEscape(s))
fmt.Println("path: ", url.PathEscape(s))
}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.

