Up and tracking in 60 seconds.
Paste the snippet. Refresh your site. Watch events stream into the dashboard. Everything you need — install, config, API, plugins — is on this page.
Install the SDK
Two ways to install — pick the one that matches your stack. Both ship the same framework-agnostic core (@observejs/browser), ~10KB gzipped, zero dependencies.
Option A — Script tag (any site)
<script> window.__OBSERVE_ID = "obs_pk_live_xxx"; import("/observe.js"); </script>
Option B — npm package
npm install @observejs/browser # Framework wrappers npm install @observejs/react # React npm install @observejs/nextjs # Next.js npm install @observejs/vue # Vue
Initialize once at bootstrap
Call Observe.init() once — the SDK is idempotent, SSR-safe (no-ops on the server), and never throws into your app.
import { Observe } from "@observejs/browser"; Observe.init({ apiKey: "obs_pk_live_xxx", environment: "production", }); Observe.page(); Observe.identify("user_123", { email: "ada@example.com" }); Observe.track("Signup Completed", { plan: "pro" });
Configuration options
All options accepted by Observe.init(config). Only apiKey is required.
| Option | Type | Default | Description |
|---|---|---|---|
| apiKey | string ★ | — | Project tracking key (publishable). |
| apiUrl | string | observejs.lovable.app/api/public | Ingestion base URL. |
| environment | string | "production" | e.g. "production", "staging", "dev". |
| release | string | — | App version / git SHA for source maps. |
| debug | boolean | false | Verbose console logging. |
| batchSize | number | 20 | Events queued before auto-flush. |
| flushInterval | number ms | 5000 | Periodic auto-flush interval. |
| offlineQueue | boolean | true | Persist queue to localStorage. |
| sampling | 0..1 | 1 | Drop events outside the sample rate. |
| sessionTimeout | number ms | 1_800_000 | Inactivity before new session. |
| ignoreUrls | (string|RegExp)[] | [] | Drop events whose url matches. |
| ignoreSelectors | string[] | [] | Selectors auto-track skips. |
| globalContext | object | {} | Static props merged into every event. |
| plugins | Plugin[] | [] | Plugins registered at init. |
| enabled | boolean | true | Master kill-switch (opt-out flows). |
| fetchImpl | typeof fetch | globalThis.fetch | Override fetch (SSR / tests). |
★ required
API reference
The public surface of the Observe singleton.
Observe.track(name, properties?)
Record a product event. Use "Object Action" title-case names.
Observe.track("Checkout Completed", { orderId: order.id, total: order.total, currency: "USD", });
Observe.page(properties?)
Record a pageview. Auto-captures URL, path, referrer, and sticky UTM params.
Observe.identify(userId, traits?)
Bind the anonymous visitor to a known user. Traits persist across sessions.
Observe.identify(user.id, { email: user.email, plan: user.plan, createdAt: user.createdAt, });
Observe.alias, group, setUserProperties, reset
// Merge an anonymous visitor into a user Observe.alias("user_123"); // Associate a user with an account/org Observe.group("acme_corp", { plan: "enterprise" }); // Update traits without re-identifying Observe.setUserProperties({ plan: "enterprise" }); // Clear identity on logout Observe.reset();
Observe.flush()
Force-flush the queue. Returns a Promise. Useful before redirects or logout.
await Observe.flush(); window.location.href = "https://billing.stripe.com/...";
captureException, captureMessage, setGlobalContext, shutdown
Observe.captureException(err, { component: "Checkout" }); Observe.captureMessage("Payment retried", "warn"); Observe.setGlobalContext({ tenant: "acme" }); await Observe.shutdown();
Framework recipes
Next.js (App Router)
import { ObserveProvider } from "@observejs/nextjs"; export default function RootLayout({ children }) { return ( <ObserveProvider apiKey={process.env.NEXT_PUBLIC_OBSERVE_API_KEY!}> {children} </ObserveProvider> ); }
React / Vite
import { ObserveProvider } from "@observejs/react"; <ObserveProvider apiKey={import.meta.env.VITE_OBSERVE_KEY}> <App /> </ObserveProvider>
Vue 3
import { ObservePlugin } from "@observejs/vue"; app.use(ObservePlugin, { apiKey: "obs_pk_live_xxx" });
TanStack Router
const { location } = useRouterState(); useEffect(() => Observe.page({ path: location.pathname }), [location.pathname]);
Event schema
Every event shipped by the SDK carries this shape:
{
eventId: "uuid",
eventName: "Signup Completed",
type: "track",
timestamp: "2026-06-27T12:34:56.789Z",
sessionId, visitorId, deviceId, userId,
projectKey, environment, release,
url, path, referrer,
utm: { source, medium, campaign, term, content },
device: {
browser, browserVersion, os, osVersion,
device: "desktop" | "mobile" | "tablet",
screen: { width, height, dpr },
language, timezone, userAgent,
},
level: "info" | "warn" | "error",
metadata: { ...globalContext },
properties: { ...yourProps },
sdk: { name, version, api: "v1" }
}Plugins
The SDK is plugin-driven. Auto-track, replay, heatmaps, errors, network, performance, and AI insights are all plugins that share the same contract — no core changes needed.
import { Observe, definePlugin } from "@observejs/browser"; const consoleSpy = definePlugin({ name: "console-spy", setup(ctx) { const off = ctx.on("event", (e) => ctx.logger.debug(e.eventName, e.properties)); return () => off(); }, }); Observe.init({ apiKey: "obs_pk_live_xxx", plugins: [consoleSpy] });
Available hooks: init, event, beforeSend, page, identify, flush, shutdown. A throwing plugin is isolated — it can't crash the host app or the SDK.
Official plugins
@observejs/plugin-auto-track— clicks, forms, scroll, visibility@observejs/plugin-errors— window errors + unhandled rejections@observejs/plugin-network— fetch + XHR instrumentation@observejs/plugin-performance— Core Web Vitals@observejs/plugin-replay— session replay@observejs/plugin-heatmaps— click / scroll heatmaps@observejs/plugin-ai— AI-powered insights
Reliability & privacy
- Batching. Flushes when
batchSizeis reached, everyflushInterval, onvisibilitychange→hidden, and onpagehide. - Retries. Exponential backoff (500ms → 30s, 5 attempts) for network / 408 / 429 / 5xx. Permanent 4xx drops the batch.
- Offline. Queue mirrored to
localStorage(capped 500 events), restored on next load. - Beacon. Final flush during unload uses
navigator.sendBeacon. - Safety. Every public method is wrapped — internal errors never reach the host app.
- Privacy. No third-party cookies. IDs are first-party in
localStorage. Useenabled: falsefor opt-out flows.
Start seeing what your users
actually experience.
Install in under two minutes · no credit card required