Track custom events
Use window.muro.track() to record signups, purchases, CTA clicks, and any other moment that matters in your product.
Muro tracks pageviews and sessions on its own. For everything else, you fire a custom event from your code. There's exactly one API to learn.
The API
window.muro.track(eventName, props);eventName: a short string that identifies the action, like'signup'or'purchase'.props: an optional object with extra context, like the plan name or amount.
Two examples:
// A simple event with just a name.
window.muro.track('signup');
// An event with extra context.
window.muro.track('purchase', {
plan: 'pro',
amount: 19,
annual: true,
});That's the whole interface. No tracking plans, no event schemas to register, no SDK methods to chain.
Naming events
Stick to a few rules that pay off later when you're skimming a list of event names:
- Use lowercase with underscores or hyphens.
signup,cta_click,checkout-started. Pick one style and stick to it. - Use verbs or short noun phrases.
signup,purchase,trial_started,invite_sent. - Stay consistent across your app. Don't fire
signupin one place andSign Upin another. They count as different events. - Keep names short. The maximum length is 64 characters, but most useful event names are under 25.
Event properties
The props object is for the context you'd want to filter by later. Keep it lean.
| Rule | Limit |
|---|---|
| Allowed value types | Strings, numbers, booleans only |
| Maximum keys per event | 10 |
| Maximum serialized size | 2048 bytes |
Nested objects, arrays, null, and undefined are not allowed and will be rejected. If you need a list, pick one representative value (for example, the first item) or flatten the keys.
// Good. Flat, primitive values.
window.muro.track('checkout_started', {
plan: 'team',
seats: 5,
has_coupon: true,
});
// Won't work. Nested object is rejected.
window.muro.track('checkout_started', {
user: { id: 'u_123', plan: 'team' },
});Common recipes
These are the events most Muro users fire first.
Signup. Fire it once the account is actually created, not on form submit.
window.muro.track('signup', {
plan: 'free',
source: 'homepage',
});Purchase or upgrade. Fire it from your success page or post-payment webhook callback in the browser.
window.muro.track('purchase', {
plan: 'pro',
amount: 19,
});CTA click. For tracking which calls-to-action actually drive traffic.
document.querySelector('#hero-cta').addEventListener('click', () => {
window.muro.track('cta_click', { location: 'hero' });
});Trial started. If you have a separate trial flow.
window.muro.track('trial_started', { plan: 'pro' });TypeScript
window.muro is added by the tracking script at runtime, so TypeScript doesn't know about it by default. Add a one-time declaration to a global types file (for example, src/types/global.d.ts):
declare global {
interface Window {
muro: {
track: (
eventName: string,
props?: Record<string, string | number | boolean>,
) => void;
};
}
}
export {};Now window.muro.track('signup') is fully typed across your project.
A few good-to-knows
- Events fire even if the page unloads. Muro uses
navigator.sendBeacon, so an event fired right before navigation or tab close still gets delivered. - Order matters less than you'd think. Custom events are batched and written within a few seconds of being fired. Don't worry about ordering across very-fast events.
- You don't need to wait for
muroto be defined. As long as the snippet is in your<head>,window.murois available before any user-triggered code runs.
What's next
Install Muro on Next.js
Add Muro to a Next.js app using either the App Router or the Pages Router. Single-page navigation is tracked automatically.
The Dashboard
A tour of the Muro dashboard: KPI cards, the visitors chart, filters, time ranges, and the click-to-filter trick that ties them all together.