Documentation

SDK reference

Everything is imported from @marsadco/sdk, with React helpers under @marsadco/sdk/react (web) and @marsadco/sdk/react-native (React Native / Expo). The runtime (browser / Node / React Native) is auto-detected and the right integration — including runtime context and breadcrumbs — installed for you.

init(config)

Configures and starts the SDK, returning a MarsadClient. Everything is optional — key falls back to the environment (see below).

OptionTypeDefaultDescription
keystringenvProject ingest key (bk_live_…). Optional if set in the env — init() reads MARSAD_KEY / NEXT_PUBLIC_MARSAD_KEY / EXPO_PUBLIC_MARSAD_KEY. With neither arg nor env, init() throws (pass enabled: false to disable).
endpointstringhostedIngest base URL. Override only for self-hosted / staging.
environmentstringNODE_ENVDeployment environment tag.
releasestringautoRelease / version identifier for grouping by deploy. Auto-detected from CI env vars when unset (see Release auto-detection).
platformPlatformautoOverride the detected platform: web | node | ios | android.
enabledbooleantrueMaster switch. Set false to no-op the SDK (e.g. in dev).
debugbooleanfalseLog SDK activity to the console.
sampleRatenumber1Fraction of error / log events to send, 0–1. Fallback for the per-type rates below.
errorSampleRatenumbersampleRateFraction of error events to send. Set 1 to keep every error while sampling logs.
logSampleRatenumbersampleRateFraction of log / message events to send.
networkSampleRatenumber1Fraction of network events to send (not governed by sampleRate).
tracesSampleRatenumber0Fraction of traces to record (performance spans), 0–1. Default 0 (tracing off). Decided once per trace at the root; children inherit, so a trace is recorded whole or not at all.
tracePropagationTargetsMatcher[]same-originURLs to inject the W3C traceparent header on so a backend continues the trace. Default: same-origin only — keep it to your own APIs (sending trace ids to third parties triggers CORS preflights).
tagsRecord<string, string>{}Initial indexed tags applied to every event. Add more at runtime via setTag() / setTags(); filterable on the dashboard.
ignoreErrorsMatcher[][]Drop errors whose message / type matches.
denyUrlsMatcher[][]Drop errors originating from a matching script URL.
captureUnhandledbooleantrueInstall global error / unhandledrejection handlers.
captureNetworkbooleantrueCapture fetch / XHR as network events.
ignoreUrlsMatcher[][]Exclude matching URLs from capture (errors + network).
networkNetworkOptionsFine-grained network controls (see below).
sessionsbooleantrueTrack a session per app start, marked crashed on an unhandled error — powers crash-free rate on the Overview.
webVitalsbooleantrue (web)Capture Web Vitals (LCP / CLS / INP / FCP / TTFB) on the web — shown as a p75 card on the Overview.
feedbackbooleantrueShow the built-in FeedbackWidget + the crash-report form in the default ErrorBoundary. captureFeedback() still works when off.
screenshotScreenshotCaptureCapturer for screenshot-on-error (off by default; you supply the pixel capture, e.g. html2canvas). The shot is attached to the issue. See Screenshots & attachments.
breadcrumbsbooleantrueAuto-capture console logs, navigation, clicks and network requests as breadcrumbs, attached to the next error.
maxBreadcrumbsnumber30Breadcrumbs retained and attached to errors. 0 disables.
storageMarsadStorage | nullautoPersist the event queue so events survive reload / offline / crash (localStorage on web, AsyncStorage on React Native). Set null to disable.
flushIntervalnumber5000Flush the queue every N ms.
maxBatchSizenumber30Max events per request.
maxQueueSizenumber100Max events buffered before the oldest are dropped.
maxQueueAgeMsnumber86400000Drop persisted events older than this (default 24h) so stale events from old sessions aren't replayed.
userMarsadUserInitial user. Change later via setUser().
beforeSend(e) => e | nullLast-chance transform / drop per event. Return null to drop.
beforeBreadcrumb(c) => c | nullLast-chance transform / drop per breadcrumb (mirrors beforeSend).
breadcrumbCategories{ console?, navigation?, click?, http? }{}Turn off individual auto-breadcrumb categories, e.g. { console: false }.

