Skip to Content
v1.33.8 · shipped One-screen report · Looks like your app Read the changelog →
SDK reference@mushi-mushi/web

@mushi-mushi/web

The Mushi Mushi browser SDK: screenshots, console logs, network breadcrumbs, and a 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 Mushi equivalent and includes an interactive checklist.

See Quickstart → Vanilla JS for setup. The project ID and API key come from a Mushi Mushi project: create a free account  (no card) to get both. 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.
  • proactive: { rageClick, errorBoundary, longTask, apiCascade, pageDwell, firstSession, cooldown }: friction detectors that open the reporter contextually (rage clicks, error boundaries, long tasks, API cascades, page dwell, first session). Subscribe with mushi.on('proactive:triggered', ({ data }) => …) (proactive:dismissed fires on close) and cap frequency with cooldown.
  • 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.
  • Built-in PII scrubber: always on; masks emails, phones, SSNs, credit-card-shaped strings, JWTs and vendor API keys (see the pattern table). It has no init option. To remove anything else, edit or drop the report in beforeSend.

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 0–1.
replaySampleRate1Whether this session records replay. Independent of sampleRate.
beforeSend—Every report type after PII scrub. Async OK.
beforeSendFeedback—Deprecated. 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 report screen 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/zh) // 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 (highlight, blur, arrow). 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. The SDK sets this availability itself; there is no public method to call.


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 and traits does nothing, and calling it with new traits updates them. Calling it with a different userId switches the identity used for later reports and activity.

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 first route visits and [data-testid] clicks consentMode: 'explicit', // 'explicit' | 'auto' showInWidget: true, // show tier + points in the bug-report widget flushIntervalMs: 300_000, // how often to POST activity (min 30s) }, })

trackActivity: true automatically captures:

ActionTrigger
screen_view_unique_per_dayFirst visit to each route during the page session (history.pushState, popstate, or a DOM change under <main>). Despite the name, the SDK remembers visited routes in memory only, so a reload counts them again
element_selectedClicks on [data-testid] elements

With rewards enabled, the SDK also records session_minute once a minute while the page is open (up to 60 minutes a day), whether or not trackActivity is on. Nothing is sent until the user has opted in and identify() has been called.

For custom actions:

mushi.recordActivity('lesson_complete', { 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 Mushi web SDK subscribes to hashchange automatically, so 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 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 Mushi init call.

Last updated on