Skip to content

feat: capture GA4 campaign parameters and Google click IDs used on iOS and Display & Video 360 - #223

Merged
sadjow merged 5 commits into
mainfrom
feat/google-campaign-parameters
Oct 14, 2026
Merged

sadjow merged 5 commits into
mainfrom
feat/google-campaign-parameters

Conversation

@sadjow

@sadjow sadjow commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

The module read five UTM parameters plus gclid and gad_source. Google documents more:

  • Google Analytics 4 documents four additional manual campaign parameters: utm_id, utm_source_platform, utm_creative_format, and utm_marketing_tactic, although its reports don't show the last two yet (URL builders).
  • Google Ads appends gbraid or wbraid alongside or instead of gclid for some iOS traffic (iOS measurement, click conversion uploads). It also adds gad_campaignid alongside gad_source (traffic-source dimensions), and Display & Video 360 and Campaign Manager 360 tag clicks with dclid.

None of these were stored, so attribution payloads lacked them. With captureWithoutCampaign: false, a visit tagged only with them was skipped, and in the same session a new gbraid click or a different utm_id was deduplicated as a repeat of the previous entry.

src/runtime/parameters.ts now holds both lists, and everything else derives from them: the UTMParams and GCLIDParams types, reading the query, deciding whether a visit is a campaign, and deduplication. Adding a parameter later is one edit. First and last touch already considered any nonempty field, so they pick up the new ones without changes. The README lists the supported fields.

UTMParams and GCLIDParams stay interfaces. Each one extends the type derived from its list, so apps can still add fields through declaration merging. @typescript-eslint/no-empty-object-type flags an interface that only extends another type, so the ESLint config allows that single-extends form. Entries stored by earlier versions remain valid because every field stays optional.

This changes which visits count as campaigns. A visit tagged only with one of the new parameters, such as a dclid click or a gbraid visit, used to be a direct visit. Now it is a campaign touch, can become lastTouch, and adds new keys to getAttribution() payloads. A backend that validates attribution against only the original keys needs to accept the new ones, so the release notes should call this out.

The 12 new cases in test/utm.test.ts cover each new parameter on its own, reading all of them, and deduplication on a changed utm_id or a new gbraid; all of them failed before the change. The package consumer fixture checks that the published types include the new fields, keep them typed as strings, and can still be augmented. The augmentation check failed with Duplicate identifier 'UTMParams' while the types were aliases.

Validation: pnpm lint --max-warnings 0, pnpm format:check, pnpm test:types, pnpm dev:build, pnpm test (96 tests), pnpm test:package, and pnpm test:compatibility against Nuxt 4.6.1 and Nuxt 3.21.11 pass.

Base automatically changed from test/consolidate-utm-helper-tests to main October 14, 2026 01:14
@sadjow

sadjow commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

Review notes before merging:

  • UTMParams and GCLIDParams are interfaces on main, and this PR had turned them into type aliases, which would break any app that adds fields to them through declaration merging. They're interfaces again, each extending the type derived from its parameter list. The package consumer fixture now augments both and failed with Duplicate identifier 'UTMParams' while they were aliases. @typescript-eslint/no-empty-object-type flags an interface that only extends another type, so the ESLint config allows that single-extends form.
  • Google Analytics 4 documents utm_creative_format and utm_marketing_tactic, but its reports don't show them yet, so the comments and README say the list is what GA4 documents rather than what it reads. Google Ads can send gbraid or wbraid alongside gclid, not only instead of it, and the wording now says so.
  • A visit tagged only with one of the new parameters used to be a direct visit and is now a campaign touch, with new keys in getAttribution() payloads. The description calls this out for the release notes.
  • fix: generate session IDs from 128 cryptographically random bits #222 also changed src/runtime/utm.ts above generateSessionId. The merge keeps both the parameter helpers from here and the new session ID generator.

@sadjow
sadjow enabled auto-merge (squash) October 14, 2026 01:27
@sadjow
sadjow merged commit 874525f into main Oct 14, 2026
7 checks passed
@sadjow
sadjow deleted the feat/google-campaign-parameters branch October 14, 2026 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant