Tracker.js
tracker.js is the browser-side part of Agent Analytics. Use it when you want page views, custom events, and client-side experiments without shipping a heavy SDK.
The API reference now treats GET /tracker.js as the script endpoint only. The setup and feature guide lives here.
Base snippet
Section titled “Base snippet”Add this before </body>:
<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="my-site" data-token="aat_..."></script>Required attributes:
data-project: your project namedata-token: the public project token (aat_*)
The tracker automatically sends an initial page_view with a sanitized URL and referrer, pathname, hostname, title, screen size, browser, OS, device, language, timezone, session count, days since first visit, and allowlisted UTM attribution. No cookies are required.
Privacy contract
Section titled “Privacy contract”Agent Analytics separates the default browser contract from explicit opt-ins:
| Area | Default behavior | Explicit opt-ins |
|---|---|---|
| Page views | One initial page_view is sent on load. url and referrer are sanitized to origin + pathname; query strings and fragments are not sent by the automatic URL fields. path is the current pathname. | SPA route listeners require data-track-spa="true". Manual virtual pages can be sent with aa.page(name) or aa.track('page_view', ...). |
| Attribution | Only utm_source, utm_medium, utm_campaign, utm_content, and utm_term are read from the query string. They are kept for the current session and first touch. Other query parameters are not collected automatically. | Add custom campaign fields yourself with aa.track(...) only when they are safe and intentional. |
| Browser fields | Browser family/version, OS, coarse device type, language, timezone, hostname, title, and screen size are included. The tracker does not use hard fingerprinting, dynamic script loading, eval, document.write, or form value collection. | Performance, Web Vitals, errors, scroll depth, 404s, downloads, form-submit metadata, outgoing links, and generic clicks are off until their data-track-* attributes are set. |
| Clicks and forms | Declarative data-aa-event clicks only send the event name and explicit data-aa-event-* attributes you add. | data-track-clicks="true" tracks link/button metadata but not visible click text. data-track-forms="true" tracks form metadata but not form values. |
| Identity | Anonymous IDs and session IDs are scoped to the project token/name in browser storage. Legacy unscoped keys are not migrated, so a new scoped anonymous ID may be created after upgrading. | aa.identify(userId, traits) is explicit-only. Use a stable app/account ID as userId; pass email only as top-level traits.email when you want server-side support lookup. |
| Email lookup | Email is not collected automatically and should not be used as the userId. | When traits.email is provided to identify, raw email is transmitted over HTTPS for that identify call so the server can compute a project-scoped HMAC lookup index. Raw email is stripped from event rows and profile traits by default. |
| Cross-domain identity | No link decoration occurs by default. | data-link-domains decorates allowed cross-domain links with a short _aa anonymous ID parameter and then removes it on the destination. |
| Consent and DNT | Tracking starts immediately unless you configure consent or DNT. | data-require-consent="true" buffers until aa.grantConsent(). data-do-not-track="true" respects the browser DNT signal. |
Automated traffic that reaches the tracker is filtered out of your normal analytics. Use Bot Traffic if you want to inspect those automated requests separately.
If your agent can edit code, ask it to add the snippet for you. If not, use First Project in 5 Minutes to create the project, get the snippet, and verify the first page view.
If you want to see these pieces in a working repository, start with the examples guide and the Agent Analytics examples repo.
Website analysis recommendations assume these automatic fields already exist. A good implementation_hint should use the smallest tracker capability that unlocks the decision: declarative attributes for simple intent, window.aa.track(...) for computed client state, and server-side tracking for durable outcomes like account creation.
Common options
Section titled “Common options”| Attribute | What it does |
|---|---|
data-link-domains="example.com" | Link anonymous identity across sibling domains or subdomains |
data-do-not-track="true" | Respect the browser Do Not Track signal |
data-heartbeat="15" | Measure active time on page while the tab is visible |
data-track-outgoing="true" | Track external link clicks as outgoing_link |
data-track-clicks="true" | Track <a> and <button> clicks as $click |
data-track-errors="true" | Capture uncaught JS errors and promise rejections as $error |
data-track-performance="true" | Add Navigation Timing metrics to page_view |
data-track-vitals="true" | Add Core Web Vitals to page_view |
data-track-downloads="true" | Track download link clicks as $download |
data-track-forms="true" | Track form submissions as $form_submit |
data-track-404="true" | Track 404 pages as $404 |
data-track-scroll-depth="true" | Add max scroll depth to page_view |
data-track-spa="true" | Listen for client-side route changes and send route-change page_view events |
data-require-consent="true" | Buffer events until consent is granted |
Example:
<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="my-site" data-token="aat_..." data-track-outgoing="true" data-track-performance="true" data-track-vitals="true" data-track-errors="true" data-track-scroll-depth="true" data-heartbeat="15"></script>Declarative events
Section titled “Declarative events”For simple click tracking, you usually do not need custom JavaScript. Add data-aa-event directly in HTML:
<button data-aa-event="signup_cta_clicked" data-aa-event-plan="pro"> Sign up for Pro</button>That fires a signup_cta_clicked event with { plan: "pro" }.
This is usually the easiest path for agents too. They can add attributes to existing markup instead of wiring onclick handlers or editing application code.
Reserve signup for the durable moment when an account is actually created. If a button only starts the flow, use an intermediate event like signup_started or signup_cta_clicked instead of treating the click itself as the completed signup.
Impressions
Section titled “Impressions”Track whether a section was actually seen:
<section data-aa-impression="pricing_table" data-aa-impression-plan="pro"> ...</section>When the element becomes visible, the tracker sends an $impression event.
window.aa API
Section titled “window.aa API”Use the JavaScript API when the event depends on runtime state:
window.aa?.track('checkout_started', { plan: 'pro' });window.aa?.set({ plan: 'pro', team: 'acme' });Useful methods:
aa.track(event, properties): send a custom eventaa.page(name): manually send a page viewaa.identify(id, traits): link anonymous behavior to a stable known user ID and update the private profile lookup index used for support journeysaa.set(properties): attach global properties to future eventsaa.experiment(name, variants): assign variants deterministically client-sideaa.grantConsent()/aa.revokeConsent(): manage consent mode
Authenticated apps: signup, login, and identify
Section titled “Authenticated apps: signup, login, and identify”For apps with accounts, the cleanest post-auth browser step is:
window.aa?.identify(account.id, { email: account.email, plan: account.plan, team: account.team,});Call aa.identify(account.id, traits) immediately after auth succeeds and before any other post-auth browser events. That stitches the current browser activity onto the same canonical user ID that your server should use. Use a stable app/user/account ID as the first argument, not an email address. Pass email only as top-level traits.email when you want later support lookup by email.
When traits.email is present, raw email is transmitted over HTTPS during identify so Agent Analytics can compute a project-scoped HMAC lookup index on the server. Raw email is stripped from event rows and profile traits by default; browser-side SHA-256 email hashing and email_hash payloads are no longer part of the public tracker contract.
Recommended event boundaries:
- Fire
signupexactly once when the account is created. Prefer the server-side account-creation path for this. - Fire
loginwhen an existing account finishes auth. Prefer the server-side auth callback or session-creation path for this too. - Use client-side events like
signup_started,signup_cta_clicked, orcheckout_startedfor earlier UI steps that happen before the account exists.
That separation keeps funnels honest and makes browser events and server auth events land on the same user_id.
SPA routing and virtual pages
Section titled “SPA routing and virtual pages”By default, Agent Analytics sends the initial page view only. Automatic SPA route tracking is now explicit opt-in because it listens for URL changes and patches history.pushState() / history.replaceState().
To enable automatic route-change page views, add data-track-spa="true":
<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="my-site" data-token="aat_..." data-track-spa="true"></script>With that opt-in, Agent Analytics tracks page_view when the browser URL changes through history.pushState(), history.replaceState(), popstate, or hashchange. That covers most client-side routers, including hash-based SPAs.
If your UI changes without changing the path, query string, or hash, the tracker does not try to guess that a new screen appeared. In that case, send a manual page_view.
Preferred implementation order:
- Real URL changes
- Hash routing
- Manual virtual
page_view
Use aa.page(name) when the current browser URL already matches the screen you want to label. It sends a page_view with a page property, but path and url still come from the current browser location.
For true virtual pages with no URL change, send a manual page_view and override the route fields yourself:
window.aa?.track('page_view', { page: 'Checkout Step 2', path: '/checkout/step-2', url: `${location.origin}/checkout/step-2`});Do not combine manual page tracking with router-driven transitions after you enable data-track-spa="true", or you will double-count the same screen change.
If you want a prompt-first workflow for deciding between router tracking and manual virtual pages, use SPA and Virtual Page Tracking.
Common recipes
Section titled “Common recipes”Cross-domain identity
Section titled “Cross-domain identity”Use data-link-domains when one visitor can move between related domains and should keep the same anonymous browser identity across that click.
<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="my-site" data-token="aat_..." data-link-domains="example.com,app.example.com,docs.example.com"></script>On click, the tracker decorates links to those domains with a short _aa query parameter. The destination tracker captures that anonymous ID, links it to the destination browser’s anonymous ID, and removes _aa from the address bar.
For projects that use the same project token, data-link-domains is enough. For separate Agent Analytics projects, add the projects to the same identity portfolio; otherwise the link is decorated, but cross-project reads stay project-local. See Identity portfolios for the CLI setup.
Typical portfolio flow:
discovery.example.com -> example.com -> app.example.comExample with separate projects in one portfolio:
agent-analytics portfolios create growth-system \ --name "Growth system" \ --projects example.com,discovery.example.comThen include the same portfolio domains on each participating surface:
<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="discovery-example" data-token="aat_..." data-link-domains="example.com,discovery.example.com,app.example.com"></script>Verify the destination project after a real click:
agent-analytics query example.com \ --metrics event_count,unique_users \ --filter '[{"field":"properties.referrer","op":"contains","value":"discovery.example.com"}]' \ --days 1Expected result: the destination events from that browser resolve to one unique_users value instead of fragmenting across the source and destination projects.
Privacy and consent
Section titled “Privacy and consent”<script defer src="https://api.agentanalytics.sh/tracker.js" data-project="my-site" data-token="aat_..." data-do-not-track="true" data-require-consent="true"></script>window.aa?.grantConsent();window.aa?.revokeConsent();When consent is granted, buffered events are flushed automatically. If the buffer contains more than 100 events, the tracker splits it into multiple /track/batch requests of 100 events or fewer.
Experiments
Section titled “Experiments”<h1 data-aa-experiment="hero_text" data-aa-variant-b="Try it free today!"> Start your free trial</h1>If you want the full prompt-first workflow for creating, wiring, QAing, and reading an experiment through your agent, use AI Agent Experiment Tracking.
Local development
Section titled “Local development”On localhost, the tracker switches to dev mode and logs activity to the browser console instead of sending production data. That keeps local testing out of your real analytics.