NetworkOptions

Passed as network on the config. Matcher is a string (substring) or RegExp; StatusMatcher is a code (404) or an inclusive range ([500, 599]).

OptionTypeDefaultDescription
captureBodybooleanfalseCapture request / response bodies (truncated).
maxBodyLengthnumber2048Max captured body length in chars.
captureHeadersbooleanfalseCapture request / response headers (scrubbed).
minDurationnumberOnly capture requests slower than N ms.
onlyStatusCodesStatusMatcher[]Only capture matching statuses (failures always kept).
ignoreStatusCodesStatusMatcher[]Never capture matching statuses.
allowUrlsMatcher[]Allow-list: capture ONLY matching URLs; all others dropped.

Methods

Top-level functions are no-ops before init(), so they're always safe to call.

init(config): MarsadClientInitialize and install the runtime integration. Call once at startup; a second call is ignored.
captureException(error, ctx?): stringReport an error manually. ctx can set level, user, context, mechanism, handled, tags (indexed/filterable) and fingerprint (string[] to force grouping). Returns a short reference code (MAR-…) for the event.
captureMessage(message, level?, ctx?): stringReport a string event. level defaults to "info". Same ctx as captureException (incl. tags + fingerprint). Returns a short reference code (MAR-…).
captureFeedback({ message, name?, email?, eventCode? }): voidSend a user feedback report. Pass the reference code returned by captureException as eventCode to tie it to that issue. Surfaced on the Feedback page.
setUser(user | null): voidAttach the current user to subsequent events, or pass null to clear it on logout.
setTag(key, value): voidSet a single indexed tag on subsequent events — filterable on the dashboard and shown as clickable chips on the issue detail.
setTags(tags): voidSet multiple indexed tags at once, e.g. { region: "eu", tenant: "acme" }.
setContext(key, value): voidAttach arbitrary structured data to every event.
addBreadcrumb(crumb): voidRecord a trail entry (navigation, click, log) shown with the next error, on top of the auto-captured breadcrumbs.
metrics.increment / distribution / gauge(name, value?, { unit?, tags? })Record a custom metric — counters, distributions (percentiles) and gauges, aggregated on the Metrics page.
startSpan({ op, name?, attributes? }, callback): TRun a callback inside a performance span (sync or async); records its duration on the Performance page. Spans started inside it nest into one trace. Requires tracesSampleRate > 0.
startInactiveSpan(opts) / getActiveSpan()Start a span you end() manually, or read the current active span (e.g. to add attributes or read its traceparent for manual propagation).
flush(): Promise<void>Send any queued events now — e.g. before a serverless function exits.
getClient(): MarsadClient | nullThe active client, or null before init().
close(): Promise<void>Uninstall handlers, flush and reset. Mainly for tests / HMR.

ErrorBoundary

Captures render-time errors in its subtree (with the component stack) and renders a fallback. Import from @marsadco/sdk/react on the web and @marsadco/sdk/react-native in React Native / Expo. Props: children, fallback (a node, or (error, reset) => node), and onError(error, info). With no fallback, the default UI shows Something went wrong, the event's reference code (Reference: MAR-…), a Try again button, and a one-tap Report a problem form pre-linked to that crash (hidden when init({ feedback: false })) — rendered with native View / Text on React Native.

Both the ErrorBoundary and FeedbackWidget live in @marsadco/sdk/react (web) and @marsadco/sdk/react-native (React Native) — @marsadco/sdk/expo re-exports the React Native ones alongside its init().

@marsadco/sdk/react (web)tsx
import { ErrorBoundary } from "@marsadco/sdk/react";

<ErrorBoundary
  fallback={(error, reset) => (
    <Crash message={error.message} onRetry={reset} />
  )}
>
  <App />
</ErrorBoundary>;
@marsadco/sdk/react-native (Expo / RN)tsx
import { ErrorBoundary } from "@marsadco/sdk/react-native";

