Skip to Content
v1.27.1 · shipped Global XHR capture, Hash-router inventory Read the changelog →
SDK reference@mushi-mushi/web

@mushi-mushi/web

Browser SDK: screenshots, console logs, network breadcrumbs, and a shake-to-report widget in a Shadow DOM so your CSS never leaks in or out.

Migrating from another bug-capture tool? We have step-by-step guides for Instabug / Luciq, Shake, LogRocket Feedback, BugHerd, and Pendo Feedback. Each maps the competitor’s API to the Mushi equivalent and includes an interactive checklist.

See Quickstart → Vanilla JS for setup. Notable extras:

  • XHR + fetch network capture — the SDK patches both XMLHttpRequest and window.fetch globally at init(). Every network request (including legacy jQuery/axios XHR calls) is captured as a MushiNetworkEntry and appears in the Network breadcrumbs tab. Calling destroy() removes the patches; if another APM tool (Sentry, Datadog) has since wrapped the same globals, the SDK detects the unsafe state and leaves the native references alone.
  • runtimeConfig: 'auto' — fetches console settings from GET /v1/sdk/config and merges them over host init. Host-wired banner and capture flags win over console defaults. See Runtime config.
  • onProactiveTrigger(({ context }) => …) — fires when the SDK detects user friction (rage clicks, repeated navigation, console errors during the same interaction). Use it to surface the report widget contextually.
  • beforeSend((report) => report | null) — last-mile transform after built-in PII scrub. Return null to drop the report client-side. Prefer this over the deprecated beforeSendFeedback (feedback-only).
  • sampleRate / replaySampleRate — probabilistic gates for automatic error reports and session replay (see below). User-initiated feedback is never sampled out.
  • pii — built-in scrubber masks emails, phones, SSNs, credit-card-shaped strings, and JWTs by default. Add custom regex via pii.customPatterns.

Sampling & beforeSend

High-traffic apps can thin automatic capture without turning off the widget:

Mushi.init({ projectId: '00000000-0000-0000-0000-000000000000', apiKey: 'mushi_xxxxxxxxxxxxxxxxxxxxxxxxxxxx', // Automatic (non-user) error reports only — feedback always sends sampleRate: 0.1, // Session replay: decision once at init; sampled-out sessions never load rrweb replaySampleRate: 0.2, beforeSend: (report) => { if (report.description?.includes('internal-only')) return null return report }, })
OptionDefaultApplies to
sampleRate1Automatic error/exception reports. Range 01.
replaySampleRate1Whether this session records replay. Independent of sampleRate.
beforeSendEvery report type after PII scrub. Async OK.
beforeSendFeedbackDeprecated. Feedback-only; ignored when beforeSend is set.

Types live on MushiConfig in @mushi-mushi/core.


When screenshot capture is enabled (capture.screenshot: 'on-report' or 'auto'), the details step shows a visible preview of the image that will be attached — not just a “Screenshot attached ✓” label. Reporters can Remove the screenshot before submit and optionally read a privacy caption beneath the preview.

Mushi.init({ projectId: '00000000-0000-0000-0000-000000000000', // UUID from Projects page apiKey: 'mushi_xxxxxxxxxxxxxxxxxxxxxxxxxxxx', // report:write key widget: { // true → localized default caption (en/es/ja/th) // string → your compliance copy verbatim // false → hide caption (preview + Remove still show) screenshotSensitiveHint: true, }, capture: { screenshot: 'on-report' }, })

Tune the caption from the admin console (Projects → SDK install → Screenshot privacy caption) without rebuilding — it travels in GET /v1/sdk/config under widget.screenshotSensitiveHint.

The preview stays in sync if the reporter uses Mark up (blur/highlight). Keep img-src data: in your CSP when using screenshots on locked-down pages. See the Next.js App Router + CSP recipe.

Maintainer deep-dive: SDK_SCREENSHOT_PREVIEW.md 


Runtime config merge (Jul 2026+)

When runtimeConfig is 'auto', the SDK merges console settings from GET /v1/sdk/config over your init() config. Explicit host wiring wins — if you set widget: { trigger: 'banner' }, a console default of launcher: 'auto' no longer hides the banner.

Capture flags merge key-by-key: only values the console explicitly configured replace host values. Unconfigured console defaults are omitted so they cannot clobber host init.

See Runtime config for the setup default and troubleshooting table. Maintainer deep-dive: SDK_RUNTIME_CONFIG.md .


Capture availability & errors

Screenshot and element-picker buttons hide when that capture mode is unavailable (permission denied, unsupported browser, or disabled in config). When capture fails at runtime, the widget shows a short inline error instead of a dead button.

Wire availability from host code when needed:

mushi.setCaptureAvailability({ screenshot: true, element: false })

Description draft persistence

Typed report text (description, email, follow-up reply) survives background panel re-renders — for example when runtime config refreshes or the route changes. Drafts clear on successful submit or when the reporter opens a new session.


Identifying users & the Rewards program

Call identify() as soon as your auth state resolves. The SDK links all subsequent reports and activity events to that user identity server-side.

// On login / auth state change const { user } = await supabase.auth.getUser() if (user) { mushi.identify(user.id, { email: user.email, name: user.user_metadata?.full_name, provider: 'supabase', }) }

identify() is idempotent — calling it again with the same userId updates the stored traits. Calling it with a new userId flushes any buffered events for the previous session first.

Enabling the Rewards program

Add a rewards block to your init() config:

import { Mushi } from '@mushi-mushi/web' const mushi = Mushi.init({ projectId: 'YOUR_PROJECT_ID', apiKey: 'YOUR_API_KEY', rewards: { enabled: true, trackActivity: true, // auto-capture page_view, navigate, session_start consentMode: 'explicit', // 'explicit' | 'auto' showInWidget: true, // show tier + points in the bug-report widget showNotifications: true, // "+X pts" toast on each award flushIntervalMs: 300_000, // how often to POST activity (min 30s) }, })

trackActivity: true automatically captures:

ActionTrigger
session_startFirst SDK init after 30 min idle, capped 3×/day
page_viewEvery history.pushState / popstate event
navigate<a> clicks and programmatic router pushes
button_pressClicks on [data-mushi-track] or [data-testid] elements

For custom actions:

mushi.submitActivity([ { action: 'lesson_complete', metadata: { lessonId: 'l_123', score: 0.9 } }, ])

Querying points & tier

// Current user's points const points = await mushi.getReputation() // → { totalPoints, points30d, reputation, confirmedBugs, totalReports } // Current tier const tier = await mushi.getTier() // → { id, slug, displayName, pointsThreshold, perks } | null

See Concepts → Rewards & contributor identity for the full data model, anti-gaming integration, and webhook reference.

Reporter API

Let reporters see and follow up on their own submissions without a login. Every method is keyed to the persistent anonymous reporterToken the SDK stores in localStorage, so no auth wiring is required.

// The reporter's own report history (newest first) const reports = await mushi.listMyReports() // → MushiReporterReport[] (each carries `unread_count` for a badge) // The comment thread on one of their reports const comments = await mushi.listMyComments(reportId) // → MushiReporterComment[] (their comments + team replies) // Post a follow-up comment on their own report const comment = await mushi.replyToReport(reportId, 'Still happening on iOS 18') // → MushiReporterComment | null // The project's public contributor leaderboard const leaders = await mushi.getHallOfFame(10) // → MushiHallOfFameEntry[] (display_name, tier_name, points_30d, total_points)

All four methods fail soft: they return [] / null (never throw) when the network is down or no reporter token exists yet, so they’re safe to call on first render.

React

The same methods are exposed on the useMushi() hook, memoised so they’re stable across renders:

import { useMushi } from '@mushi-mushi/react' function MyReports() { const { listMyReports, replyToReport } = useMushi() // ... call inside an effect or event handler }

useMushi() returns no-op fallbacks (() => Promise.resolve([])) before the SDK finishes initialising, so you never need to null-check the instance.


Hash-routed SPAs (e.g. React Router <HashRouter>, Backbone, legacy apps)

Hash-routed SPAs navigate via location.hash changes (#/login, #/article/my-post). The Mushi web SDK subscribes to hashchange automatically — every navigation is captured as a timeline entry. To group parametric routes in the Inventory, supply /#/-prefixed route templates:

Mushi.init({ projectId: 'YOUR_PROJECT_ID', apiKey: 'YOUR_API_KEY', capture: { discoverInventory: { enabled: true, routeTemplates: [ '/#/article/[slug]', // matches #/article/my-post → /#/article/[slug] '/#/profile/[username]', ], }, }, })

Why the /#/ prefix? Hash fragments are not part of the pathname. When a hash route is present Mushi stores '/#' + hashPath (the pathname is dropped); otherwise it stores the plain pathname. History-based routes (/article/[slug]) and hash routes (/#/article/[slug]) therefore stay distinct in the Inventory and can be monitored independently.

Filter params (e.g. #/?tag=elixir&offset=10) are stripped before the route template is matched — only the path segment after #/ is matched.

Without templates, every unique hash path becomes its own Inventory row. For slug-heavy apps (articles, profiles, products) this inflates the Inventory; route templates collapse them to a manageable list.

mushi doctor --host-app detects hash-router usage in your source files and warns when /#/ route templates are missing from your Mushi init call.

Last updated on