Skip to main content
Menu
On this page

Capture UTM attribution

Store campaign parameters in hidden Fillo fields, keep them through product navigation, and treat them as respondent-supplied context.

Hidden fields let you store campaign query parameters beside a response without asking anyone to type them.

Add hidden fields

Code
const acquisition = defineForm({
  id: "demo-request",
  title: "Request a demo",
  pages: [{
    id: "request",
    blocks: [
      { id: "email", kind: "email", label: "Work email", required: true },
      { id: "utm_source", kind: "hidden", label: "UTM source", paramName: "utm_source" },
      { id: "utm_medium", kind: "hidden", label: "UTM medium", paramName: "utm_medium" },
      { id: "utm_campaign", kind: "hidden", label: "UTM campaign", paramName: "utm_campaign" },
      { id: "landing_path", kind: "hidden", label: "Landing path", paramName: "landing_path" },
    ],
  }],
});

A hosted form reads those parameters directly. For an embed later in a multi-page product flow, capture the approved values on arrival and pass them through initialData when the form mounts.

ts
const initialData = {
  utm_source: sessionStorage.getItem("utm_source") ?? "",
  utm_medium: sessionStorage.getItem("utm_medium") ?? "",
  utm_campaign: sessionStorage.getItem("utm_campaign") ?? "",
  landing_path: sessionStorage.getItem("landing_path") ?? location.pathname,
};

Decide when attribution expires and whether first-touch or last-touch wins. Don't collect arbitrary query parameters or personal data because they happen to be available.

Interpret it correctly

Visitors, link shorteners, and referrers can change or strip UTM values. They're respondent-supplied analysis context, not proof — don't base commissions, billing, access, or security decisions on them. When the source is missing, use a documented direct or unknown bucket instead of inventing one.

Try it

Start with a prompt and your coding agent builds the first form, staged for your review — or open the editor.

Updated

Was this page helpful?