Blog
The cookie that JavaScript cannot read is the cookie that ad blockers cannot delete. Trade-offs and patterns for getting it right.
A cookie set by your tagging server with the HttpOnly flag is not visible to client-side JavaScript. That sounds limiting until you realise it is exactly what you want for first-party identity persistence: ad blockers and tracking-prevention extensions cannot delete it through the document.cookie API, and any leak from a malicious script on your page cannot exfiltrate it.
If you need to read the cookie from JavaScript (most third-party analytics tools do), HttpOnly will silently break them. The classic example: GA4's _ga cookie cannot be HttpOnly because the gtag.js library reads it directly from document.cookie. Setting it to HttpOnly will not throw an error; it will just make the cookie invisible to the library.
In a Custom Template, the setCookie API takes an options object:
setCookie('_user_id', userId, {
domain: 'auto',
path: '/',
'max-age': 60 * 60 * 24 * 365,
secure: true,
httpOnly: true,
sameSite: 'lax'
});
SameSite=Lax is the right default for analytics cookies. Strict will break cross-domain navigation in some flows; None requires Secure and increases your exposure to CSRF unless you have other protections.
A pattern we see in production: keep one HttpOnly cookie for the canonical server-side identity, and one regular cookie for the same value duplicated for client-side use. The client-side one is what JavaScript reads; the HttpOnly one is what the server trusts. If the client cookie is missing, your tagging server can rehydrate it from the HttpOnly one.
This costs you a little storage and some complexity, but it survives most aggressive privacy configurations that delete client-readable cookies on a schedule.
Browsers cap first-party cookies set by JavaScript at 7 days (Safari ITP) or until the next storage cleanup (Firefox). Cookies set by the server via Set-Cookie headers are not subject to this cap and survive longer. Setting your identity cookies server-side gives you genuine multi-month persistence; setting them client-side gives you a week.