Skip to help content
Help / field manual
Mu operator reference

Read the signal clearly.

Install Mu, verify incoming data, understand every report, and keep a privacy-first analytics instance healthy across websites, extensions, iOS, and Android.

Manual coordinates

04

platforms

08

event kinds

30s

live refresh

v1

batch contract

Signal path

Source

Consent + identity

Queue

Durable + bounded

Batch

Validated + deduped

Report

Scoped + aggregated

START

From empty project to first signal

A project is the reporting boundary. Add one source for every product surface that should contribute data.

  1. 01

    Create a project

    Open the dashboard, choose New project, give it a recognisable product name, then select the first source platform.

  2. 02

    Configure the source

    For web, add the production domain and any allowed alternates. For apps or extensions, add the exact bundle, package, add-on, or extension identifier.

  3. 03

    Install the collector

    Copy the source-specific setup shown in Settings → Data sources. App and extension collection must receive an explicit consent state.

  4. 04

    Verify the signal

    Generate a test visit or event. The source rail and Settings show the last event plus accepted and rejected ingestion counts. Realtime and headline readings update in place while the tab is visible.

First verification

Use a production-shaped test: the real origin or app ID, the intended environment, and the source's current write key. A successful request with a source mismatch is still rejected at the event level.

MODEL

Projects, sources, and filters

One project can receive compatible signals from several platforms without pretending that every metric means the same thing.

Project

Owns reports, people, goals, funnels, cohorts, retention, share links, timezone, and retention policy. Existing website IDs remain valid project IDs.

Source

Represents one web, browser-extension, iOS, or Android input. Each source has its own validation rules, SDK, identifiers, write key, and health readings.

PlatformIdentifierBest-fit readings
WebAllowed domain(s)Visitors, acquisition, SEO, engagement, heatmaps
Browser extensionExtension, add-on, or bundle IDInstalls, surfaces, browser/version, errors
iOSBundle IDDAU, screens, releases, revenue, stability
AndroidPackage nameDAU, sessions, releases, revenue, timing

The source signal rail beneath the dashboard navigation shows every configured platform, its last signal, and the active source filter. Select a source to scope compatible reports; choose all sources for a project-wide view. Acquisition, SEO, and heatmaps only appear when the project contains a web source.

INSTALL

Install each platform

Installation details and rotatable public write keys live under Settings → Data sources.

Website

Put the script before the closing </head> tag. Replace the host and project ID with the values shown by your source. The legacy endpoint and existing snippets remain supported.

Web collector

<script
  defer
  src="https://YOUR-MU-HOST/script.js"
  data-website-id="PROJECT_ID"
></script>

The web collector is cookie-free and storage-free. It records page views and privacy-safe engagement signals; it does not need an app write key in page markup.

Browser extensions

Bundle the TypeScript SDK into the extension worker. Popup, options, side-panel, and content-script clients proxy to that worker, which owns the IndexedDB queue. No extra storage permission is required.

WebExtension worker

const mu = await MuExtension.configure({
  endpoint: "https://YOUR-MU-HOST",
  writeKey: "mu_…",
  appId: chrome.runtime.id,
  collectionEnabled: consent.analytics,
  runtime: chrome.runtime,
  incognito: chrome.extension.inIncognitoContext,
});

await mu.capture("upgrade_clicked", { plan: "pro" });

Store-safe defaults

Automatic collection is limited to install, update, and runtime lifecycle. Mu never automatically reads visited URLs, active tabs, page content, searches, or browsing history. Incognito contexts are excluded. Firefox builds must also declare the current built-in data-consent manifest requirements.

Mobile and cross-platform apps

Swift / iOS

Use the Swift Package core. It owns identity, foreground sessions, lifecycle, a file-protected queue, and optional automatic screen tracking.

Kotlin / Android

Use the Android library. It owns app lifecycle, five-minute foreground sessions, private preferences, and the durable event queue.

React Native / Expo

Use the JavaScript facade over the native cores so sessions, identity, queues, and lifecycle remain platform-consistent.

Flutter

Use the Dart facade and native plugin. Configuration always includes the endpoint, source write key, and explicit collection state.

Mobile queues flush every 10 seconds or 20 events, flush again when the app backgrounds, retry with exponential jitter, and cap themselves at 1,000 events or 5 MB. The default foreground inactivity boundary is five minutes.

CAPTURE

Events, properties, and context

Mu keeps a small typed event vocabulary and carries platform context separately from your custom properties.

KindUse it forRequired payload
pageviewA website locationName and standard context
screenAn app screenScreen name/class in context
eventA product actionEvent name
lifecycleInstall, update, foreground, backgroundLifecycle name
revenueA purchase, refund, or renewalAmount and ISO currency
errorA handled or unhandled failureMessage; optional type/stack
metricStartup, screen, or custom timingDuration in milliseconds
presenceA current activity heartbeatName and session

