Tracking
What Flowtag records automatically, and how to capture your own events.
Once the tag is installed, Flowtag records six kinds of events. Five are automatic; the sixth is yours to send.
| Type | Recorded when | Configuration needed |
|---|---|---|
pageview | A session starts, or the path changes | None |
scroll | The visitor scrolls to a new depth | None |
click | The visitor clicks an element | None |
input | The visitor types into a field | None |
health | A JavaScript or resource error occurs | None |
event | You call flowtag.capture() | Yours |
Every event is stored with its session, timestamp, and the path it happened on, so any event can be attributed to a page and a traffic source.
Pageviews
The first pageview is recorded during the handshake, at page load. After that, the tag reports the current path with each ping, and the server writes a new pageview whenever the path differs from the last one recorded.
This means single-page apps work without extra code. The tag patches
history.pushState and history.replaceState and listens for popstate and
hashchange, so client-side navigations are detected as they happen. On a route
change, the tag flushes any queued events attributed to the old page, sets the
previous path as the new page's referrer, re-reads UTM tags from the new URL,
and immediately pings the new path rather than waiting for the next 6-second
tick.
Pageviews are keyed on window.location.pathname. Query strings and hash
fragments do not create separate pageviews, so /pricing?plan=pro and
/pricing are the same page. Only the six attribution
tags are read from the query string.
Scroll depth
Scrolling is sampled, not streamed. The tag debounces scroll events over a 1-second window and only reports a depth when it has moved by at least 2 percentage points, and the server additionally accepts at most one scroll event per session every 5 seconds.
Each scroll event carries the scroll percentage, the pixel offset, the viewport height, and the document height. Average scroll depth per page is shown in Page Discovery.
Clicks
Clicks are captured with aggressive throttling so that a rage-clicking visitor cannot flood your quota:
| Rule | Limit |
|---|---|
| Per unique element | 1 click per 5 seconds |
| Globally | 1 click per 2 seconds |
| Per page load | 50 clicks |
| Repeated clicks on the same element in a row | Ignored |
Each click records the element's tag name, its id, its classes, and the
cursor coordinates. If the click is on a link that navigates away, the tag
flushes the queue immediately so the event is not lost to the page unload.
Inputs
Input events record that a field was filled, never what was typed. Each
event carries the element's tag name, id, classes, name attribute, and
input type — no value, ever.
Inputs are throttled like clicks (1 per element per 5 seconds, 1 globally per 2 seconds, 50 per page load) and are only recorded once at least 3 characters have been entered, which filters out stray keystrokes.
The name and id attributes you give your fields are stored as-is. Avoid
putting personal data in them (id="[email protected]"), since that value
will appear in your event catalog.
Health events
The tag installs three error listeners and reports what they catch as health
events:
| Kind | Trigger | Payload |
|---|---|---|
unhandled_rejection | A rejected promise with no handler | Message and stack |
window_onerror | An uncaught JavaScript exception | Message and stack, or source:line:column |
resource_error | A <script>, <link>, or <img> that failed to load | The failing URL and element tag |
These are useful for spotting a broken deploy in the same timeline as your traffic — a spike in health events next to a drop in conversions usually explains itself.
Custom events
Call capture() with an event name and an optional flat object of properties:
flowtag.capture("signup_completed", { plan: "pro", trial: false });
flowtag.capture("button_clicked");From plain HTML the API is on window:
<script>
window.flowtag.capture("checkout_started", { cart_value: 49 });
</script>In a React or Next.js app, import the typed client instead — see
@flowtag/next.
Rules and limits
- The event name must be a non-empty string, and the payload must be a plain object (not an array).
- Events sent before the handshake completes are rejected. If you capture on page load, do it after the first paint or in an effect, not in a top-level module body.
- The tag throttles each event name to once per second, and the server accepts at most one event per session every 3 seconds.
- Property values are stored for filtering in the Event
Browser. Values longer than 256 characters are
truncated, and Flowtag keeps at most 500 distinct values per property — once
that ceiling is hit, further values are grouped as
(other).
Design event names as a small, closed set (signup_completed, not
signup_completed_pro_annual_2026). Put the varying parts in properties. This
keeps the catalog readable and keeps you under the distinct-value ceiling.
UTM tags and attribution
Six query parameters are read from the landing URL and stored on the session:
utm_source, utm_medium, utm_campaign, utm_term, utm_content, ref
They are re-read on client-side route changes, so a UTM-tagged link into a single-page app is attributed correctly. Any other query parameter is ignored.
The session's referrer is recorded alongside them:
| Value | Meaning |
|---|---|
| A URL | The external page the visitor came from |
<direct> | No referrer — typed URL, bookmark, or a stripped referrer |
<self> | The referrer was the same page |
How these feed the Referred and Organic metrics is covered in Metrics.
Controlling the tag at runtime
window.flowtag exposes the same API the SDK wraps:
| Method | Purpose |
|---|---|
capture(name, data?) | Send a custom event. |
consentCookies(allowed?) | Grant or revoke cookie consent. Called with no argument, it grants. |
getConsent() | Read the current consent state. |
init() | Start tracking manually when data-auto-init="false". |
stop() | Stop tracking for the rest of this page load. |
doNotTrackMe() | Toggle a persistent opt-out for this browser, stored in localStorage. |
getConfig(key?) / setConfig(key, value) | Read or change debug, gtagsync, trackerId, or baseUrl at runtime. |
doNotTrackMe() is the one to hand to your own team: call it once from the
console on a site you're developing and that browser stops sending data
permanently, until you call it again.
Debugging
Set data-debug="true" on the script tag (or run
flowtag.setConfig("debug", true)) to log the tracker ID, visitor ID, current
path, referrer, consent state, and captured tags to the console every 10
seconds, plus a line for every queued event and every envelope sent.
The tag also warns in the console when it detects localhost, so you'll notice
before test traffic lands in your production reports.