{"record":{"id":"d0101e1cb1b37fb5","repo":"nodejs/node","slug":"query-params-cannot-be-passed-when-url-already-con","errorCode":null,"errorMessage":"Query params cannot be passed when url already contains \"?\" or \"#\".","messagePattern":"Query params cannot be passed when url already contains \"\\?\" or \"#\"\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"deps/undici/src/lib/core/util.js","lineNumber":109,"sourceCode":"/**\n * @param {string} url The path to check for query strings or fragments.\n * @returns {boolean} Returns true if the path contains a query string or fragment.\n */\nfunction pathHasQueryOrFragment (url) {\n  return (\n    url.includes('?') ||\n    url.includes('#')\n  )\n}\n\n/**\n * @param {string} url The URL to add the query params to\n * @param {import('node:querystring').ParsedUrlQueryInput} queryParams The object to serialize into a URL query string\n * @returns {string} The URL with the query params added\n */\nfunction serializePathWithQuery (url, queryParams) {\n  if (pathHasQueryOrFragment(url)) {\n    throw new Error('Query params cannot be passed when url already contains \"?\" or \"#\".')\n  }\n\n  const stringified = stringify(queryParams)\n\n  if (stringified) {\n    url += '?' + stringified\n  }\n\n  return url\n}\n\n/**\n * @param {number|string|undefined} port\n * @returns {boolean}\n */\nfunction isValidPort (port) {\n  const value = parseInt(port, 10)\n  return (","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/nodejs/node/blob/1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e/deps/undici/src/lib/core/util.js#L91-L127","documentation":"serializePathWithQuery throws when the input URL string already contains '?' or '#', because appending another serialized query would produce a malformed URL (duplicate '?' or query after fragment). Undici treats this as a programmer error rather than silently merging.","triggerScenarios":"Passing both a URL with embedded query/fragment AND a queryParams object to an internal helper that calls serializePathWithQuery (the public fetch/request APIs do not surface this directly; it fires through diagnostic, interceptor, or ProxyAgent path-building code).","commonSituations":"Hand-building URLs with template strings then also passing { query: {...} }; migrating from got/axios where query merging was lenient; double-encoding after a redirect handler that re-applies params.","solutions":["Drop the query string from the URL and supply all params via queryParams.","Or drop queryParams and pre-serialize with new URLSearchParams(...).toString() into the URL.","Use the URL/URLSearchParams APIs to mutate the URL in place rather than concatenating strings."],"exampleFix":"// before\nclient.request('https://api.example/x?a=1', { query: { b: 2 } })\n// after\nclient.request('https://api.example/x', { query: { a: 1, b: 2 } })","handlingStrategy":"validation","validationCode":"function buildUrl(base, query) {\n  if (query && (base.includes('?') || base.includes('#'))) {\n    throw new Error('Cannot combine inline query with queryParams object')\n  }\n  // proceed with the API call\n  return query ? `${base}?${new URLSearchParams(query).toString()}` : base\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick one source of truth for query params: inline OR object, never both.","Use URL/URLSearchParams for URL assembly instead of string concatenation.","Unit-test URL builders for the no-query and with-query cases."],"tags":["url","query-string","validation","undici"],"backgroundTag":null,"analyzedSha":"1b2de5e052fc0fb95fd7fb6846dcec4ade598e9e","analyzedAt":"2026-08-13T00:53:24.642Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}