Autocapture
Capture clicks, form submits and input changes without manual instrumentation. Privacy-first defaults; full configuration.
Automatically capture clicks, form submissions, and input changes without manual instrumentation. Privacy-first by default — sensitive data is filtered and cookie consent banners are excluded from capture.
#Enable autocapture
Pass autocapture: true (or an AutocaptureConfig object) to vt.init to opt in. If you don't pass the option, autocapture is controlled by the Analytics → Autocapture toggle in your project settings — the SDK fetches that on boot via /decide and starts listening once it resolves.
vt.init('YOUR_PROJECT_TOKEN', {
api_host: 'https://www.vtilt.com',
autocapture: true,
})You can also start and stop autocapture programmatically at any time:
vt.startAutocapture() // takes precedence over the dashboard toggle
vt.stopAutocapture() // disables capture for the rest of the session
vt.isAutocaptureActive() // → boolean#Debugging "no $autocapture events"
If pageviews are flowing but $autocapture events are missing, call vt.getAutocaptureDiagnostics() from the browser console. It returns a structured snapshot of the enabled state, the inputs that drove the decision, and a disabledReason so you can see at a glance why nothing is being captured (waiting on /decide, dashboard toggle off, server opt-out, or vt.stopAutocapture() was called).
vt.getAutocaptureDiagnostics()
// → {
// isEnabled: false,
// isStarted: false,
// disabledReason: 'config_autocapture_undefined',
// inputs: { configAutocapture: undefined, isDisabledServerSide: false, ... }
// }For richer logs, pass log_level: 'info' to vt.init — the SDK emits [vTilt]:autocapture lifecycle messages (started, stopped, disabled reason) on every config change.
Why is this specific element not captured?
When clicks are coming through but a particular button or link still doesn't fire $autocapture, set log_level: 'debug' and reproduce the click. The SDK prints a one-line trace per skipped event with the rule that rejected it:
[vTilt]:autocapture skip css_selector_ignorelist_hit { tag: "button", text: "Continue", target }
[vTilt]:autocapture skip vt-no-capture class on element chain { target }
[vTilt]:autocapture skip synthetic event (isTrusted=false) { type: "click", target }
[vTilt]:autocapture captured $autocapture { tag: "BUTTON", text: "Sign up", href: "/signup" }The reason at the end of every skip line is one of:
| Reason | What it means |
|---|---|
no_window / no_element | The event has no DOM target (rare — usually a synthetic event). |
html_element / tag_html | Click landed on the <html> element itself, not a real interactive child. |
url_allowlist_miss | Current page URL didn't match autocapture.url_allowlist. |
url_ignorelist_hit | Current page URL is in autocapture.url_ignorelist. |
dom_event_not_allowed | Event type (e.g. mouseover) isn't in autocapture.dom_event_allowlist. |
element_allowlist_miss | Tag (e.g. <div>) isn't in autocapture.element_allowlist. |
css_selector_allowlist_miss | Element doesn't match any selector in autocapture.css_selector_allowlist. |
css_selector_ignorelist_hit | Element matches autocapture.css_selector_ignorelist (cookie banners by default). |
form_event_not_allowed | Form submit event for a tag that wasn't on the allowlist. |
input_event_not_allowed | Input change event for a non-capturable input. |
tag_not_capturable | Element tag isn't in the default capturable set (e.g. <span> outside a button). |
Combine that with the existing vt-no-capture and vt-sensitive skip lines to fully account for every click that doesn't make it onto the wire.
#Events captured
| Event | Trigger | Config |
|---|---|---|
$autocapture | Clicks on links, buttons, inputs and other interactive elements; form submissions; input changes. | autocapture: true (default) |
$rageclick | 3+ clicks within 30 px in under 1 second. | rageclick: true |
$copy_autocapture | Copy/cut text events. | autocapture.capture_copied_text: true |
$scroll_depth | User crosses a scroll-depth milestone on the current page. | autocapture.scroll_depth.milestones |
$pageleave can include $prev_pageview_scroll_depth_pct when autocapture.scroll_depth.pageleave is enabled (requires capture_pageleave).
#Scroll depth
Two independently opt-in modes under autocapture.scroll_depth (both default off):
| Mode | Config | What you get |
|---|---|---|
| Milestones | milestones: true or milestones: { thresholds: [50, 100] } | $scroll_depth once per threshold per page |
| Page leave | pageleave: true | Max scroll % on the page being left, on $pageleave |
// Milestones only — funnel-friendly
vt.init('YOUR_PROJECT_TOKEN', {
api_host: 'https://www.vtilt.com',
autocapture: { scroll_depth: { milestones: true } },
})
// Page-leave summary only — lower volume
vt.init('YOUR_PROJECT_TOKEN', {
api_host: 'https://www.vtilt.com',
autocapture: { scroll_depth: { pageleave: true } },
capture_pageleave: true,
})A passive scroll listener runs when either mode is on. State resets on each $pageview (including SPA navigations). Milestone events respect url_allowlist and url_ignorelist like other autocapture.
#Properties collected
| Property | Description |
|---|---|
$event_type | DOM event type (click, submit, change). |
$el_text | Visible text of the element (buttons, links, nested spans). |
$elements_chain | Encoded chain string of element and ancestors (tag, classes, attributes, position). |
$external_click_url | Destination URL when clicking an external link. |
$selected_content | Copied/cut text (with capture_copied_text). |
$copy_type | Clipboard event type (copy or cut). |
$el_value | Form field value on change (with capture_element_values, privacy-filtered). |
$selected_text | Human-readable selection text — dropdown option text, checkbox label. |
$element_selectors | Matched CSS selectors from action definitions. |
$scroll_depth_pct | Milestone threshold reached (milestones mode). |
$scroll_depth_max_pct | Max scroll depth % when the milestone fired. |
$prev_pageview_scroll_depth_pct | Max scroll depth % on the page being left (pageleave mode, on $pageleave). |
#Advanced configuration
Pass an object to autocapture for fine-grained control over which pages, elements, and events are captured.
vt.init('YOUR_PROJECT_TOKEN', {
api_host: 'https://www.vtilt.com',
autocapture: {
url_allowlist: ['https://example.com/app/.*'],
url_ignorelist: ['https://example.com/admin/.*'],
dom_event_allowlist: ['click', 'submit'],
element_allowlist: ['a', 'button', 'form'],
css_selector_allowlist: ['[data-track]', '.trackable'],
css_selector_ignorelist: ['[data-no-track]', '#admin-panel'],
element_attribute_ignorelist: ['data-secret', 'aria-label'],
capture_copied_text: true,
capture_element_values: true,
},
})| Option | Type | Description |
|---|---|---|
url_allowlist | (string | RegExp)[] | Only capture on pages matching these URLs/patterns. |
url_ignorelist | (string | RegExp)[] | Skip capture on pages matching these URLs (overrides allowlist). |
dom_event_allowlist | string[] | Limit to specific event types (click, change, submit). |
element_allowlist | string[] | Limit to HTML tags (a, button, form, input, select, textarea, label). |
css_selector_allowlist | string[] | Only capture elements matching these CSS selectors. |
css_selector_ignorelist | string[] | Exclude elements matching these selectors (defaults exclude cookie banners). |
element_attribute_ignorelist | string[] | Exclude specific HTML attributes from captured properties. |
capture_copied_text | boolean | Capture text on copy/cut events as $copy_autocapture. |
capture_element_values | boolean | Capture form field values on change (opt-in, default false). |
scroll_depth.milestones | boolean | { thresholds?: number[] } | Milestone $scroll_depth events (default off). |
scroll_depth.pageleave | boolean | Max scroll % on $pageleave (default off). |
scroll_depth.scroll_root_selector | string | string[] | Scroll container; defaults to window. |
#Privacy controls
Use CSS classes to control what gets captured. Privacy protections apply automatically — password fields, sensitive names (cc, ssn, etc.), and credit card / SSN patterns in values are never captured.
<!-- Prevent autocapture on this element and its children -->
<div class="vt-no-capture">
<button>This click will NOT be captured</button>
</div>
<!-- Mark element as containing sensitive data -->
<div class="vt-sensitive">
<input type="text" placeholder="SSN" />
</div>
<!-- Force capture on a specific element -->
<span class="vt-include">This text WILL be captured</span>| Class | Effect |
|---|---|
vt-no-capture | Prevent capture on this element and all children. |
vt-sensitive | Mark as sensitive; only tag name, id, and class are captured (no text or input values). |
vt-include | Force capture on an element that would otherwise be excluded. |
#Custom properties
Attach custom key-value properties to autocapture events using data-vt-capture-attribute-* HTML attributes.
<button
data-vt-capture-attribute-feature="signup"
data-vt-capture-attribute-variant="blue"
>
Sign Up
</button>
<!-- The autocapture event will include:
{ feature: "signup", variant: "blue", ...standard props } -->#Rage click detection
Detect frustrated users clicking repeatedly in the same area. Fires a separate $rageclick event.
vt.init('YOUR_PROJECT_TOKEN', {
api_host: 'https://www.vtilt.com',
autocapture: true,
rageclick: true,
})
// When a user clicks 3+ times within 30px
// in under 1 second, a $rageclick event fires.