Yellow Theme

Next start · October 2026

Notes / 05

2026-09-12

Query parameters after the hash never arrive

Personalized links into a public intake form looked fine in a chat window and did nothing useful in the browser. Scroll missed the form. Prefill fields stayed blank. The tags we thought we were sending never left the address bar.

Thesis: /#form?utm=outreach&t=abc is not a query string. Everything after # is a fragment. The browser does not send it to the server, and location.search stays empty. Personalized links must be /?utm=outreach&t=abc#form.

What we shipped first

The intake form sits near the bottom of the page, anchored as #intake-form. We scrolled to it, copied the address bar, and pasted that URL into the campaign sheet. The bar already ended in the fragment. We appended ?utm_source=outreach&t=token after it.

The sheet looked ordered. The form id came first. The browser still treated the question mark as part of the fragment, not as a search string. The document request had no query string.

Empty fields look like a form bug. They were not. The parameters never left the address bar.

The working shape

Search first, fragment last: /?utm_source=outreach&t=token#intake-form. Generate every outreach link from one helper. Do not copy a scrolled address bar into the sheet.

function formUrl(query) {
  const qs = query.toString();
  return '/?' + qs + '#intake-form';
}

Concatenating the hash first and appending ? afterward is the bug, written as a helper. The test is the URL constructor: search is non-empty, and hash is #intake-form with no question mark inside it.

Checklist

How we deliver: methodology.

Engineering commentary only — not audit, legal, or certification advice.