Skip to content
Maple Docs
Open app
Browse the docs
On this page

Effect SDK in the browser

Set up the Effect SDK in browser environments with explicit configuration and auto-captured browser metadata.

The browser entry point of @maple-dev/effect-sdk runs in single-page apps and any other browser context. Unlike the server build, it reads no environment variables: browsers have no process.env, so all configuration is passed to Maple.layer().

Browsers

Already installed the SDK? If not, see the install instructions.

Quick start

import { Maple } from "@maple-dev/effect-sdk/client"
import { Effect } from "effect"

const TracerLive = Maple.layer({
	serviceName: "my-frontend",
	ingestKey: "maple_pk_...",
	region: "eu", // omit for the US region
})

const program = Effect.log("Hello!").pipe(Effect.withSpan("hello"))

Effect.runPromise(program.pipe(Effect.provide(TracerLive)))

serviceName is the only required option. Without endpoint, the layer sends to the ingest for region: https://ingest.maple.dev for "us" (the default) or https://ingest.eu.maple.dev for "eu". Set endpoint only for a proxy or Maple Local; it overrides region.

Import from /client explicitly. Bundlers that apply the node export condition, such as the server side of a framework build, resolve the bare @maple-dev/effect-sdk import to the server build.

Auto-captured browser attributes

The client layer reads globalThis.navigator and Intl.DateTimeFormat to fill in resource attributes:

  • user_agent.original: navigator.userAgent
  • browser.language: navigator.language
  • browser.timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
  • maple.sdk.type: always "client", so server and browser spans can be filtered apart

Attributes you pass in the attributes option are merged on top of these.

Session replay and sessions

The browser presets (Maple.layer and MapleFlush.make) record rrweb session replays by default, without a separate browser SDK. Every span carries a session.id, the session appears in Sessions with its linked traces, and the recording plays back next to them.

const TracerLive = Maple.layer({
	serviceName: "my-frontend",
	ingestKey: "maple_pk_...",
	replay: {
		sampleRate: 0.1, // record 10% of sessions (default 1)
	},
})
OptionDefaultDescription
replay.enabledtrueRecord rrweb session replays.
replay.sampleRate1Fraction of sessions to record, 0 to 1.
replay.maskAllInputstrueMask all <input> values in the recording.
replay.maskAllTextfalseMask all text in the recording.
emitSessionMetatruePost session metadata rows so unrecorded sessions still appear in the Sessions UI.

How it behaves:

  • Sampling still yields sessions. When replay is disabled or a session isn’t sampled, the SDK still posts session metadata rows. The session shows up in the Sessions UI with its linked traces, just without a recording. Set emitSessionMeta: false to turn that off too.
  • Tab lifecycle. Recording suspends when the tab is hidden (flushing the tail with keepalive) and resumes when it becomes visible again. Sessions survive reloads within a tab and rotate after 30 minutes of inactivity (24-hour hard cap).
  • Identify users at any point; the id is attached when the session’s next metadata row is posted and stamped as user.id on future spans. Pass null or undefined after sign-out to make future telemetry anonymous again:
import { identify } from "@maple-dev/effect-sdk/client"

identify(user.id)
identify(null)
  • Clear the identity on logout with clearIdentity() (the explicit inverse of identify()); metadata rows and spans go back to anonymous while the session continues:
import { clearIdentity } from "@maple-dev/effect-sdk/client"

clearIdentity()
  • Interop with @maple-dev/browser. If the standalone browser SDK is also on the page, it owns the session. This SDK’s recorder and row emission stand down automatically, and spans link to that session instead. Run replay from one SDK, not both.

Privacy

The privacy option controls consent, the persistent visitor id and what identity data is sent:

OptionDefaultDescription
privacy.requireConsentfalseCapture nothing until setConsent(true) is called
privacy.persistVisitorIdtrueStore a persistent visitor id
privacy.crossSubdomainCookietrueScope the visitor-id cookie to the registered domain, so sibling subdomains share one visitor
privacy.cookieDomainExplicit cookie Domain (without the leading dot). "" forces a host-only cookie
privacy.captureUserEmailtrueSend the email passed to identify()
privacy.respectDoNotTrackfalseTreat navigator.doNotTrack like Global Privacy Control
privacy.sanitizeUrlFunction that rewrites every URL before it leaves the page. Runs after the built-in redaction of credential-shaped query and fragment parameters such as token and access_token

Global Privacy Control is honored by default: when the browser sends it, the SDK does not store the persistent visitor id. Capture itself continues without it.

With requireConsent: true, grant or revoke consent from your consent banner:

import { setConsent } from "@maple-dev/effect-sdk/client"

setConsent(true)

With requireConsent, Maple.layer does not export metrics, because it cannot separate values recorded before consent was granted. Use MapleFlush.make if you need browser metrics after consent.

Use the public ingest key

Anything in your bundle is visible to every visitor. Use the Public key (maple_pk_…) from Settings → Ingestion, which the page marks for browser and client-side SDKs. Never ship the private key (maple_sk_…) in client code. Ingest keys can only write telemetry; they cannot read data back out.

Bundle size

The /client entry point leaves out the Node-only resource detector and platform helpers, so the base bundle ships the OTLP JSON exporter and Effect’s tracer and logger primitives (about 13 kB). The replay engine, rrweb included, sits behind a dynamic import in a separate chunk (about 360 kB). It is only fetched when replay is enabled and the session is sampled, so apps that set replay: { enabled: false } never download it. effect is a peer dependency; if your app already uses Effect on the client, the SDK adds only its own layer code.

Configuration reference

See the full configuration table on the Effect SDK page. In the browser, serviceName is required and every other option is optional.

Verify

  1. Load a page of your app that runs code inside the layer.
  2. In Maple, open Explore → Traces and filter on your service name. The layer exports spans every 5 seconds by default.
  3. With replay on, open Explore → Replays to see the session and its linked traces.

Troubleshooting

  • A console warning about sending without an ingest key. Pass ingestKey, or point endpoint at a proxy that adds the key.
  • 401 responses. The key is wrong, or it belongs to the other region. An EU key needs region: "eu".
  • Requests blocked by the browser. Check the Network tab. Maple’s ingest answers CORS preflights from any origin, so a blocked request usually means an ad blocker or a Content Security Policy without connect-src for the ingest host.
  • Nothing sent at all. With privacy.requireConsent: true, the SDK captures nothing until setConsent(true) is called.

Next steps