[analytics-core] getQueryParams does not decode `+` as space and truncates values containing `=`
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 82/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- javascript, typescript
- Domain
- analytics
Research direction
Start in packages/analytics-core/src/query-params.ts, where getQueryParams splits each pair with split('=', 2) and decodes through tryDecodeURIComponent. CampaignParser in the same package consumes its output, so check it too. Done when the example query string from the issue yields utm_source 'news letter', utm_term 'running shoes', utm_content 'a=b=c' and gclid 'abc==', with a test covering it.
Written by the indexing model from the issue text.
Description
Expected Behavior
getQueryParams() and CampaignParser should decode + as a space per application/x-www-form-urlencoded (matching URLSearchParams and legacy amplitude-js's utils.getQueryParam) and preserve = characters inside parameter values.
For ?utm_source=news+letter&utm_term=running+shoes&utm_content=a%3Db=c&gclid=abc==, the parsed parameters should be:
{
utm_source: 'news letter',
utm_term: 'running shoes',
utm_content: 'a=b=c',
gclid: 'abc=='
}
Current Behavior
In packages/analytics-core/src/query-params.ts, getQueryParams() splits each pair with curr.split('=', 2) and decodes with decodeURIComponent() directly:
- In JavaScript,
String.prototype.split('=', 2)splits on every=and returns only the first two elements, discarding everything from the second=onward ('gclid=abc=='.split('=', 2)returns['gclid', 'abc']). decodeURIComponent()leaves+as a literal+, so?utm_term=running+shoesand?utm_term=running%20shoesare recorded as two distinct campaign terms.
Because CampaignParser and analytics-browser call getQueryParams(), default init() records the unparsed + and truncated values in both $identify (initial_utm_*, utm_*, initial_gclid, gclid) and [Amplitude] Page Viewed event properties.
Possible Solution
Split on the first = using indexOf('=') instead of split('=', 2), and replace + with ' ' before calling decodeURIComponent() in tryDecodeURIComponent:
const eqIndex = curr.indexOf('=');
if (eqIndex === -1) {
return acc;
}
const key = tryDecodeURIComponent(curr.slice(0, eqIndex).replace(/\+/g, ' '));
const value = tryDecodeURIComponent(curr.slice(eqIndex + 1).replace(/\+/g, ' '));
Steps to Reproduce
- Install
@amplitude/[email protected]:npm i @amplitude/[email protected] - Run:
node --input-type=module <<'EOF' import { CampaignParser, getQueryParams } from "@amplitude/analytics-core"; const search = "?utm_source=news+letter&utm_term=running+shoes&utm_content=a%3Db=c&gclid=abc=="; globalThis.location = { search, href: "https://example.com/" + search, hostname: "example.com" }; console.log(getQueryParams()); const c = await new CampaignParser().parse(); console.log(c.utm_source, "|", c.utm_term, "|", c.utm_content, "|", c.gclid); EOF - Actual output:
{ utm_source: 'news+letter', utm_term: 'running+shoes', utm_content: 'a=b', gclid: 'abc' } news+letter | running+shoes | a=b | abc
Environment
- JS SDK Version:
@amplitude/[email protected],@amplitude/[email protected], andmain(5b445aa) - Installation Method: npm
- Browser and Version: Node.js v26.13.0 (also affects all browsers via
@amplitude/analytics-browser)
Credit: https://ucsbplse.github.io/
- Dominant language
- TypeScript
- Stars
- 181
- Forks
- 66
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 41
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from amplitude/Amplitude-TypeScript
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
amplitude/Amplitude-TypeScript#1615 · 1 comment ·
Maintainers usually reply within 2 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
amplitude/Amplitude-TypeScript#1367 · 5 comments · 24 reactions ·
Maintainers usually reply within 2 days
-
Session Replay keeps observing blocked iframes, retaining their documents until recording stopsOpenbug customer-reported
Difficulty 3/5 1-2 days Newbie friendliness 58/100
amplitude/Amplitude-TypeScript#2119 · 1 comment ·
Maintainers usually reply within 2 days
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 68/100
amplitude/Amplitude-TypeScript#2027 · 2 comments ·
Maintainers usually reply within 2 days
-
Difficulty 1/5 Under an hour Newbie friendliness 1/100
amplitude/Amplitude-TypeScript#1736 ·
Maintainers usually reply within 2 days
All issues in amplitude/Amplitude-TypeScript
Similar issues
-
ci good first issue severity: low
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
TMHSDigital/GH-Arcade#111 ·
-
AsnIntegerConverter.toASN coerces strings with +value, rounding integers outside safe-integer rangeOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PeculiarVentures/asn1-schema#189 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 4 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
anaisbetts/spawn-rx#38 ·