MIRA FIVE

Track events

Record pageviews, custom events, revenue, clicks and site searches, and know the naming rules and limits every SDK applies.

An event is a named thing that happened, with optional properties. Browser SDKs record pageviews for you; you add custom events with track() from the browser or the server. This page covers the rules every SDK shares. For the request itself, see Send events.

Pageviews

Pageviews are automatic. The script tag records them by default, and the browser SDK records them with the pageviews() plugin. Both send $pageview for the landing page and for every same-document navigation, for any router: through the Navigation API, or else through pushState, replaceState and popstate.

  • The same path and query twice in a row is one pageview.
  • The title is read one macrotask after a navigation, so the router has set it.
  • After the first pageview, the referrer is the previous page of your site.
  • With hash routing (#/pricing), turn on hash mode: data-hash on the script tag, pageviews({ hash: true }) in the browser SDK. Hash changes then count as pageviews and the fragment stays in the URL.

To send pageviews yourself, turn the automatic ones off (data-manual, or pageviews({ initial: false }) for the landing page only) and call pageview():

<script defer src="https://cdn.mirafive.io/mira.js" data-key="mf_…" data-manual></script>
<script>
  mirafive('pageview', { url: '/checkout/step-2', title: 'Checkout: shipping' })
</script>

pageview() without an argument uses the current URL, document.title and the referrer.

Custom events

Call track(name, properties) where the thing happens:

<script>
  mirafive('track', 'signup', { plan: 'pro' })
</script>

Calls made before mira.js has loaded wait in the queue from the first line of the install snippet.

Browser events carry the current page, with its URL cleaned (see Page URLs). Server events carry a page only when you pass page: { url, title, referrer }.

Event names

A name has 1 to 128 characters and no leading or trailing whitespace. Names are labels such as signup or order completed: never put an email address, a name or other personal data in them. Pick one style and keep it, since signup and Signup are two events.

A leading $ is reserved for these names:

NameSent byCarries
$pageviewbrowser SDKspage. $boot: 1 when the consent answer was known when the page first drew
$autocapturebrowser SDKsthe clicked, submitted or changed element (see Autocapture)
$identifyidentify(), full mode onlyuserId and the person's traits as properties
$searchsite search, full mode onlyquery
$exposureflag reads, full mode only$experiment, $variant, see Experiments
$install_checksetup checksnothing; never stored or billed, see Verify and debug

The server refuses any other name that starts with $, and drops $identify, $search and $exposure from a consentless batch. In the browser, track() refuses every $ name; the SDK sends the reserved ones itself.

Properties

Properties are a JSON object. The server refuses a batch when one event breaks a limit, so every SDK checks them first:

LimitValue
Size32 KB, encoded as UTF-8 JSON
Values64 leaf values. A list or an empty object counts as one
Nesting5 levels
Key length128 characters

What happens to an event over a limit:

SDKBehaviour
Script tag, browser SDKThe event is dropped alone; a development warning names it ([mirafive] event dropped: …)
@mirafive/sdk-servertrack() reports an invalid_event error to onError; send() rejects with it
PHP, Laravel, Symfonytrack() and send() throw an InvalidArgumentException

Keep properties free of personal data: no email addresses, names, phone numbers or free text a person typed. Use your own pseudonymous ids.

In PHP, an empty array [] is sent as a JSON list. Pass new stdClass where you mean an empty object.

Revenue

Two property names have a meaning:

PropertyTypeMeaning
revenuenumberThe amount the event earned, for example 49.9
currencystringIts ISO 4217 code, for example EUR. Without one, the project's reporting currency applies

Revenue is summed per currency and never added across currencies. Send it on the event that confirms the money, after the payment succeeds:

<script>
  mirafive('track', 'order completed', { revenue: 49.9, currency: 'EUR', items: 2 })
</script>

Purchases are best sent from the server: a browser event is lost when the visitor has an ad blocker or closes the tab, and a server can send the order once, however often a webhook arrives. See Server-side events.

Typed events

In TypeScript, describe your events once and track() checks names and properties. The types add no code to the bundle. undefined makes the properties optional:

src/mira.ts
import { createMira } from '@mirafive/sdk-browser'
import { pageviews } from '@mirafive/sdk-browser/pageviews'

type Events = {
  signup: { plan: 'free' | 'pro' }
  'order completed': { revenue: number; currency: string }
  logout: undefined
}

export const mira = createMira<Events>({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  plugins: [pageviews()],
})

mira.track('signup', { plan: 'pro' })
mira.track('logout')

The same type works with new Mira<Events>() from @mirafive/sdk-server and useMira<Events>() from the React SDK.

Page URLs

Browser SDKs clean every page URL before sending it. The query keeps only these parameters, in their original encoding (names compared case-sensitively):

  • every utm_* parameter
  • ref, source
  • gclid, gbraid, wbraid, fbclid, msclkid, ttclid, li_fat_id

The fragment is dropped, unless hash mode is on; then it stays and a query inside it is cleaned the same way. A URL that does not parse loses everything from the first ? or #.

So https://shop.example/pricing?utm_source=news&email=max%40example.com#faq is sent as https://shop.example/pricing?utm_source=news.

Server SDKs send page.url as you pass it. Clean it the same way before you pass it.

Autocapture

Autocapture records clicks, form submits and changes as $autocapture events, in both modes. Turn it on with data-autocapture on the script tag, or the autocapture() plugin:

import { createMira } from '@mirafive/sdk-browser'
import { autocapture } from '@mirafive/sdk-browser/autocapture'
import { pageviews } from '@mirafive/sdk-browser/pageviews'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  plugins: [pageviews(), autocapture()],
})

