Script configuration
Reference for the Muro script, its attributes, the JavaScript API, what it sends and the limits each event must fit.
This page is the reference for muro.js: the attributes it reads, the API it exposes, what it records on its own, and the rules every event must pass. For a first install, start with All installation guides.
Script attributes
<script async src="https://api.muroanalytics.com/muro.js" data-project-id="YOUR_PROJECT_ID"></script>| Attribute | Required | What it does |
|---|---|---|
data-project-id | Yes | Your project's ID. Without it the script does nothing. Copy it from Settings → Project → Tracking script. |
async | Recommended | Downloads the script without blocking your page and runs it as soon as it's ready. defer also works. |
There are no other configuration options. Muro reads the first <script> on the page that has a data-project-id attribute, so don't give that attribute to any other script tag.
Load the script once per page. A second copy sends every event twice.
HTML element attributes
| Attribute | What it does |
|---|---|
data-muro-event | A click on the element, or on anything inside it, sends a custom event with the attribute's value as its name. |
<button data-muro-event="signup_click">Start free trial</button>- No JavaScript needed, and the event has no properties.
- Works for elements added after the page loads and across single-page app navigations.
- If elements are nested, the closest one with the attribute wins.
- Muro only reads the attribute. It never changes the element or its behaviour, and your own click handlers still run.
JavaScript API
Muro exposes one function:
window.muro.track(name, props);| Argument | Type | Notes |
|---|---|---|
name | string | Required. 1 to 64 characters. Case-sensitive: Signup and signup are different events. |
props | object | Optional. Up to 10 keys. Values must be a string, number or boolean. |
window.muro?.track('signup', { plan: 'pro', annual: true });window.muro exists only after the async script has run, and there is no queue for calls made before that. Use optional chaining (window.muro?.track(...)) so an early call or a blocked script never throws on your page. Call it in the browser only, never during server rendering.
Lowercase names with words joined by _ or -, like signup_completed, are easiest to match with goals. Never put personal data such as email addresses in names or properties.
For TypeScript declarations and recipes, see Track custom events.
What's tracked automatically
| Event | When it's sent |
|---|---|
pageview | When the script first runs, on every history.pushState navigation, and on back and forward (popstate). history.replaceState is ignored on purpose. Hash changes aren't tracked as new pages. |
session_end | Once per session, when the tab is hidden or the page unloads. It records the last moment of the visit so session length stays accurate, including for one-page visits. It doesn't count as a pageview. |
| custom | When a data-muro-event element is clicked or window.muro.track is called. |
Each event carries:
- The page path and query string (never the
#fragment). - The referrer's hostname only. Links from your own hostname aren't counted as referrers.
utm_source,utm_mediumandutm_campaignfrom the page URL.utm_termandutm_contentaren't captured.- A visitor ID and a session ID (see Storage keys).
- Device type from screen width (under 768 px is mobile, under 1,024 px is tablet, otherwise desktop), browser, operating system and screen width.
- A timestamp, plus properties for custom events.
Country, region and city are added on Muro's side from the network location of the request. The IP address itself is never stored.
The visitor ID is a SHA-256 hash computed in the browser from the user agent, screen size, language, time zone and a random salt that changes every 24 hours. The same visitor gets the same ID for a day, and a new one after that. A session lasts until 30 minutes of inactivity or until the tab closes. See Privacy.
Validation rules
Muro checks every event when it arrives. An event that breaks a rule is rejected as a whole, and the script stays silent about it, so nothing shows in your console.
| Field | Rule |
|---|---|
| Event name | Required for custom events, 1 to 64 characters. |
| Properties | Up to 10 keys. Values are strings, numbers or booleans only: no null, arrays or nested objects. At most 2,048 characters once serialized as JSON. |
| Path | Path plus query string, at most 2,048 characters. |
| UTM values | Each of utm_source, utm_medium, utm_campaign at most 256 characters. |
| Timestamp | Not more than 10 minutes in the future or 24 hours in the past. The time comes from the visitor's device clock, so a badly wrong clock can get events rejected. |
If a custom event never shows in the Custom Events panel, check it against this table first.
Storage keys
Muro sets no cookies. It uses browser storage on your site's origin:
| Key | Storage | Lifetime | Holds |
|---|---|---|---|
muro_s | localStorage | 24 hours | A random salt for the daily visitor ID. Never sent to Muro. |
muro_i | sessionStorage | The tab's session | The session ID. |
muro_t | sessionStorage | The tab's session | The time of the last activity, for the 30-minute timeout. |
Storage is per origin, so each subdomain counts its own visitors and sessions. See Subdomains.
Endpoints and CSP
| URL | Used for |
|---|---|
https://api.muroanalytics.com/muro.js | Loading the script. |
https://cdn.muroanalytics.com/collect | Sending events, as a POST with navigator.sendBeacon, or fetch with keepalive as a fallback. |
If your site has a Content Security Policy, allow both:
script-src https://api.muroanalytics.com;
connect-src https://cdn.muroanalytics.com;See Fix Content Security Policy errors.
What the collect endpoint returns, as seen in the Network tab:
| Status | Meaning |
|---|---|
202 | Accepted. |
404 | No active project with that ID. Check data-project-id, and that Tracking active is on in Settings → Project. |
422 | The event broke a validation rule. |
429 | Usually means your monthly pageview limit is reached. See Pageview limits. |
A 202 means the event was accepted. If your trial has ended or a payment failed, events are accepted but not stored until you subscribe. See Events are paused.
Verify it works
Open DevTools, go to the Network tab, filter by collect and reload your site. You should see a POST to cdn.muroanalytics.com/collect with status 202. Or set the dashboard time picker to Live.