// No fallback → native "Something went wrong" + "Reference: MAR-…"
<ErrorBoundary>
  <RootNavigator />
</ErrorBoundary>;

Framework helpers

Thin wrappers that read the key from the environment for you, so most apps never pass key at all.

@marsadco/sdk/nextjs exports register (server, reads MARSAD_KEY) and initBrowser() (reads NEXT_PUBLIC_MARSAD_KEY), and re-exports ErrorBoundary.

@marsadco/sdk/nextjstsx
// instrumentation.ts (server)
export { register } from "@marsadco/sdk/nextjs";

// a "use client" component mounted once in app/layout.tsx
"use client";
import { useEffect } from "react";
import { initBrowser } from "@marsadco/sdk/nextjs";

export function MarsadInit() {
  useEffect(() => initBrowser(), []);
  return null;
}

@marsadco/sdk/expo exports init() (reads EXPO_PUBLIC_MARSAD_KEY) and re-exports the React Native ErrorBoundary.

@marsadco/sdk/expotsx
import { init, ErrorBoundary } from "@marsadco/sdk/expo";

init(); // key from EXPO_PUBLIC_MARSAD_KEY

export default function App() {
  return (
    <ErrorBoundary>
      <RootNavigator />
    </ErrorBoundary>
  );
}

Tags

Tags are indexed string key/value pairs you can filter by on the dashboard (the issue detail shows them as clickable chips). Set defaults via the tags config option, update them at runtime with setTag() / setTags(), or attach per-event tags via ctx.tags.

import { setTag, setTags, captureException } from "@marsadco/sdk";

setTag("plan", "pro");
setTags({ region: "eu", tenant: "acme" });

captureException(err, { tags: { feature: "checkout" } });

Web Vitals

On the web, the SDK captures Core Web Vitals — LCP, CLS, INP, FCP and TTFB — automatically, with no extra code. They appear as a p75 card on your Overview. Disable with webVitals: false.

User feedback

Collect a report from the user and tie it to the issue they hit by passing the reference code returned from captureException() as eventCode. Feedback shows up on the dashboard's Feedback page.

import { captureException, captureFeedback } from "@marsadco/sdk";

const code = captureException(err);

captureFeedback({
  message: "Pay button did nothing!",
  email: "a@b.com",
  eventCode: code, // links the report to that issue
});

For a ready-made form, drop in <FeedbackWidget /> from @marsadco/sdk/react (or /react-native) — a floating button that opens a message form and submits for you, auto-tagged with the latest event code.

import { FeedbackWidget } from "@marsadco/sdk/react";

<FeedbackWidget />

Custom metrics

Track counters, distributions and gauges. They're aggregated on the Metrics page (counters → count + total; distributions → avg / p50 / p95 / max) over the selected window. Add dimensions with tags.

import { metrics } from "@marsadco/sdk";

metrics.increment("checkout.completed");
metrics.distribution("image.upload.ms", 842, { unit: "ms", tags: { region: "eu" } });
metrics.gauge("queue.depth", 12);

Performance tracing

Set tracesSampleRate above 0 to record traces. On the web the SDK opens a pageload transaction on load and a navigation one per route change, and wraps every fetch / XHR in an http.client span — so each page becomes one trace you can open as a waterfall on the Performance page. Wrap your own work with startSpan; nested spans join the same trace.

import { init, startSpan } from "@marsadco/sdk";

init({ tracesSampleRate: 1.0 }); // record all traces (lower in production)

const user = await startSpan({ op: "db.query", name: "load user" }, async () => {
  return db.users.find(id); // duration recorded; child spans nest under it
});

The SDK injects a W3C traceparent header on same-origin requests (configurable via tracePropagationTargets). On a Next.js backend, wrap route handlers / server actions to continue that trace — the server spans nest under the same trace, so a slow page links to the backend call that caused it:

import { wrapRouteHandler, withMarsad } from "@marsadco/sdk/nextjs";

export const GET = wrapRouteHandler(async (req) => { /* … */ });
export const submit = withMarsad(async (data: FormData) => { /* … */ }, "submit");

Screenshots & attachments

