Attribution and hidden fields
Every response records where the person came from, so a form linked from twenty pages tells you which page sent them, and a campaign isn't lost when the visitor browses before filling it in:
| Stored as | Example | |
|---|---|---|
| The page with the form (origin and path, no query string) | meta.pageUrl |
https://acme.com/demo |
| The page on your site that led to the form, or the other site that linked straight to it | meta.referrer |
/pricing, or https://news.ycombinator.com |
| The first page of the visit, when it isn't the form's page | meta.landingPage |
/blog/launch |
| The other site the visit arrived from, when the form's page wasn't the first | meta.entryReferrer |
https://www.google.com |
| Campaign tags from the form's page, else from the landing page | meta.utm |
{ "source": "google", "medium": "cpc" } |
It shows on each response (Came from, Landing page, Visit came from), in Slack and email notifications ("From /pricing · via google / cpc · landed on /blog/launch"), in Insights (Where people come from, Pages that led here), in the CSV and API exports (page_url, referrer, landing_page, entry_referrer, utm_*), and in webhooks and database rows (meta).
Record it on every page (optional)
A page with a form records it by itself. To also know the pages a visitor saw before reaching the form (which page sent them, where the visit started), add KibbleAttribution once to the root layout. It's optional: a choice for the site owner, since it runs on every page. It takes no props and renders nothing, so a Next.js server layout can render it:
// app/layout.tsx
import { KibbleAttribution } from 'kibbleforms';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<KibbleAttribution />
{children}
</body>
</html>
);
}
Outside React, call startAttribution() from kibbleforms/client once per page load. Without either, responses still carry the form's own page, the site that linked straight to it and its campaign tags.
Privacy
- Other sites are recorded as their origin only (
https://www.google.com), never their path or query, which can hold search terms or tokens. Pages on your site are recorded as paths, without query strings. Kibble's API trims whatever a client sends to this, including older versions that send the fulldocument.referrer. - The visit record lives in the visitor's own
sessionStoragefor that tab, until the tab closes. It's sent only with a submission. No cookies, nothing shared across sites. - Turn it off with
createClient({ projectId, attribution: false })inkibble/client.ts: responses then carry only what the form's page knows. - Consent. In the EU and UK, keeping data in a visitor's browser and sending it later can need consent under the ePrivacy rules, even without cookies, when it isn't strictly necessary for the form. The site owner decides. If your site asks for consent for analytics, treat attribution the same way: pass
attribution: falseuntil the visitor agrees, or leave it off. Mention Kibble in your privacy notice either way (what Kibble stores).
Hidden fields
A hidden field isn't shown; its value comes from the URL, like Typeform's hidden fields. Link to /demo?plan=pro&partner=acme and:
fields: [
{ key: 'plan', type: 'hidden', label: 'Plan' }, // ?plan=…
{ key: 'partner', type: 'hidden', label: 'Partner', prefill: { query: 'partner' } }, // any parameter name
{ key: 'source', type: 'hidden', label: 'Source', defaultValue: 'website' }, // a fixed value
]
A hidden field reads the parameter named after its key, or prefill.query. When the form's page doesn't have the parameter, it reads it from the landing page of the visit, so acme.com/?plan=pro → /pricing → /demo still fills plan (with KibbleAttribution in the layout). prefill.query works on visible fields too, and initialValues on <Form> sets values from code (it wins over the URL). Hidden values are stored and delivered like any answer.