Makuhari Development Corporation
9 min read, 1661 words, last updated: 2026/1/21
TwitterLinkedInFacebookEmail

Introduction

When building a web service that needs to track where users come from before they register, you inevitably face a foundational architecture decision: should you persist attribution data in cookies or in localStorage?

Both approaches share the same end goal — capture campaign parameters like UTM tags and click IDs on arrival, then submit that data to your backend when the user completes registration. But the paths they take, and the risks they carry, are meaningfully different.

This post compares the two approaches across implementation, privacy constraints, and advertising ecosystem compatibility — and explains why the best production systems use both.


Criteria for Comparison

To evaluate these two approaches fairly, we will examine them across the following dimensions:

  • Write timing — when and how attribution data can be recorded
  • Scope and shareability — whether data is accessible across subdomains or requests
  • Persistence reliability — how likely the data survives until the user registers
  • ITP and browser privacy impact — how modern privacy features affect each approach
  • Trust model when submitted to backend — how verifiable the data is
  • Ad platform compatibility — what advertisers and ad networks actually care about

Option A: Cookies

How It Works

Cookies are written either server-side on the first request, or client-side via JavaScript after parsing the landing URL. Once set, they are automatically included in every subsequent HTTP request to your domain — no extra code needed.

A typical flow:

  1. User lands on /register?utm_source=google&utm_campaign=spring2026
  2. Server (or client JS) sets a cookie: attribution=source:google;campaign:spring2026; Expires=...
  3. User browses, returns, eventually registers
  4. Registration request automatically includes the cookie
  5. Backend reads and persists attribution

Pros

  • Zero-effort transmission: cookies ride along on every request automatically
  • Server-side write on first touch: you can capture attribution before any JavaScript executes
  • Cross-subdomain sharing: a cookie scoped to .example.com is readable by app.example.com, www.example.com, etc.
  • Well-understood by legacy ad infrastructure: older analytics platforms and affiliate networks were built around cookies

Cons

  • ITP (Intelligent Tracking Prevention) is severe: Safari, which holds significant market share particularly in Japan and mobile, aggressively shortens cookie lifetimes
    • Non-interaction cookies may expire in 7 days
    • In some configurations, as short as 24 hours
    • Even a Max-Age=365 cookie can be truncated by the browser
  • Registration gaps: if your users take more than a week to register after clicking an ad, your attribution cookie may already be gone
  • SameSite and Secure misconfiguration: cross-site cookies require SameSite=None; Secure, and misconfiguring this silently breaks attribution
  • Perceived as tracking: cookies carry a social and regulatory association with tracking, requiring integration with consent management in GDPR/APPI contexts
  • Size constraints: cookies have per-cookie and total-domain size limits; storing rich attribution JSON is impractical

What to Watch Out For

Keep tracking cookies separate from session and auth cookies. Store only a compact set of fields — source_type, campaign_id, and first_touch_ts. Do not store raw query strings.


Option B: Query Parameter Capture to localStorage

How It Works

When the user arrives, client-side JavaScript parses the URL query parameters — UTM tags, gclid, fbclid, etc. — and persists them in localStorage. This data survives page navigations and browser restarts, but lives entirely in the client.

A typical structured payload:

{
  "first_touch": {
    "utm_source": "google",
    "utm_campaign": "spring2026",
    "ts": 1737432000
  },
  "last_touch": {
    "utm_source": "email",
    "utm_campaign": "follow-up",
    "ts": 1737518400
  }
}

When the user registers, the form or API call explicitly reads localStorage and includes this payload in the request body.

Pros

  • Not subject to ITP cookie restrictions: localStorage does not share the same expiration mechanics as cookies
  • You control the lifecycle: data persists until explicitly cleared or the user wipes browser storage
  • Richer storage capacity: you can store structured JSON without size anxiety
  • Lower perceived privacy footprint: data is not automatically sent on every request

Cons

  • Requires JavaScript execution: if the user has JS disabled, or if your framework renders server-side before JS loads, the first touch may be missed
  • Cannot be read server-side directly: the backend only sees this data when the frontend explicitly submits it
  • Subdomain isolation: localStorage is strictly origin-scoped — app.example.com and www.example.com cannot share it without a bridging mechanism (cookie relay, postMessage, or redirect)
  • Vulnerable to overwrite: if you do not implement a clear attribution strategy (first-touch wins vs. last-touch wins), later visits may silently overwrite earlier, more valuable attribution
  • Fully client-controlled: the backend must treat this data as user-supplied and potentially unverifiable

What to Watch Out For

Whitelist only the parameters you actually need: utm_*, gclid, fbclid. Do not store raw referrer URLs — these can leak sensitive path information. Always include timestamps so you can validate attribution windows on the backend.


Comparison Table

