Skip to content

Restore marketing-site signup attribution with a ref fallback on Console links - #8418

Merged
ankur-arch merged 5 commits into
mainfrom
site/console-ref-attribution
Oct 8, 2026
Merged

ankur-arch merged 5 commits into
mainfrom
site/console-ref-attribution

Conversation

@ankur-arch

@ankur-arch ankur-arch commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stamp ref=prisma.io/<page> on Console links for visitors who carry no attribution. This records the prisma.io page a signup converted from. It is not a source, it adds no UTM, and it never touches a visitor who arrived with real attribution.

This agrees with the thread on #8141: UTMs attribute links to the site from outside, the site itself is not an acquisition channel, and utm_source=website should not come back. The question this PR answers is a different one: for the ~1,000 signups a week that convert from a prisma.io page without any campaign behind them, which page was it, and are they coming through the site at all or straight to the console and CLI? Since Aug 11 nothing records that.

Three things support the change:

  1. It is not utm_source=website under another name. Different parameter, different column, fires only when there is nothing else, and PostHog does not read it as a campaign.
  2. The conversion page is worth recording, and nothing else captures it. The step change in untagged signups is the removed default, not privacy tooling, and the HTTP referrer recovers only about half of it with no page.
  3. UTM forwarding is intact and audited, and the change is small and verified.

1. Why this is different from utm_source=website

