[analytics-core] getQueryParams does not decode `+` as space and truncates values containing `=`

Open Beginner friendly
#2,179 1 comment 0 reactions 0 assignees View on GitHub

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
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

bug

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:

  1. 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']).
  2. decodeURIComponent() leaves + as a literal +, so ?utm_term=running+shoes and ?utm_term=running%20shoes are 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

  1. Install @amplitude/[email protected]:
    npm i @amplitude/[email protected]
    
  2. 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
    
  3. 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], and main (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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from amplitude/Amplitude-TypeScript

All issues in amplitude/Amplitude-TypeScript

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.