URLSearchParams encoder / decoder

Encode and decode form-urlencoded query strings where spaces become + (not %20).

Docify's URLSearchParams tool uses the WHATWGURLSearchParams API to encode and decode query strings in application/x-www-form-urlencoded format. Key difference from percent-encoding: spaces become +, not %20. Example: q=hello world&category=news encodes to q=hello+world&category=news. Preserves duplicate keys. For percent-encoding with %20, use our URL Encoder. Nothing is uploaded.

Query string
Encoded output

How it works

  1. Encoding: Uses new URLSearchParams(input).toString() which applies form-urlencoded serialization. Spaces → +, special characters → percent-encoded.
  2. Decoding: Parses the encoded query string and outputs decoded key=value pairs, one per line. The + character is decoded as a space.
  3. Leading ?: An optional leading ? is stripped before processing, so both "?q=test" and "q=test" work the same.
  4. Duplicate keys: Preserved in order. "a=1&a=2" stays as two entries with key "a".

URLSearchParams vs encodeURIComponent

CharacterURLSearchParamsencodeURIComponent
Space+%20
~%7E~ (unchanged)
** (unchanged)%2A

URLSearchParams in Python and Node

The snippets below parse and serialize application/x-www-form-urlencoded query pairs. Node's new URLSearchParams() is the same WHATWG class this page uses. CPython urllib.parse.parse_qsl plus urlencode is the stdlib pair that keeps duplicate keys in order. For when form encoding differs from RFC 3986 percent-encoding, see URLSearchParams vs encodeURIComponent: Form vs Percent-Encoding.

Node.js with URLSearchParams

Node 10+ ships the WHATWG URLSearchParams class (also as import { URLSearchParams } from 'node:url'). A leading ? is stripped. Spaces become + on toString(). Avoid the legacy querystring helper — stringify writes %20 for spaces, not this form encoding:

const encoded = new URLSearchParams('q=hello world&category=news').toString();
// "q=hello+world&category=news"

const pairs = [...new URLSearchParams('q=hello+world&category=news').entries()];
// [["q", "hello world"], ["category", "news"]]

new URLSearchParams('?q=hello world').toString();
// "q=hello+world"

new URLSearchParams('a=1&a=2').toString();
// "a=1&a=2"

Python with urllib.parse.parse_qsl

parse_qsl returns an ordered list of (key, value) tuples. Strip a leading ? first — unlike this page and Node, parse_qsl('?q=hello') treats ?q as the key. Pass keep_blank_values=True to keep empty values. Prefer parse_qsl over parse_qs, which groups duplicates into lists and reorders mixed keys:

from urllib.parse import parse_qsl, urlencode

raw = 'q=hello world&category=news'.lstrip('?')
pairs = parse_qsl(raw, keep_blank_values=True)
# [('q', 'hello world'), ('category', 'news')]

urlencode(pairs)
# 'q=hello+world&category=news'

parse_qsl('a=1&a=2', keep_blank_values=True)
# [('a', '1'), ('a', '2')]

urlencode writes * as %2A and leaves ~; this page and Node leave * and write ~ as %7E. quote_plus / unquote_plus encode one value, not a query. This page rejects a full URL; paste only the query. curl --url-query samples are in URLSearchParams in the shell below. Percent-encode a string on the URL encoder; split an absolute URL on the URL parser.

URLSearchParams in the shell

There is no POSIX form-urlencoded encoder. jq 1.7 @uri percent-encodes UTF-8 and writes %20 for spaces — RFC 3986, not this page. curl 8.5 --url-query (and --data-urlencode with -G) is application/x-www-form-urlencoded: spaces become +. Each flag is one name=value pair, not a whole query. On the documented pairs ( q=hello world and category=news), two --url-query flags write the same q=hello+world&category=news Encode writes. -w '%{url_effective}' prints the encoded URL; the host is only a placeholder. curl is not POSIX. For the Node and CPython snippets, see URLSearchParams in Python and Node above.

curl --url-query

Confirmed here with curl 8.5. --url-query (curl 7.87+) appends a query without -G. name@- reads stdin as that field's value. echo appends a newline, so use printf:

# Two pairs (same query Encode writes)
curl -s --url-query 'q=hello world' --url-query 'category=news' \
  -o /dev/null -w '%{url_effective}\n' \
  https://example.invalid/
# https://example.invalid/?q=hello+world&category=news

# Named stdin
printf '%s' 'hello world' | curl -s --url-query 'q@-' \
  -o /dev/null -w '%{url_effective}\n' \
  https://example.invalid/
# https://example.invalid/?q=hello+world

# Same encoder via -G --data-urlencode
curl -s -G --data-urlencode 'q=hello world' \
  --data-urlencode 'category=news' \
  -o /dev/null -w '%{url_effective}\n' \
  https://example.invalid/
# https://example.invalid/?q=hello+world&category=news

Passing the whole query as one flag encodes & and = in the value (q=hello+world%26category%3dnews); this page parses pairs. A leading ? becomes a literal question mark in the name (??q=hello+world); Encode strips one leading ?. curl leaves ~ and encodes * as %2a; this page writes %7E and leaves *. A literal + becomes %2b; Encode of q=hello+world stays q=hello+world (Decode treats that plus as a space). Hex digits are lowercase (%2b / %c3%a9); this page writes uppercase %2B / %C3%A9. Already-encoded %20 becomes %2520 in curl; Encode here writes hello+world. A space in a field name stays a space (hello world=news); this page writes hello+world=news. Empty stdin with @- adds no query. A full URL is encoded as a field; this page throws. curl has no form-urlencoded decoder — use Decode here or the Node / CPython snippets above. Without -G, --data-urlencode is a POST body, so %{url_effective} has no query. @uri and Encode component live on the URL encoder. curl %{url.host} is not a WHATWG parser — split an absolute URL on the URL parser. For form encoding vs percent-encoding, see URLSearchParams vs encodeURIComponent: Form vs Percent-Encoding.

FAQ

Does this tool upload my query string?
No. All encoding and decoding runs in your browser using the URLSearchParams API. Nothing is sent to a server.
What is the difference between URLSearchParams and encodeURIComponent?
URLSearchParams uses form-urlencoded format where spaces become + (application/x-www-form-urlencoded). encodeURIComponent uses percent-encoding where spaces become %20. Use URLSearchParams for form submissions and query strings; use encodeURIComponent for individual path segments.
Does this handle duplicate keys?
Yes. URLSearchParams preserves duplicate keys. "a=1&a=2" is kept as two separate entries with the same key, which is valid per the WHATWG URL Standard.
Can I paste a full URL?
No. Paste only the query string (with or without the leading ?). Full URLs like "https://example.com?q=test" will be misinterpreted. Use the URL Parser tool to extract query strings from full URLs.
Why does my space become + instead of %20?
This is correct for form-urlencoded data. The + for space is specified by the WHATWG URL Standard for application/x-www-form-urlencoded. Both + and %20 are valid, but form submissions use +.
Does encoding preserve Unicode normalization?
URLSearchParams percent-encodes the exact UTF-8 bytes without normalization. NFC é (U+00E9) and NFD e+combining-accent produce different encoded strings (%C3%A9 vs e%CC%81).

Related