It listens on links, buttons, input, select and textarea, and elements with the role button, link, tab or menuitem. Each event describes the element: $event_type (click, submit or change), $el_tag, $el_selector, $el_id, $el_classes, $el_text (the label of links and buttons, up to 128 characters), $el_href (cleaned), $el_name, $el_type, and $el_attrs with any data-testid, data-test, data-cy, data-qa or data-track attribute. Generated class names and ids are left out.

It never records what was typed. Password, email and hidden inputs are skipped. To exclude part of a page, mark it:

<div data-mira-no-capture>
  <button>Not recorded</button>
</div>

Site search records what visitors search for on your site as $search events. It needs full mode and statistics consent. Turn it on with data-site-search on the script tag, or the siteSearch() plugin:

import { createMira } from '@mirafive/sdk-browser'
import { identity } from '@mirafive/sdk-browser/identity'
import { pageviews } from '@mirafive/sdk-browser/pageviews'
import { siteSearch } from '@mirafive/sdk-browser/search'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  mode: 'full',
  plugins: [pageviews(), identity(), siteSearch()],
})

It reads the term from the q, s, search or query parameter of the page URL, before the URL is cleaned. Name your own parameters with siteSearch({ parameters: ['term'] }) or data-site-search="term". A term is sent once it has not changed for 1 second, or when the visitor moves on, so a live search that rewrites the URL per keystroke sends one event.

For a search that does not change the URL, call mira.search(query) or mirafive('search', query). The server lowercases every term and replaces email addresses and runs of six or more digits.

When events leave

Events are queued and sent in batches:

SDKA batch leaves
Script tag, browser SDK5 seconds after the first queued event, at 20 events, before a batch passes about 48 KB, and when the tab is hidden or the page is left
@mirafive/sdk-server1 second after the first queued event, or at 100 events. See Server-side events
PHP, Laravel, SymfonyAt 100 events, and when the request, job or command ends

In the browser, a batch sent while the page is hidden goes by navigator.sendBeacon. Call flush() (mirafive('flush')) to send the queue now. Browser options: flushAt (1–1000) and flushAfterMs (50–300000) on createMira.

A failed browser batch is tried up to three times in all, with backoff, honouring Retry-After up to 10 seconds. Every retry sends the same batch id, so the server stores it once.

On this page