Every event also has a client-generated event ID, occurrence time, anonymous install ID, optional external user ID, session ID, platform, SDK, environment, and optional release, screen, surface, OS, device, browser, locale, and timezone context.

Standard SDK surface

configure()capture()screen()identify()setUserProperties()reset()revenue()captureError()timing()setCollectionEnabled()flush()

Direct batch request

curl https://YOUR-MU-HOST/api/v1/batch \
  -H "Authorization: Bearer mu_…" \
  -H "Content-Type: application/json" \
  --data '{
    "sentAt": 1786000000000,
    "events": [{
      "eventId": "evt_01JEXAMPLE",
      "kind": "event",
      "name": "upgrade_clicked",
      "occurredAt": 1786000000000,
      "anonymousId": "install_01JEXAMPLE",
      "sessionId": "session_01JEXAMPLE",
      "context": {
        "platform": "ios",
        "sdk": "swift",
        "environment": "production",
        "appId": "studio.selfhood.example",
        "appVersion": "2.4.0"
      },
      "properties": { "plan": "pro" }
    }]
  }'

Write keys are public identifiers

A client write key authenticates one source but is not a secret; it will ship inside your app. Mu protects ingestion with source identifiers, allowed origins, validation, rate limits, and key rotation. Never reuse it as an administrative credential.

IDENTITY

People, installs, and sessions

Anonymous activity is useful by default; identified activity is intentionally opt-in.

Anonymous

App SDKs create a random install ID only after collection is enabled. Web visitors stay storage-free and rotate daily.

Identified

identify(userId) joins earlier anonymous activity to a canonical person using a project-scoped server hash.

Session

Mobile closes after five foreground-inactive minutes. Extensions share a 30-minute session across their product surfaces.

  • reset() ends the identified context and begins a fresh anonymous identity.
  • setCollectionEnabled(false) clears the durable queue, persistent install identity, and identified user.
  • User properties describe the person; event properties describe one action. Secret-like and common PII property keys are removed.
  • Settings can delete a person and their events from an external user ID without storing that raw external ID in Mu.

Cross-platform rule

Mu only builds unified cross-source funnels, journeys, and retention when events can be tied to the same identified person. It never silently equates a daily-rotating anonymous web visitor with a persistent app install.

ANALYZE

Read reports without mixing meanings

The dashboard adapts its headline vocabulary to the selected source while project-wide analysis stays available where identity allows it.

AreaQuestion it answersImportant behavior
OverviewWhat changed in this range?Platform-aware totals, trends, and realtime
ExploreHow does a metric move and segment?Two breakdowns, typed filters, source/version/environment filters
FunnelsWhere do people leave a fixed path?Ordered steps and conversion drop-off
JourneysWhat paths do sessions actually take?Page, screen, and event transitions
RetentionDo identified people return?Weekly cohorts; anonymous rotating web IDs are excluded
CohortsWho matches a reusable behavior?Event occurrence within a chosen number of days
GoalsDid a named outcome happen?Page or event outcomes and optional revenue
UsersWhat has one known person done?Private only; never exposed by public share links

Explore

Choose event totals, unique users, sessions, revenue, or average duration. Filter by event, source, platform, environment, app version, and typed property values; then add up to two breakdown dimensions. Save a useful configuration as a report so it can be reopened without rebuilding the query.

Date ranges use the project timezone. Event occurrence time—not upload time—places offline events in a report, subject to clock and retention validation.

WEB

Acquisition, SEO, engagement, and heatmaps

Web-only instruments stay attached to the relevant web source instead of appearing as generic app metrics.

Acquisition

Referrers, landing pages, UTM campaigns, locations, and automatic paid-search or affiliate channel grouping.

Engagement

Automatic scroll depth from 1–100%, outbound links, file downloads, form completion without values, and page journeys.

SEO

Connect Google Search Console per web source from Settings to read search queries and organic performance beside conversions.

Heatmaps

Inspect privacy-safe click density for the selected web page and range; app screens do not masquerade as DOM heatmaps.

Built-in filtering removes known bots, referrer spam, and data centre traffic from web readings. Allowed domains are configured per source; add preview or alternate domains deliberately rather than weakening origin validation.

QUALITY

Releases, stability, and performance

Quality readings connect errors and timings to the release and session in which they occurred.

  • Releases shows adoption, sessions, errors, and crash-free sessions by application or extension version and build.
  • Stability groups matching errors from type, scrubbed message, and a normalized raw-stack fingerprint. Mark groups open, resolved, or ignored as work progresses.
  • Timings cover cold/warm app start, screen duration, and manually captured work, with percentile summaries instead of misleading averages alone.
  • Abnormal-termination markers improve crash-free session estimates when the platform cannot submit a crash immediately.

Current reliability boundary

