Form Attribution
A lightweight, zero-dependency script that automatically captures UTM parameters, ad click IDs, and referrer data and passes them into your forms as hidden fields.

Try the Script Builder | View Documentation
Features
- Zero dependencies - Self-contained script using only browser APIs
- Automatic capture - Captures UTM parameters, referrer, landing page, and timestamps
- Persistent storage - Stores attribution data across page visits with smart fallbacks
- Form injection - Injects hidden fields into all forms automatically
- Dynamic form support - Detects and injects into dynamically added forms via MutationObserver
- First-touch attribution - Preserves initial attribution data across subsequent visits
- Privacy-respecting - Honors Global Privacy Control (GPC) and Do Not Track (DNT) signals
- XSS-safe - Sanitizes all values before injection
Installation
CDN
<script src="https://cdn.jsdelivr.net/npm/form-attribution@latest/dist/script.min.js"></script>
Quick Start
Add the script to your HTML 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 UTM parameters from the URL
- Store them in sessionStorage
- Inject hidden fields into all forms on the page
Parameters Captured
URL Parameters (default)
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)
landing_page | First page URL visited |
current_page | Current page URL (when form is submitted) |
referrer_url | Document referrer |
first_touch_timestamp | ISO 8601 timestamp of first visit |
Click ID Parameters (when data-click-ids="true")
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 using data-* attributes on the script tag:
<script src="/dist/script.min.js"
data-storage="sessionStorage"
data-field-prefix="attr_"
data-extra-params="gclid,fbclid"
data-exclude-forms=".no-track"
data-debug="true">
</script>
Options
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 |
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":
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 fallback when storage is unavailable:
localStorage | localStorage → sessionStorage → cookie → memory |
sessionStorage | sessionStorage → cookie → memory |
cookie | cookie → memory |
Privacy
The script respects user privacy preferences:
- Global Privacy Control (GPC) - Disables tracking when
navigator.globalPrivacyControl is 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.
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
Setup
pnpm install
Commands
pnpm test
pnpm exec playwright test --ui
pnpm check
pnpm fix
Browser Support
The script uses standard browser APIs with graceful fallbacks:
- URL API for query parameter parsing
- MutationObserver for dynamic form detection
- Web Storage API (sessionStorage/localStorage)
- CookieStore API with legacy
document.cookie fallback
License
Apache-2.0
Built by Ben Sabic | GitHub