# Custom events

> Track your own events with mrkr.track() or the declarative data-mrkr-event attribute, with the exact property limits, and why none of it is billed.

Section: Tracking. Canonical page: https://mrkr.app/docs/tracking/custom-events. Last updated: 2026-10-03.

The tracker records the visit for you: pageviews, outbound clicks, downloads, engaged time. Anything that happens inside your product is yours to name, with `mrkr.track()` or its declarative counterpart. Neither costs you anything: custom events do not count toward your plan's quota.

## mrkr.track()

Call it from your own code, anywhere after the script tag has loaded:

```js
window.mrkr?.track("signup_completed", { plan: "pro", placement: "landing_page" });
```

The first argument is the event name (truncated to 64 characters). The second is an optional flat object of properties. There's no need to declare an event before you send it: the first time an event name arrives, it registers itself and immediately shows up in your Events list.

> **Wait for the tracker:** `window.mrkr` becomes available when the tracking script executes. Optional chaining skips a call made too early; it does not create a pre-load queue. Send important events after the script's load callback or your framework's script-ready callback. Once the API exists, the tracker batches events automatically.

## The declarative attribute: data-mrkr-event

For clicks specifically, you can skip writing JavaScript entirely and annotate the element instead:

```html
<button data-mrkr-event="cta_click"
        data-mrkr-prop-variant="hero"
        data-mrkr-prop-plan="pro">
  Get started
</button>
```

This fires the same as calling `mrkr.track("cta_click", { variant: "hero", plan: "pro" })` on click. Each `data-mrkr-prop-*` attribute becomes one property; multi-word attribute names (`data-mrkr-prop-plan-tier`) come through as `plan_tier`, matching what you wrote in the HTML.

## Configure and explore an event

1. **Send a meaningful event**
   Call `window.mrkr.track()` after the tracker loads and after the action succeeds. For a signup, track the completed signup rather than every click on the form. Optional chaining avoids an error before the tracker loads but does not queue a missed event.

2. **Open Events**
   Your event appears automatically. Search for its captured name, then open its row. Set a readable display label and mark it as a conversion when it represents an outcome.

3. **Configure its value**
   For an event such as `order_completed`, set Revenue property to a numeric property such as `total`, and include `currency` in each payload. Leave it unset for nonmonetary events. Use one revenue event per purchase to avoid counting the same payment twice.

4. **Filter traffic**
   Choose Filter traffic in the event drawer, or select Event in Filters. Sources, Revenue, Visitors and the overview now show matching sessions. Multiple events use OR; events combine with source, campaign and other dimensions using AND.

A valued custom event:

```js
window.mrkr?.track("order_completed", {
  total: 49.00,
  currency: "USD",
  plan: "pro"
});
// In Events, mark order_completed as a conversion.
// Set Revenue property to total (major currency units).
// Send this OR mrkr.revenue() for the purchase, not both.
```

> **Source stays automatic:** Properties describe the event. A property named `source` does not replace the visit's traffic source: source attribution uses the session's measured referrer or campaign. Outbound links and downloads are already tracked automatically and grouped into their own catalog rows.

### Configure through your AI assistant

You can also [connect an assistant through MCP](https://mrkr.app/docs/integrations/mcp) and grant Configure custom events for this site. Ask it to read the current definition, then set a display label, conversion flag and revenue property. `configure_event` can declare a definition before its first occurrence; it changes configuration without sending a synthetic event. See the [exact tool inputs](https://mrkr.app/docs/integrations/mcp-tools#configure-event).

## Property limits

Properties are bounded on both the client and the server, so a stray payload can't blow up storage or the bytes your visitors upload:

| Limit | Value |
| --- | --- |
| Event name length | 64 characters |
| Properties per event | 8 |
| Property key length | 64 characters |
| Allowed key characters | letters, digits, `_`, `.`, `:`, `-` |
| Property value length (strings) | 256 characters |
| Total serialized properties size | 2,048 bytes |

- Booleans and finite numbers are stored as-is; `NaN` and `Infinity` are dropped.
- Strings longer than 256 characters are truncated, not rejected.
- Objects and arrays are JSON-stringified, then truncated the same way as a string: nested structures survive as a stringified blob, but they can't be broken down by key in the dashboard.
- `null` and `undefined` values are dropped entirely rather than stored as empty.
- If you send more than 8 properties, the ones that get dropped are chosen deterministically (by sorting keys alphabetically and keeping the first 8), so the same payload always keeps the same subset instead of a random one.

## Naming conventions

Event names and property keys become labels in your dashboard's breakdown UI, so treat them like you would a database column: lowercase, `snake_case`, and consistent across call sites (`signup_completed`, not `signup_completed` in one place and `SignupComplete` in another; those are two different events to mrkr). Avoid putting anything personally identifying in an event name or a property value; properties are for categories to group by (a plan tier, a variant, a step number), not free text.

> **Note:** Mark the events that matter as conversions in Events. A display label changes how the event reads; its captured name stays stable for instrumentation and filters. For monetary events, see [Revenue](https://mrkr.app/docs/tracking/revenue).
