form-attribution
v2.5.1
Published
Automatically capture and persist marketing attribution data in your web forms.
Maintainers
Readme
Form Attribution
A lightweight, zero-dependency script that automatically captures and passes the referrer, UTM parameters, ad click IDs, and more to your forms as hidden fields.
Try the Script Builder | View Documentation
Features
- Zero dependencies - Runs entirely on native browser APIs with no external libraries
- Automatic capture - Records UTM parameters, referrer URL, landing page, timestamp and more without manual setup
- Persistent storage - Maintains attribution data across sessions using intelligent storage fallbacks
- Form injection - Automatically adds hidden fields to every form on the page
- Dynamic form support - Monitors the DOM via MutationObserver to handle forms added after page load
- First-touch attribution - Retains original attribution data even when users return later
- Privacy-respecting - Complies with Global Privacy Control (GPC) and Do Not Track (DNT) preferences
- XSS-safe - Sanitizes all injected values to prevent cross-site scripting attacks
- Debug panel - Visual overlay for inspecting attribution data, forms, and activity in real-time
- JavaScript API - Programmatic access via
window.FormAttributionfor custom integrations
Quick Start
Add the script to your website before the closing </body>tag:
<script src="https://cdn.jsdelivr.net/npm/form-attribution@latest/dist/script.min.js"></script>That's it! The script will automatically:
- Capture common URL parameters and metadata (e.g. landing page)
- Store the data in the user's browser temporarily
- Inject hidden fields into all forms on the page
Parameters Captured
URL Parameters (default)
| Parameter | Description |
|-----------|-------------|
| utm_source | Traffic source (e.g., google, newsletter) |
| utm_medium | Marketing medium (e.g., cpc, email) |
| utm_campaign | Campaign name |
| utm_term | Paid search keywords |
| utm_content | Content variant for A/B testing |
| utm_id | Campaign ID |
| ref | Referrer tracking parameter |
Metadata (automatically captured)
| Parameter | Description |
|-----------|-------------|
| landing_page | First page URL visited |
| current_page | Current page URL (where form was submitted) |
| referrer_url | Document referrer |
| first_touch_timestamp | ISO 8601 timestamp of first visit |
Click ID Parameters (when data-click-ids="true")
| Parameter | Platform |
|-----------|----------|
| gclid | Google Ads |
| fbclid | Meta Ads |
| msclkid | Microsoft Advertising |
| ttclid | TikTok Ads |
| li_fat_id | LinkedIn Ads |
| twclid | Twitter/X Ads |
Configuration
Configure the script by adding optional data attributes to the script tag:
<script src="/dist/script.min.js"
data-storage="localStorage"
data-field-prefix="attr_"
data-extra-params="gclid,fbclid"
data-exclude-forms=".no-track"
data-debug="true">
</script>Options
| Attribute | Default | Description |
|-----------|---------|-------------|
| data-storage | sessionStorage | Storage method: sessionStorage, localStorage, or cookie |
| data-field-prefix | "" | Prefix for hidden field names (e.g., attr_ creates attr_utm_source) |
| data-extra-params | "" | Comma-separated list of additional URL parameters to capture |
| data-exclude-forms | "" | CSS selector for forms to exclude from injection |
| data-storage-key | form_attribution_data | Custom key name for stored data |
| data-debug | false | Enable console logging and debug panel |
| data-privacy | true | Set to "false" to disable GPC/DNT privacy signal detection |
| data-click-ids | false | Set to "true" to automatically capture ad platform click IDs |
Cookie Options
When using data-storage="cookie":
| Attribute | Default | Description |
|-----------|---------|-------------|
| data-cookie-domain | "" | Cookie domain (e.g., .example.com) |
| data-cookie-path | / | Cookie path |
| data-cookie-expires | 30 | Expiration in days |
| data-cookie-samesite | lax | SameSite policy: lax, strict, or none |
Usage Examples
Use localStorage for Longer Persistence
<script src="/dist/script.min.js"
data-storage="localStorage">
</script>Use Cookies for Cross-Subdomain Tracking
<script src="/dist/script.min.js"
data-storage="cookie"
data-cookie-domain=".example.com"
data-cookie-expires="90">
</script>Exclude Specific Forms
<script src="/dist/script.min.js"
data-exclude-forms=".login-form, [data-no-attribution]">
</script>Add Field Prefix for CRM Compatibility
<script src="/dist/script.min.js"
data-field-prefix="lead_">
</script>Script Builder
Use the interactive Script Builder tool to generate a configured script tag with a visual interface.
Storage Fallback Chain
The script uses intelligent fallbacks when a storage type isn't available:
| Requested | Fallback Chain |
|-----------|----------------|
| localStorage | localStorage → sessionStorage → cookie → memory |
| sessionStorage | sessionStorage → cookie → memory |
| cookie | cookie → memory |
Privacy
By default, the script respects user privacy preferences:
- Global Privacy Control (GPC) - Disables tracking when
navigator.globalPrivacyControlis true - Do Not Track (DNT) - Disables tracking when DNT is enabled
When privacy signals are detected, no data is captured or stored. You can override this behavior by setting data-privacy="false" on the script tag.
JavaScript API
Form Attribution exposes a global FormAttribution object for programmatic access:
// Get all attribution data
const data = FormAttribution.getData();
// Get a specific parameter
const source = FormAttribution.getParam('utm_source');
// Get tracked forms with their status
const forms = FormAttribution.getForms();
// Clear all stored data
FormAttribution.clear();
// Re-inject data into forms
FormAttribution.refresh();
// Register event callbacks (supports multiple listeners)
FormAttribution.on('onReady', ({ data, config }) => {
console.log('Attribution ready:', data);
});
// Remove a callback
FormAttribution.off('onCapture', myHandler);Available Methods
| Method | Returns | Description |
|--------|---------|-------------|
| getData() | Object\|null | Get all captured attribution data |
| getParam(name) | string\|null | Get a specific parameter value |
| getForms() | Array | Get list of forms with their status |
| clear() | void | Clear all stored attribution data |
| refresh() | void | Re-inject data into all forms |
| on(event, cb) | Object | Register event callback (chainable) |
| off(event, cb) | Object | Unregister a callback (chainable) |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| onReady | { data, config } | Fired when initialization is complete |
| onCapture | { data } | Fired when new data is captured |
| onUpdate | { data } | Fired when data is updated |
Debug Panel
Enable the debug panel by adding data-debug="true" to the script tag:
<script src="/dist/script.min.js" data-debug="true"></script>The debug panel provides:
- Data Tab - View all captured UTM parameters and metadata
- Forms Tab - See all forms and their injection status (click to highlight)
- Log Tab - Real-time activity log with timestamps
- Actions - Copy data to clipboard, clear storage, refresh forms
The panel is draggable, collapsible, and its state persists across page reloads. Uses Shadow DOM for style isolation.
Note: Remove
data-debugbefore deploying to production.
Injected Fields
Hidden fields are injected with the following attributes:
<input type="hidden"
name="utm_source"
value="google"
data-form-attribution="true"
data-form-attribution-managed="true">- Existing hidden fields with matching names are updated (no duplicates created)
- User-visible form fields are never modified
- All values are HTML-entity encoded for XSS protection
Development
Prerequisites
- Node.js
- pnpm
Setup
pnpm installCommands
pnpm test # Run Playwright tests (Chromium, Firefox, WebKit)
pnpm exec playwright test --ui # Run tests with interactive UI
pnpm check # Lint with Biome
pnpm fix # Auto-fix lint issuesBrowser Support
Built on standard browser APIs with graceful fallbacks for broad compatibility:
- URL API — Parses query parameters
- MutationObserver — Detects dynamically added forms
- Web Storage API — Persists data via sessionStorage and localStorage
- CookieStore API — Falls back to
document.cookiefor older
Documentation
Complete documentation is available at https://form-attribution.flashbrew.digital/docs.
License
Built by Ben Sabic at Flash Brew Digital | GitHub
