Docs

Custom events and goals

Signups, downloads, outbound clicks, form submissions. Send them from your own code, or declare them in your markup and write no JavaScript at all.

Verified against the shipping code, September 2026

Sending an event from JavaScript

One function, on the global window.aa. The first argument is the event name; everything else is optional.

The simplest possible event
window.aa('Signup');

If you call it before the script has loaded, the calls are lost unless you added the loader stub described in the install guide. The stub queues them and replays them in order the moment the real script arrives, so the safe pattern is to include it on any site whose own code sends events during page load.

Attaching properties

Pass props to describe the event. Values may be strings, numbers or booleans.

An event with properties
window.aa('Signup', {
  props: {
    plan: 'growth',
    billing: 'annual',
    referred: true,
  },
});

Never put anything personal in a property. A property is stored as you send it, and an email address or a user id in one turns a cookieless, non-identifying dataset into personal data — which puts your consent obligations straight back where they were. If you find yourself wanting a user id here, the honest answer is that this is the wrong product for that question.

Knowing whether it was sent

Pass a callback and you get told what happened. This is the only way to see that an event was deliberately suppressed rather than lost, which makes it the fastest way to debug an install that looks silent.

Reading the result of a send
window.aa('Signup', {
  callback: (result) => {
    // { status: 202, ignored: null }         sent
    // { status: undefined, ignored: 'local' } suppressed, and why
    console.log(result);
  },
});

ignored is null on a successful send and otherwise names the reason: local for a local address, self-excluded for a browser carrying the self-exclusion flag, opted-out for a visitor who has opted out, and excluded or not-included for a path filtered by your path rules. A network failure reports error.

Declaring events in your HTML

Most events are a click on a button or a link, and writing a click handler for each one is tedious. The tagged-events build lets you declare them in the markup instead. Load it, and no JavaScript of yours is involved at all.

Load the build that reads tagged events
<script defer data-domain="example.com"
  src="https://cdn.absolutelyanalytics.com/aa.tagged-events.js"></script>
A tagged button
<button data-aa-name="Signup" data-aa-plan="growth">
  Start the trial
</button>

That fires an event named Signup with the property plan set to growth. The rule is simple: data-aa-name is the event name, and every other data-aa-* attribute becomes a property whose key is the attribute name with data-aa- removed.

Property keys arrive lowercase, because HTML lowercases attribute names before your script ever sees them. data-aa-planTier becomes the property plantier, not planTier. Use hyphens — data-aa-plan-tier gives you the key plan-tier, which is at least readable.

The class-based form

Some systems will not let you add arbitrary attributes to an element — a page builder, a CMS field, a component library with a locked-down prop list — but almost all of them let you add a class. The same events can be declared as classes.

The same event, declared as classes
<button class="aa-event-name--Signup aa-event-plan--growth">
  Start the trial
</button>

-- and = are interchangeable separators, so aa-event-name=Signup is the same thing. Because a class cannot contain a space, a + in the value becomes a space: aa-event-name--Newsletter+signup fires Newsletter signup. Nothing else is decoded, so a value that needs a literal + has to use the attribute form instead.

What counts as a click

  • A left click and a middle click both fire. A right click does not — opening a context menu is not a conversion.
  • A form submit fires, so tagging the <form> itself is the right way to measure a submission rather than tagging its button.
  • The script looks at the clicked element and up to three ancestors above it. Tag a wrapper and a click on the icon inside it still counts.
  • An inner tag wins over an outer one for the same key, so a shared wrapper can supply defaults that individual children override.
  • Nothing fires without a name. Properties on their own are inert, which is usually the cause when a "tagged event is not working".

If you also use session replay or heatmaps, note that their masking attributes share the data-aa- prefix. Putting data-aa-mask on a tagged element, or within three ancestors of one, will also record it as an event property. Keep masking attributes off elements you have tagged.

Events you do not have to write

Two categories are common enough that they ship as builds rather than as instructions.

Outbound links

The outbound-links build fires an event named Outbound Link: Click whenever a visitor clicks a link to another host, with the destination in a url property. Anything that is not http or https is ignored, so mailto: and tel: links do not fire. A different subdomain counts as outbound.

File downloads

The file-downloads build fires File Download, again with the destination in url, when a visitor clicks a link whose extension looks like a file. It does not check the host, so a download from your own domain counts — and a link to a PDF on someone else's domain fires both events if you have both builds loaded.

Both, on one tag
<script defer data-domain="example.com"
  src="https://cdn.absolutelyanalytics.com/aa.outbound-links.file-downloads.js"></script>

Turning an event into a goal

An event on its own appears in your events report. Making it a goal is what gives you a conversion rate and lets you see which sources, campaigns and countries actually convert. Goals come in two kinds:

  • Event goals match on the event name — Signup, exactly as you sent it.
  • Pathname goals match a page by path pattern, so /thank-you or /checkout/** becomes a conversion with no code at all. * matches within one path segment and ** crosses /.

An event goal can also name a currency, which is what turns conversions into money — see revenue tracking.

Once a goal exists you can filter and group by it through the Stats API using the event:goal dimension, which is also the thing the conversion_rate metric requires.

Scroll depth and engaged time

The engagement build measures two more things about a page: how far down it anyone got, and how long they spent actually looking at it. Engaged time only accrues while the tab is visible and focused, so a page left open in a background tab for an hour contributes nothing.

Both are sent as part of a small periodic beacon rather than by polling, and that beacon is deliberately marked non-interactive so that it can never turn a bounce into a non-bounce. Your bounce rate does not move because you loaded this build.

Honest limitation: the scroll_depth and time_on_page metrics in the Stats API return null today, with a warning saying so. The data is being collected; the aggregate is not yet queryable. Scroll behaviour is currently readable through the scroll maps, on Growth and above.

Common questions

Do custom events count against my plan allowance?

Yes. Your allowance counts pageviews and custom events together. Bots and AI crawlers are filtered out before counting, so you are never billed for them.

That is the same unit both incumbents meter on, and we are not going to describe it as generous. If you instrument a checkout flow heavily, work out the event volume before you pick a rung — the pricing page has the ladder.

Why is my tagged event not firing?

Four things, in the order they go wrong. One: you are on the base build, which does not contain the tagged-events parser — the attributes are ignored entirely, silently. Two: you set properties but no name; an event with no name never fires. Three: the element carrying the name is more than three ancestors above the thing that was clicked. Four: it was a right click, which is not a click as far as the script is concerned.

Can I see a breakdown by my custom properties?

Sending them works on any plan. Querying by them is a Business-tier capability, in the dashboard and in the Stats API alike. That is the incumbent's gating reproduced, and it is worth knowing before you instrument around it rather than after.

Does tracking a link slow the click down?

No, and this is deliberate. The script never cancels a navigation to squeeze a request in first. The event is sent with a keepalive request that survives the page being torn down, so the link behaves exactly as it would with no analytics on the page. The trade is that on a genuinely broken network the odd click will be lost, which we consider the right way round.

What is the difference between an event and a goal?

An event is a thing that happened. A goal is an event you have told the dashboard to treat as a conversion, so it gets a conversion rate and shows up against sources, campaigns and countries. Goals come in two kinds: one that matches an event by name, and one that matches a page by path pattern — a thank-you page makes a fine goal without writing any JavaScript at all.