Skip to content

SDK

OwlEye’s browser SDK has a tiny mental model: initialize it once, call track for product moments you care about, and call stop when you tear it down. It handles page views and SPA navigation for you. No cookie confetti.

The SDK currently ships as the @owleye/analytics workspace package in the OwlEye monorepo. Build it from a source checkout while the public npm package is being prepared:

Terminal window
pnpm --filter @owleye/analytics build

After the public package is published, installation will be:

Terminal window
pnpm add @owleye/analytics

Run this once in a browser entrypoint. In an SSR framework, put it in client-only code.

import { useAnalytics } from "@owleye/analytics";
const analytics = useAnalytics("your-site-id", {
server: "https://api.owleye.dev",
});
analytics.track("signup_clicked", {
annual: true,
plan: "starter",
seats: 3,
});

useAnalytics immediately starts tracking and returns an AnalyticsController. It records the initial page view, SPA route changes, and the time spent on each page segment. Call analytics.stop() during teardown or withdrawal; it immediately disables tracking, discards the active page segment without sending it, and removes its browser listeners. stop() and start() are safe to call more than once.

// For a component or app lifecycle cleanup function:
return () => analytics.stop();

Create one controller per site and reuse it. Creating a controller for every click would technically work, but so would wearing five watches.

Use stable, descriptive names such as signup_completed or report_exported. Pass one flat object in normal use:

analytics.track("report_exported", {
format: "csv",
rows: 842,
scheduled: false,
});
  • An event accepts at most five custom fields in total.
  • Event names are trimmed and must be 1–128 UTF-8 bytes with no control characters.
  • Values can be strings, finite numbers, or booleans.
  • Arrays, nested objects, null, NaN, and infinite numbers are rejected with a TypeError.
  • The complete event data object is capped at 16 KiB before OwlEye starts a request.
  • Calling track while the controller is stopped is a no-op.

The flat payload is deliberate: fewer accidental identifiers, easier queries, less archaeological work for Future You.

Option Default What it does
server https://api.owleye.dev Accepts an HTTP(S) API base or same-origin path such as /analytics.
autoStart true Starts automatic page analytics on initialization; set false to wait for an external decision.
respectDoNotTrack true Disables tracking when the browser sends a Do Not Track signal.
respectGlobalPrivacyControl true Disables tracking when the browser exposes an enabled Global Privacy Control signal.
captureCampaigns false Includes only bounded utm_source, utm_medium, and utm_campaign values.
captureQuery false Includes the full URL query string. Use only after checking what your app puts there.
captureHash false Includes URL fragments. Enable only when the fragment is useful and safe.
debug false Prints payloads and transport decisions in the browser console.
mock false Sends no network requests. Pair it with debug to inspect payloads locally.

Query strings and URL fragments are excluded by default because they often contain tokens, search terms, email addresses, and other things analytics did not need to know. Prefer captureCampaigns: true when you only need campaign attribution: it keeps arbitrary parameters excluded and bounds each accepted UTM value. captureQuery takes precedence if both options are enabled and deliberately exposes the query string, subject to the ingestion field-size limit.

Site IDs are trimmed and must contain 1–128 ASCII letters, numbers, dots, underscores, or hyphens. The SDK rejects invalid IDs and API bases during setup, before installing listeners or making a request.

Page analytics, interaction rules, and performance measurements are separate entrypoints. Import only what the site uses; a bundler does not need to ship rule tracking just because you measure a checkout.

import { trackRules } from "@owleye/analytics/rules";
const rules = trackRules("your-site-id", {
server: "https://api.owleye.dev",
});
// During app teardown:
rules.stop();

The browser fetches enabled rules from GET /v1/rules. Click and submit rules use delegated event listeners; view rules use one IntersectionObserver plus a debounced child-list observer so targets inserted asynchronously are discovered without re-reporting handled elements. Page-scoped matches also rebuild after an SPA navigation. rules.stop() aborts an in-flight rule fetch, disconnects both observers, restores shared navigation hooks, clears pending timers, and removes the listeners.

An optional synchronous enricher can attach up to three primitive fields. If it throws or returns invalid data, OwlEye still sends the base rule event.

const rules = trackRules("your-site-id", {
enrichRule(rule) {
return { experiment: "pricing_v2", rule: rule.name };
},
});

Rule matches apply sample_rate independently and do not store a sampling ID in the browser. Authenticated rule management remains behind the site-scoped console API; the public SDK only reads the enabled set.

import { trackPerf } from "@owleye/analytics/performance";
const perfTracker = trackPerf("your-site-id");
const endCheckout = perfTracker.start("checkout_submit", {
cart_items: 3,
source: "checkout",
});
try {
await submitCheckout();
endCheckout({ status: "ok" });
} catch (error) {
endCheckout({ status: "error" });
throw error;
}

start returns an end function for that measurement. The end function is idempotent, so a second call is ignored. Start and end data accept at most three primitive fields in total per call.

The package also builds a dependency-free IIFE for sites that do not use a JavaScript bundler. Copy dist/owleye.analytics.iife.js to your own static assets, then load it like this:

<script
defer
src="/vendor/owleye.analytics.iife.js"
data-owleye-id="your-site-id"
data-owleye-server="https://api.owleye.dev"
></script>

Equivalent rule and performance builds are emitted as owleye.rules.iife.js and owleye.performance.iife.js. Prefer the ESM entrypoints for application code because they give bundlers the clearest tree-shaking boundary.

The public SDK does not write cookies, local storage, or session storage. Event requests omit browser credentials and send no referrer header. These claims apply to public visitor tracking; the authenticated OwlEye console can use secure, HttpOnly session cookies.

The SDK respects both Do Not Track and Global Privacy Control by default. The API independently honors those request signals. Cookie-free tracking can still require a lawful basis, notice, or consent depending on your use and jurisdiction. To wait for your consent manager or another documented decision, create an inert controller and start it later:

const analytics = useAnalytics("your-site-id", {
autoStart: false,
server: "https://api.owleye.dev",
});
analytics.start(); // only after the applicable decision
analytics.stop(); // on withdrawal or when processing must stop

See privacy and compliance readiness for retention, pseudonymous identifiers, rights handling, and operator responsibilities.

Error tracking is not part of the current public SDK surface.