Documentation

Goals, properties and scroll depth guide

Add a goal attribute to the button or link whose click you want to count:

<button data-af-goal="signup" data-plan="pro">Create account</button>
<a href="/checkout" data-af-goal="checkout" data-af-prop-variant="annual">Continue</a>

Clicks on children of the marked element also count. Each click or auxiliary click is a completion. If both goal-name attributes exist on the same element, a nonempty data-af-goal takes precedence. Goal names are trimmed and capped at 64 characters.

Exactly which attributes are collected

AttributeMeaning
data-af-goal="signup"Goal name; click goal unless the element also has data-af-scroll
data-ml-goal="signup"Legacy goal-name alias
data-plan="pro"Explicit shorthand for the plan property on a goal element
data-af-prop-KEY="VALUE"Explicit custom property, for example data-af-prop-variant="annual"
data-ml-goal-KEY="VALUE"Legacy property alias, for example data-ml-goal-plan="pro"
data-af-scroll="50"Scroll threshold for that element's goal; not a property

HTML normalizes attribute names to lowercase, so legacy data-ml-goal-PLAN produces the plan key. Property keys must start with an ASCII letter and contain only letters, digits, underscores or hyphens, up to 40 characters. Attribute values remain strings: "false", "12" and JSON-looking text are not parsed into other types. Built-in inherited object keys such as constructor are ignored by attribute collection.

The tracker takes at most 8 distinct properties, in DOM attribute order, and at most 64 characters per value. The first occurrence of a repeated property wins. If the serialized properties before transport redaction would reach 1,000 characters (for example, many escaped control characters), it sends the goal without properties. Existing URL redaction and ingestion's 1,000-character storage limit still apply; unusually expanded URLs can exceed that limit, so keep property values short. Invalid/truncated stored JSON is skipped safely by the report. Existing stored legacy primitive strings up to 255 characters remain reportable.

Only attributes on the marked goal element are collected. The tracker does not collect unrelated data-* attributes, form input values, text content, field names, ancestor properties or adjacent controls. Do not put email addresses, personal information or credentials in goal names or properties. Use the separate optional identify API only for identity your customer supplied.

Scroll goals

<main data-af-goal="article_half_read" data-af-scroll="50" data-af-prop-category="guides">
  <!-- article -->
</main>

The threshold is an integer from 1 to 100, without spaces, decimal points or percent signs. Depth is the bottom of the viewport divided by the full document height: (scrollY + innerHeight) / documentHeight * 100. The height is the larger of the body and document-element scroll heights. This measures document progress, not whether the goal element itself is visible. A short page can therefore reach its threshold immediately after loading.

Each marked element fires once per SPA page (pathname + search), when the current depth meets its threshold while the document is visible. Scrolls, resizes, page initialization, DOM insertion and visibility restoration check current depth through a coalesced 50 ms scheduler. New elements inserted after scrolling are checked too. Removing and reinserting the same element does not fire it again on that page.

Push/replace/pop navigation to a different page resets the element dedupe, including returning to a previous URL. Same-URL history updates, hash-only navigation and a manual pageview for the same URL do not reset it. Hidden documents never complete a scroll goal; they are checked at their current depth on becoming visible. A high depth observed only while hidden is not remembered.

Any element carrying data-af-scroll is excluded from click-goal tracking, including when its threshold is malformed. Outbound-link analytics and checkout attribution continue independently. Use a separate goal element if you need a click completion as well.

Code-triggered properties

window.analyticfast?.('goal', 'signup', { plan: 'pro', seats: 3, annual: true });
// Existing aliases remain available:
window.minilytics?.('goal', 'signup', { plan: 'pro' });
window.analyticfast?.('track', 'signup', { plan: 'pro' });

Optional calls before the deferred script loads are skipped. Call after readiness or use the browser package's queue. Programmatic calls retain the existing ingestion limits; send a small object of primitive values. Goal completions are not payments.

Inspect collected values

Open the website dashboard's Goals tab, then choose a goal and property in Goal properties. The report uses the dashboard's current date range, timezone, session filters, page/goal filters and saved country exclusions. Dates restrict event timestamps; page and goal filters match the event's session, just as in the existing goals breakdown. First/last payment-attribution selection does not change these event counts.

Completions count events and visitors count distinct visitor IDs. A person can contribute to several values, so do not add visitor rows to infer unique people. Primitive types stay separate ("1" versus 1, and "false" versus false). Explicit null and empty strings are labelled. Missing properties, nested objects, arrays, invalid/truncated JSON, nonfinite numbers and strings over 255 characters are excluded. No raw properties object, visitor identity or revenue is returned. Imported aggregate history does not contain reconstructible goal properties.

The browser-session endpoint is:

GET /api/sites/SITE_ID/goals/properties?goal=signup&property=plan&period=7d

It also accepts the existing dashboard period, from, to, granularity, model and JSON filters query fields. Omitting property selects the most frequent available key. The response contains goal, selected property, resolved range, properties (key/completions/visitors), values (value/type/completions/visitors), and explicit propertiesTruncated / valuesTruncated flags. It returns at most 50 keys and 100 values, ordered by completions with deterministic ties. Narrow the view when either list is truncated.

Workspace viewers, admins and owners can read their own websites while entitled. Bearer API tokens, share links and public widgets cannot access arbitrary custom properties through this endpoint. Inaccessible sites return 404, missing browser authentication 401, expired plans 402, and invalid goal/property names 400. Plan and tenant checks run inside the router, independently of parent route middleware. SQL identifiers come only from a closed column list; customer values and validated JSON paths use D1 prepared bindings (D1 documentation).

Questions, answered.

How do I use goals, properties and scroll depth with AnalyticFast?

Follow the Goals, properties and scroll depth instructions on this page using your own website’s Site ID and configuration. Verify a real event before relying on the reports.

Where can I get setup help?

Contact support@analyticfast.com with the website domain and a redacted description. Do not send API tokens, signing secrets or personal customer data.