Mu stores scrubbed raw stacks and fingerprints errors, but does not yet symbolicate dSYM, ProGuard, or source-map artifacts. Treat release health as operational analytics, not a full crash-debugger replacement.

LIVE

Realtime and ingestion health

Realtime answers a rolling activity question; source health answers whether data is reaching the server correctly.

Active in five minutes

A visitor or session appears when Mu has received qualifying activity inside the rolling five-minute window. It does not claim the person still has the page open.

Thirty-second refresh

Headline and realtime readings refresh in place every 30 seconds only while the browser tab is visible and online. The page and report editor are not reloaded.

Settings → Data sources shows request, accepted, and rejected totals for the last 24 hours plus the last event time. Requests with only duplicates are healthy and count as processed; rejected events point to validation, identity, source, time, or rate-limit problems.

PRIVACY

Privacy, consent, exports, and sharing

Mu minimises collection before storage and keeps sensitive operational surfaces private.

Not collected

No raw IP addresses, advertising IDs, hardware IDs, automatic extension browsing history, form values, or remote extension code.

Protected at ingestion

Install and supplied user IDs are hashed with a project scope. Email-like values, query strings, bearer tokens, and common secret or PII keys are scrubbed.

  • Extensions and apps require an explicit collection state at initialization. Disabled collection performs no network or persistent-identity writes.
  • Public share links expose aggregate reports only. User profiles, raw events, write keys, error stacks, source health, and settings remain private.
  • CSV and JSON exports contain up to 50,000 privacy-scrubbed events. External identity hashes and raw error stacks are omitted.
  • A project retention window rejects data older than its policy. Run cleanup in Settings to remove existing expired events, sessions, source-health windows, and inactive identities.
OPERATE

Operate and upgrade the instance

Mu remains a one-process, self-hosted service, but production data still deserves an explicit migration and backup routine.

TaskCommandWhen
Fresh schemapnpm db:pushA new empty installation
Create migrationpnpm db:generateAfter a reviewed schema change
Back up databasepnpm db:backupBefore every production migration
Apply migrationspnpm db:migrateExisting local or remote instance
Verify releasepnpm verifyBefore deploying application code

Production libSQL/Turso uses the database URL and auth token in the server environment. Keep JWT_SECRET and preferably a dedicated ANALYTICS_IDENTITY_SECRET stable across deploys: changing the identity secret changes future hashes and breaks continuity with existing people.

Safe migration order

Back up the remote database, test the committed Drizzle migration against a local copy, run the full verification suite, apply the migration remotely, then compare project, source, event, and relationship counts before deploying dependent code.

DIAGNOSE

Troubleshoot by following the signal

Start at the source-health rail, then move from transport to validation to reporting.

01No requests+

Confirm collection is enabled, the SDK endpoint includes your Mu host, the worker or app has network access, and the current write key was bundled. Web users should confirm the script loads without a content-security-policy error.

02Requests but no accepted events+

Open the rejected count, then compare platform, SDK, origin, and appId with the configured source. Check required revenue/error/metric payloads and property limits.

03HTTP 401 or 403+

401 means a missing, invalid, or rotated write key. 403 means the web Origin is not in that source's allowed domains.

04HTTP 429+

The source exceeded its per-minute request or event budget. Keep original event IDs, back off with jitter, and retry later so deduplication remains effective.

05Accepted but absent from this range+

Check source filters, project timezone, environment/version filters, the client occurrence timestamp, and the project retention window. Offline data is charted at occurrence time.

06Realtime returned to zero+

The active reading is a rolling five-minute window. A person who was visible earlier naturally falls out after their last qualifying activity ages past five minutes; it is not a historical-visit counter.

07Person or retention looks incomplete+

Retention and cross-source identity require identify(userId). Anonymous web identity rotates daily and is intentionally excluded from identified-person retention.

08Extension events disappear+

Keep IndexedDB and batching in the background worker, send surface events through the runtime proxy, provide consent explicitly, and test service-worker termination plus restart.

REFERENCE

Ingestion limits and response contract

These boundaries keep client behavior predictable and protect a single-process deployment.

BoundaryLimitResult when exceeded
Batch events50Request validation fails
Request body500 KBHTTP 413
Requests per source120 / minuteHTTP 429 with rejected events
Events per source3,000 / minuteHTTP 429 with rejected events
Properties100; 16 KB totalEvent validation fails
Property key64 charactersEvent validation fails
String / array2,048 characters / 50 valuesEvent validation fails
Future clock skew24 hoursfuture_timestamp rejection
Event ID8–128 charactersEvent validation fails

Send POST /api/v1/batch with Authorization: Bearer WRITE_KEY. The response reports accepted, duplicates, a rejected-event list with reason codes, and serverTime. Event IDs are deduplicated per source, so retry the same batch instead of generating new IDs.

POST /api/e, existing web snippets, dashboard URLs, share URLs, and the current stats API remain available through compatibility adapters.