Dimension Cookies localStorage
Write timing Server-side or client-side Client-side JS only
Automatic request inclusion Yes No
Cross-subdomain sharing Yes (with domain config) No
ITP / Safari impact Severe (7-day cap) Minimal
Persistence reliability Lower (browser may truncate) Higher (user-controlled)
Backend trust level Semi-trusted Weakly trusted (client-supplied)
Storage flexibility Low (size limits) High (structured JSON)
Ad platform requirement Not required Not required
Requires consent handling Often yes Often yes

On Ad Platforms: What Advertisers Actually Care About

This is one of the most commonly misunderstood aspects of attribution tracking. Ad platforms do not care whether you use cookies or localStorage. They cannot see either one.

What ad platforms need is one of two things:

1. A conversion pixel fired on the success page

// Google Ads example
gtag('event', 'conversion', {
  'send_to': 'AW-XXXXXXXXX/XXXXXXXXX'
});
 
// Meta example
fbq('track', 'CompleteRegistration');

This tells the platform "a conversion happened here." It is entirely independent of how you stored attribution data.

2. A server-side conversion API call

After your database confirms the registration was saved, your backend sends an HTTP call to the platform's conversion API (Google Enhanced Conversions, Meta CAPI, etc.) with the click ID and event details.

In both cases, your registration count comes from your own database. The ad platform never reads your cookies, never polls your website, and never automatically knows when a user registers. There is no mechanism by which writing document.cookie = "registered=true" increments anything on the advertiser's dashboard.

The role of cookies and localStorage in the ad ecosystem is narrower than most people assume:

They are not counters. They are connectors.

Their only job is to preserve the click ID (gclid, fbclid, etc.) from the moment of the ad click until the conversion event fires — so the platform can attribute that conversion to the original click. Whether you store that click ID in a cookie or in localStorage is an internal implementation choice that no ad platform will audit.

The only things that can break ad attribution are:

  • The click ID being lost before conversion fires (cookie expired, localStorage overwritten)
  • The conversion event not being sent at all
  • The conversion firing outside the attribution window (typically 7–30 days depending on platform)

Recommendation: Use Both in Production

For any system that needs long-term, auditable, ad-compatible attribution, the right answer is not a choice between cookies and localStorage — it is using both with a defined fallback hierarchy.

Step 1: Parse attribution parameters on landing

Extract utm_*, gclid, fbclid, and any custom parameters. Determine source_type (paid / organic / direct / referral).

Step 2: Dual-write on arrival

  • Write a compact cookie (backup): store only source_type, campaign_id, and first_touch_ts
  • Write a full JSON object to localStorage (primary): store the complete attribution context including click IDs and timestamps

Step 3: Submit unified payload at registration

{
  "attribution": {
    "source": "google",
    "campaign": "spring2026",
    "first_touch_ts": "2026-01-21T08:00:00Z",
    "last_touch_ts": "2026-01-24T14:30:00Z",
    "click_ids": {
      "gclid": "Cj0KCQiA...",
      "fbclid": null
    },
    "storage_source": ["localStorage", "cookie"]
  }
}

Step 4: Backend fallback logic

  • Prefer localStorage data (richer, more reliable)
  • Fall back to cookie data if localStorage is empty or malformed
  • Record storage_source in your attribution table for downstream auditability

Step 5: Fire conversion signals

After successful database write, trigger your conversion pixel and/or server-side API call to the relevant ad platforms. This is the only step that actually updates the platform's count.

Additional Backend Considerations

  • Implement first-touch-wins as the default policy; allow explicit last-touch override for specific campaign types
  • Validate that first_touch_ts is within a reasonable window (e.g., no more than 90 days before registration)
  • Record storage_source so you can later analyze data quality (localStorage-sourced attribution is generally more complete)
  • For multi-device journeys, attribution breaks regardless of storage mechanism — a logged-in user identity graph is needed for that case

Summary

Cookies localStorage
Best for First-touch capture, legacy compatibility, server-side read Long-lived persistence, structured data, ITP resistance
Main risk Expires before user registers (Safari/ITP) Overwritten, subdomain isolation
Ad platform role Click ID relay only Click ID relay only
Conversion counting Your DB + your conversion pixel Your DB + your conversion pixel
Production recommendation Use as fallback Use as primary

The mental model that clarifies everything:

Cookies exist to be understood by legacy systems. localStorage exists to survive the browser. Neither of them counts your registrations — your database does, and you tell the ad platform about it yourself.

If you are building a system meant to run for years with measurable ad ROI, dual-write from day one. The extra 10 lines of code will save you from invisible attribution gaps that are nearly impossible to diagnose retroactively.

Makuhari Development Corporation
法人番号: 6040001134259
ご利用にあたって
個人情報保護方針
個人情報取扱に関する同意事項
お問い合わせ
Copyright© Makuhari Development Corporation. All Rights Reserved.