# JavaScript API

> Every method on window.mrkr, the declarative data-attribute alternative, and what happens if you call it before the tracker has loaded.

Section: Reference. Canonical page: https://mrkr.app/docs/reference/javascript-api. Last updated: 2026-10-04.

Two ways to send a custom event: call a method on `window.mrkr`, or annotate an element with a `data-mrkr-event` attribute and let a click do it for you.

## Before you call anything

The install snippet loads the tracker with `defer`, and there is no stub or command queue waiting to replay your early calls: unlike some analytics snippets, `window.mrkr` simply does not exist until the deferred script has executed. An inline `<script>` placed after the snippet in your HTML runs immediately during parsing, before the deferred script has had a chance to run, so calling `window.mrkr.track(...)` from it will throw.

> **Guard the call, or call it later:** Either check `window.mrkr` exists before calling it, or make the call from your framework's own ready/mount lifecycle (a `DOMContentLoaded` listener, a React effect, a router hook) rather than from a plain inline script right after the tag. Both `window.mrkr` and the legacy alias `window.pulsewise` point at the same object once it exists.

## mrkr.track(name, props?)

```ts
mrkr.track(name: string, props?: Record<string, string | number | boolean | object>): void
```

`name` is truncated to 64 characters with no other validation. If you call `track()` with no name, or a falsy one, the call doesn't throw: the server receives an event with no name and records it as a plain pageview instead, which will misattribute the hit rather than surface an error.

`props` accepts a plain object with up to 8 keys. If you send more, the 8 kept are chosen by sorting all keys alphabetically and taking the first 8, not the order you wrote them. Keys longer than 64 characters are truncated; keys containing anything outside letters, numbers, `_`, `.`, `:`, `-` are dropped silently. String values truncate to 256 characters; booleans and finite numbers pass through unchanged; `null`, `undefined`, `NaN`, and `Infinity` are dropped; objects and arrays are JSON-stringified and then also truncated to 256 characters, so a large nested object can be cut off mid-JSON and become unparseable on read. `track()` returns nothing and does not return a promise: it queues the event, which is flushed with the rest of the batch about a second later, sooner if ten events are already waiting, and immediately when the tab is hidden or closed.

## mrkr.revenue(amount, props?)

```ts
mrkr.revenue(amount: number, props?: Record<string, string | number | boolean | object>): void
```

`amount` is coerced with `Number(amount) || 0`: a non-numeric value silently becomes 0 rather than throwing. The call sends an event named `revenue` with `amount` merged into `props`.

> **Don't put `amount` in props:** The merge order lets a `props.amount` key override the value you passed as the first argument. If your props object already has an `amount` field for some other reason, rename it: otherwise it silently replaces the amount you intended to send.

## mrkr.pageview()

```ts
mrkr.pageview(): void
```

Closes out the route you're leaving (flushing its final `page_leave` with duration, visible and engaged time), starts fresh timers, fires a pageview for the current URL. The tracker already calls this automatically on `history.pushState`, `history.replaceState`, `popstate`, and a bfcache restore, so most single-page apps never need to call it directly: reach for it only if your router doesn't go through the History API, such as an old-style `#/route` hash router.

## mrkr.sessionId

```ts
mrkr.sessionId: string
```

A live, read-only string getter for the current session. When tracking activity rotates the session after 30 minutes of inactivity, this property immediately reflects the new ID. Reading it does not keep a session alive. For checkout, use `checkoutMetadata()` to capture a fresh session at the time the customer starts paying.

## mrkr.checkoutMetadata()

```js
const metadata = window.mrkr?.checkoutMetadata() ?? {};
// { mrkr_session_id: "s_..." }
// Send metadata to your own checkout endpoint.
```

Returns only `mrkr_session_id`, refreshes an idle session if necessary and remembers the checkout's originating session in this tab for up to 24 hours. It reads no form values and creates no persistent cookie. Your server must pass the metadata to Stripe; this helper cannot change a Checkout Session already created elsewhere. See [Stripe attribution](https://mrkr.app/docs/tracking/revenue#stripe-payments).

Native same-origin POST form:

```html
<form method="post" action="/api/checkout" data-mrkr-checkout>
  <button type="submit">Checkout</button>
</form>
```

Only a same-origin POST form marked `data-mrkr-checkout` receives a hidden `mrkr_session_id` field. Native submission and `new FormData(form)` both capture the current session. Read that field on your server and pass it to Stripe as shown in the revenue guide. Other forms and their input values are untouched.

## Declarative attribute API

```html
<button data-mrkr-event="cta_click"
        data-mrkr-prop-variant="hero"
        data-mrkr-prop-plan="pro">
  Start free
</button>
<!-- equivalent to: mrkr.track("cta_click", { variant: "hero", plan: "pro" }) -->
```

`data-mrkr-event` sets the event name, capped at 64 characters like `track()`. Any `data-mrkr-prop-<key>` attribute becomes a property; camelCase-looking dataset keys are converted back to snake_case, so `data-mrkr-prop-plan-tier` becomes `plan_tier` in props. Attribute values are always strings: there's no client-side type coercion, unlike calling `track()` directly with a number or boolean.

> **It survives stopPropagation():** The tracker installs a single delegated click listener on the document in the **capture** phase, which runs before your page's own handlers. An element that calls `stopPropagation()` in its click handler, routine on SPA links, cannot suppress the declarative event. The same listener handles outbound and download links, so a `[data-mrkr-event]` element wrapping a link fires both the event you named and the link's own outbound or download event.

## Script tag configuration

| Attribute | Default | What it does |
| --- | --- | --- |
| `data-site` | None | Required. Your site id. |
| `data-cookies` | absent | Opt-in. `"on"` enables cookie mode (a one-year first-party `_pw_id`). Absent = cookieless = no cookie written or read. |
| `data-endpoint` | same-origin `/api/collect` | Override the ingest URL. |
| `data-replay-endpoint` | same-origin `/api/replay` | Override the replay ingest URL. |
| `data-mask-input` | `"1"` | Masks all typed input in session replay. Set `"0"` to disable, not recommended. |
| `data-autocapture` | no longer used | Retired in September 2026 when click autocapture was removed. Ignored by the tracker; safe to delete from your snippet. |
| `data-debug` | `"0"` | Set `"1"` to log tracker activity to the console. |

> **Note:** `data-mode`, `data-replay`, and `data-replay-sample` are read by older snippets but ignored today: replay and its sample rate are controlled entirely from Settings in the dashboard now. Tracking mode is set there too, but switching a site to cookie-based also means adding `data-cookies="on"` to the tag, because the cookie is written in the visitor's browser before any request reaches us.

See [custom events](https://mrkr.app/docs/tracking/custom-events) for when to reach for `track()` versus the declarative attribute, and [what mrkr records automatically](https://mrkr.app/docs/tracking/autocapture) for what's already captured without either.