utm_source=website&utm_medium=<page> (removed in #8141) ref=prisma.io/<page> (this PR)
Parameter utm_source, a campaign parameter ref, the console's referrer field
When it fires On every Console link, for everyone Only when the visitor has no stored or current UTM, ref, or ad click ID, and the link carries none either
Can it override Google Ads, X, ChatGPT UTMs Yes, that was the bug No. A visitor with any attribution takes the existing first/last-touch path and the fallback never runs
PostHog $initial_utm_source and channel type Overwritten with website Untouched. PostHog derives those from UTM tags, referring domain, and ad click IDs only (channel type docs)
Where it lands in the console utm-source on the signup event, mixed in with real sources signup_last_ref and signup_first_ref, next to partner referrers (producthunt, laconsole.dev), prefixed prisma.io
What it says "source: website", which says nothing about acquisition "this signup converted from prisma.io/postgres", a landing-page fact

A report that groups signups by utm_source will never show "website" again. A report that wants the conversion page reads signup_last_ref instead, and a report that only wants partners filters out the prisma.io prefix.

2. The conversion page is worth recording, and nothing else captures it

"80% website, 10% CLI" would tell us nothing about marketing, agreed. "1,000 untagged signups a week convert from a prisma.io page, 59% of them from the homepage, and the rest go straight to the console" tells us the site is in the funnel at all, which page converts, and what the Website → Signup step in the Growth Funnel dashboard should be counting. That is the question this field answers. Channel attribution stays with UTMs.

console:user_signed_up by week:

Week Signups utm_source=website Initial referring domain = www.prisma.io No UTM at all
Jul 27 3,336 1,046 838 1,568
Aug 3 3,265 989 840 1,588
Aug 10 3,396 209 750 2,383
Aug 17 3,349 8 674 2,514
Sep 28 2,511 8 453 2,020
  • The step is the removed default, not privacy tools. Incognito, UTM strippers, and blockers existed on Aug 3 as much as on Aug 17. The no-UTM bucket moved by ~900 a week in the one week Drop default UTM params from console CTAs #8141 shipped and has stayed there. Those visitors did not start hiding; the site stopped saying anything about them.
  • The HTTP referrer recovers about half, and no page. In the four weeks before the change, 45% of website-tagged signups had www.prisma.io as the person's initial referring domain, 42% had none or $direct. Week by week the referrer now finds 450 to 500 of what used to be ~1,000, and it is sliding. The site sends Referrer-Policy: strict-origin-when-cross-origin, so the console only ever learns the origin, never which page.
  • Person stitching does not recover it either. Only about a third of website-tagged signups in the clean period had a linked pre-signup www pageview. Cross-domain identity depends on cookies and consent, and since Count non-consenting visitors with PostHog cookieless mode #8146 on cookieless hashing. The link is the one channel that does not depend on any of that, which is why this signal rides on the link.

3. UTM forwarding is intact and audited, and the change is small and verified

Where we do and do not pass the original UTMs

Checked, per the ask in the thread:

  • Every Console-bound navigation in apps/site, apps/blog, and packages/ui is an anchor. There are no window.location, router.push, or form posts to the console, so the document-level capture-phase listener in UtmPersistence sees every click: nav, heroes, pricing plans, CTA bursts, and the /login and /sign-up redirects.
  • At click time the handler merges the current URL's params with stored first and last touch. A visitor whose storage is blocked still forwards the UTMs on the page they are on.
  • Ad click IDs (gclid, msclkid, fbclid, and friends) are forwarded to the console alongside UTMs, so Google auto-tagged paid traffic is attributed even with no UTM at all.
  • The one case nothing on our side can fix is an extension that strips UTMs before the page loads. Those visitors arrive clean and, with this PR, reach the console with the page they converted from, which is the truth about them.
  • Docs still stamps utm_source=docs on its own console links. By the same rule that removed website, that should go too. Follow-up below.

Where the change lives

  • packages/ui/src/lib/utm.ts: buildSiteRef(pathname) and syncFallbackRef(url, ref), both pure and unit-tested.
  • packages/ui/src/components/utm-persistence.tsx: a new optional fallbackConsoleRef prop. In the click handler, if there is attribution it syncs as before. Otherwise, for console-bound links only, it stamps the fallback.
  • apps/site and apps/blog pass buildSiteRef. Docs is not opted in.

Relationship to #8141

Every change from #8141 stays in place:

  • The removed utm_source=website / utm_source=blog / utm_source=site defaults stay removed. Nothing here writes a utm_* value the visitor did not arrive with.
  • The removed utm_campaign=signup/login fallback in the shared WebNavigation builder is untouched.
  • The cta_click tracking it wired in is untouched: it reads the rendered href, not the rewritten one, same as for forwarded UTMs.

The one scenario from that PR that changes on purpose: a clean visit no longer reaches console.prisma.io/sign-up with no params. It reaches it with ?ref=prisma.io or ?ref=prisma.io/<page>. Every tagged scenario behaves exactly as before.

Verified

  • Unit tests: 3 new cases for the helpers, plus the existing UTM and attribution suites (15 passing).
  • Type checks for @prisma-docs/ui, site, and blog.
  • In the browser against a local build, probing the rewritten href at click time:
    • Clean visit to /postgres, click Log in or Get Started: ?ref=prisma.io%2Fpostgres, no UTMs.
    • Land on /?utm_source=x&utm_campaign=launch-test, click: UTMs forwarded with first_utm_source=x, no ref.
    • Then open /pricing untagged and click: stored UTMs still forwarded, no ref.
    • Land on /studio?ref=producthunt, click: ref=producthunt and first_ref=producthunt kept, not overwritten.

Out of scope

  • It does not bring back utm_source=website. Reports keyed on it stay empty by design.
  • It does not change anything for tagged visitors.
  • It does not fix www-to-console person stitching in PostHog. That is a separate cookie and consent problem, and this change is independent of it on purpose.

Follow-ups

  • Metabase: add signup_last_ref to staging.stg_posthog__user_signups and use it for the Website → Signup step and a "converting page" breakdown. Keep channel breakdowns on utm_source.
  • Any partner-referral view keyed on signup_last_ref should exclude values starting with prisma.io.
  • Remove the utm_source=docs default from docs console links and opt docs into the same fallback.

🤖 Generated with Claude Code

PR #8141 dropped the utm_source=website default from Console CTAs because
it froze "website" into organic visitors' $initial_utm_source. It was also
the only signal the site sent for visitors without campaign params, so
since Aug 11 the console cannot tell a signup that came from prisma.io
from one that typed console.prisma.io directly. Website-tagged signups
fell from ~1,000 a week to under 10.

Reintroduce the signal as a fallback `ref` instead of a UTM: the console
already stores `ref` on signups (signup_first_ref / signup_last_ref) and
PostHog does not treat it as a campaign parameter, so first-touch stays
clean. It only fires when the visitor has no attribution at all, and never
overrides a link that already carries some.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
blog Error Error Oct 8, 2026 12:09pm UTC
docs Ready Ready Preview Oct 8, 2026 12:09pm UTC
eclipse Ready Ready Preview Oct 8, 2026 12:09pm UTC
site Error Error Oct 8, 2026 12:09pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
handbook Skipped Skipped Oct 8, 2026 12:09pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 7e9a80e8-9707-4b0c-ba2b-584f0989a909
📥 Commits

Reviewing files that changed from the base of the PR and between c50c7eb and 6c0bcf6.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 2788d816-86d8-4e42-aeff-fc17a2df95dc
📥 Commits

Reviewing files that changed from the base of the PR and between d19a307 and c50c7eb.

📒 Files selected for processing (4)
  • apps/blog/src/components/navigation-wrapper.tsx
  • apps/blog/src/components/utm-persistence.tsx
  • apps/site/src/components/utm-persistence.tsx
  • packages/ui/src/components/utm-persistence.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/site/src/components/utm-persistence.tsx
  • apps/blog/src/components/utm-persistence.tsx
  • packages/ui/src/components/utm-persistence.tsx

Included review availability: This review used your included allowance. 3 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


Walkthrough

The site and blog wrappers now provide a fallback ref callback to the shared UTM component. For eligible Console-bound links without captured attribution, the component adds a ref value based on the current pathname.

Changes

Console link fallback refs

Layer / File(s) Summary
Fallback ref helpers and tests
packages/ui/src/lib/utm.ts, packages/ui/src/lib/utm.test.ts
buildSiteRef formats a site ref from a pathname. syncFallbackRef adds it only when the URL has no captured attribution or first_ query key. Tests cover root and non-root paths, eligible links, and links with existing attribution.
Shared click handling
packages/ui/src/components/utm-persistence.tsx
The shared component accepts an optional fallbackConsoleRef callback and uses it for Console-bound links when UTM attribution is unavailable. The click handler includes first-touch data for Console-bound links and redirects when attribution is available.
Site and blog wiring
apps/site/src/components/utm-persistence.tsx, apps/blog/src/components/utm-persistence.tsx, apps/blog/src/components/navigation-wrapper.tsx
The site and blog wrappers pass buildSiteRef. The existing storage key, base path, and proxied paths remain unchanged. Blog navigation comments describe visitor-provided UTM handling and the fallback ref listener.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant UtmPersistence
  participant syncFallbackRef
  Browser->>UtmPersistence: Click Console-bound link
  UtmPersistence->>syncFallbackRef: Apply ref when attribution is absent
  syncFallbackRef-->>UtmPersistence: Return whether URL changed
  UtmPersistence-->>Browser: Continue link handling
Loading

Merge Risk: ⚪ Minimal · up to c50c7

Existing referral attribution remains unchanged when the fallback is applied; no concrete merge-blocking risk was identified in the reviewed changes.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 6 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: restoring signup attribution by adding a ref fallback to Console links.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch was successfully deployed

4 active and 1 inactive deployments
Preview – docs — 03d77c23 Deployed Oct 8, 2026 by vercel[bot]
Preview – eclipse — 03d77c23 Deployed Oct 8, 2026 by vercel[bot]
Preview – site — 03d77c23 Deployed Oct 8, 2026 by vercel[bot]
Preview – blog — 03d77c23 Deployed Oct 8, 2026 by vercel[bot]
Preview – handbook — 03d77c23 Deployed Oct 8, 2026 by vercel[bot]
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