Attach a screenshot of the broken screen to every error. It's off by default (screenshots can contain personal data), and the SDK stays dependency-free — you supply the pixel capture, the SDK handles timing (raced against a 2s timeout), size-capping, and attaching it. Pass a function that returns a data URL / base64:

import html2canvas from "html2canvas";

init({
  screenshot: async () =>
    (await html2canvas(document.body)).toDataURL("image/jpeg", 0.7),
});
// React Native: react-native-view-shot's captureScreen() works the same way.

The shot shows up inline on the issue. You can also attach arbitrary files to a specific capture as an escape hatch (a log dump, serialized state) via captureException(err, { attachments }) — keep them small and free of secrets; most teams are better served by tags, breadcrumbs, and screenshots.

Native crashes — React Native (experimental)

The JS SDK already captures JS-thread errors. Native crashes (iOS signals/NSException, Android JVM/ANR) happen below JS and need a native module — added via our Expo config plugin. It captures the crash, persists it, and the SDK sends it on the next launch with your latest user / tags / breadcrumbs attached.

app.jsonjson
{
  "expo": {
    "plugins": [
      ["@marsadco/sdk", { "key": "bk_live_…", "endpoint": "https://…" }]
    ]
  }
}

Then npx expo prebuild + a dev/EAS build (native crash capture can't run in Expo Go). Upload debug files in CI so stacks symbolicate — iOS dSYM UUIDs are read automatically; pass the Android ProGuard mapping's id:

npx marsad upload-debug-files --platform ios ./ios/build
npx marsad upload-debug-files --platform android --proguard-id <uuid> ./android/app/build

Verify with nativeCrash() from @marsadco/sdk/expo (release builds only). Native capture is rolling out — Android JVM/ANR + iOS exceptions first; full NDK minidumps later.

Auto-context — setContextProvider

Every event is auto-enriched with runtime context: on the browserurl, userAgent, language, screen, viewport, referrer; on Node runtime, nodeVersion, os, arch; on React Nativeruntime and hermes. Augment or replace it with a provider on the client:

import { getClient } from "@marsadco/sdk";

getClient()?.setContextProvider(() => ({
  tenant: currentTenant(),
  featureFlags: activeFlags(),
}));

Release auto-detection

If release isn't set, the SDK resolves it from the first of these env vars it finds — so deploys group themselves in CI without extra config:

MARSAD_RELEASE, NEXT_PUBLIC_MARSAD_RELEASE, EXPO_PUBLIC_MARSAD_RELEASE, VERCEL_GIT_COMMIT_SHA, SOURCE_VERSION (Heroku), RENDER_GIT_COMMIT, CF_PAGES_COMMIT_SHA, GITHUB_SHA.

Either run npx marsad inject after your build (debug ids match maps to the exact build automatically) or pass the same release value to npx marsad upload-sourcemaps --release … so your maps line up with the events from that build.

Event reference codes

captureException() and captureMessage() return a short, user-facing code like MAR-K7P2QX9M. Show it to users (the ErrorBoundary default fallback already does, as Reference: MAR-…), then search it in the dashboard's ⌘K command palette to land on the exact issue.

Example

A typical setup with route exclusions, a user, and manual capture:

app.tsts
import {
  init, setUser, addBreadcrumb, captureException,
} from "@marsadco/sdk";

init({
  key: process.env.MARSAD_KEY!,
  release: "web@1.4.0",
  ignoreUrls: ["/health", /\/_next\//],
  network: {
    allowUrls: ["https://api.myapp.com"], // only our backend
    ignoreStatusCodes: [[200, 399]],      // and only 4xx/5xx + failures
    minDuration: 500,                     // …that are also slow
  },
});

setUser({ id: user.id, email: user.email });
addBreadcrumb({ category: "ui", message: "Checkout opened" });

try {
  await checkout(cart);
} catch (err) {
  captureException(err, { context: { orderId } });
  throw err;
}

The SDK never reports requests to its own ingest endpoint, and scrubs obvious secrets (auth headers, secret-looking params and JSON fields) before anything leaves the device.