querystring.escape()

Percent-encodes a string for use inside a URL query, using the module's own fast encoder.

Signature
querystring.escape(str)
  • Added in Node.js v0.1.25

Parameters

ParameterTypeDescription
$str<string>The string to percent-encode.

Return value

<string>The percent-encoded form of the input.

Errors

Throws a URIError with code ERR_INVALID_URI ('URI malformed') if the string contains a lone surrogate that cannot be encoded as UTF-8.

querystring.escape() is the encoder that querystring.stringify() calls on every key and every value. It is exported mainly so that application code can replace it — assigning a different function to querystring.escape, or passing options.encodeURIComponent to stringify(), swaps the encoding strategy for the whole module.

How it compares to encodeURIComponent()

For the same input the two produce the same output: both leave A-Z a-z 0-9 - _ . ! ~ * ' ( ) alone and percent-encode everything else as UTF-8 bytes, including a space as %20. The difference is implementation, not result — this one is written for the specific shape of query strings and is faster. It also does not throw on non-string input the way you might expect; numbers and booleans are coerced to their string form first.

When to reach for it

Reach for it when you are assembling one value into a URL by hand and stringify() would be overkill, or when you are writing a custom encoder and want to delegate to the default for most characters. If a value could contain &, =, #, ? or a space, it must pass through here or the parameter will be silently split on the other side.

Do not use it for path segments: it does not encode ', (, ), * or !, which are permitted in a query but carry meaning elsewhere.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Inserting one user-supplied value into a hand-built URL without breaking on & or =
  • Writing a custom encoder that delegates to the default for most characters
  • Encoding a value for a signature or cache key that must match what stringify() produces
  • Escaping a search term before appending it to an existing query string

Frequently asked questions

What is the difference between querystring.escape and encodeURIComponent?

They produce identical output for every well-formed string; querystring.escape is an implementation tuned for query strings and is faster, but it is not standardized and exists mainly so its behaviour can be replaced. Both leave - _ . ! ~ * ' ( ) unencoded. They diverge on unpaired surrogates: encodeURIComponent always throws a URIError, while querystring.escape throws ERR_INVALID_URI only when the unpaired surrogate is the final character of the string — a lone high surrogate followed by another character is silently encoded as incorrect bytes instead.

Why does querystring.escape encode a space as %20 instead of +?

Because it follows percent-encoding rules rather than the older form-encoding convention, where + means space. Both decode to a space in practice, but if a server or signature scheme requires +, post-process the result with .replace(/%20/g, '+').

Is querystring.escape safe for URL path segments?

Not entirely — it leaves ! ~ * ' ( ) unencoded, which are reserved sub-delimiters outside a query. For path segments use encodeURIComponent plus your own handling of those characters, or build the URL with the WHATWG URL class.

More examples

Appending one value to an existing URL

The last two lines are the whole argument for escaping. An unescaped & inside a value is indistinguishable from a separator, so the receiving parser sees an extra parameter the caller never intended — the query-string equivalent of an injection.

Example · javascript
const querystring = require('node:querystring');

function addParam(url, key, value) {
  const sep = url.includes('?') ? '&' : '?';
  return `${url}${sep}${querystring.escape(key)}=${querystring.escape(value)}`;
}

console.log(addParam('/search', 'q', 'rust & wasm'));
// /search?q=rust%20%26%20wasm

console.log(addParam('/search?page=2', 'q', 'a=b'));
// /search?page=2&q=a%3Db

// Without escaping, the value would forge a second parameter:
console.log('/search?q=' + 'rust&admin=1');
// /search?q=rust&admin=1   <-- 'admin' is now a real parameter

Replacing the module's encoder

Both routes change what stringify() emits. The per-call option is local and safe; reassigning querystring.escape is global and affects every module in the process that uses the querystring module, so it belongs only in an application entry point, never in a library.

Example · javascript
const querystring = require('node:querystring');

const builtin = querystring.escape;

// A stricter encoder: also encode the sub-delims the default leaves alone.
function strictEscape(str) {
  return builtin(String(str)).replace(
    /[!'()*~]/g,
    (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase(),
  );
}

console.log(builtin("o'brien (x)"));      // o'brien%20(x)
console.log(strictEscape("o'brien (x)")); // o%27brien%20%28x%29

// Option 1: per call.
console.log(querystring.stringify({ name: "o'brien" }, null, null, {
  encodeURIComponent: strictEscape,
}));
// name=o%27brien

// Option 2: process-wide, which every later stringify() call picks up.
querystring.escape = strictEscape;
console.log(querystring.stringify({ name: "o'brien" }));
// name=o%27brien

Guarding against a sliced surrogate pair

Shows the realistic path to ERR_INVALID_URI: a length limit applied with slice() splits an astral character into an unpaired surrogate, which has no valid UTF-8 encoding. Spreading the string into an array iterates by code point and keeps the pair intact.

Example · javascript
const querystring = require('node:querystring');

const label = 'deploy 🚀 done';

// slice() counts UTF-16 code units, so this cuts the emoji in half.
const truncated = label.slice(0, 8);
console.log(JSON.stringify(truncated)); // "deploy \ud83d"

try {
  querystring.escape(truncated);
} catch (err) {
  console.log(err.code); // ERR_INVALID_URI
}

// Truncate by code points instead.
const safe = [...label].slice(0, 8).join('');
console.log(JSON.stringify(safe));      // "deploy 🚀"
console.log(querystring.escape(safe));  // deploy%20%F0%9F%9A%80

Discussion

  • Be the first to comment on this lesson.