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.
Install
Section titled “Install”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:
pnpm --filter @owleye/analytics buildAfter the public package is published, installation will be:
pnpm add @owleye/analyticsStart in about 30 seconds
Section titled “Start in about 30 seconds”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.
Track a custom event
Section titled “Track a custom event”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 aTypeError. - The complete event data object is capped at 16 KiB before OwlEye starts a request.
- Calling
trackwhile the controller is stopped is a no-op.
The flat payload is deliberate: fewer accidental identifiers, easier queries, less archaeological work for Future You.
Configuration
Section titled “Configuration”| 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.
Optional, tree-shakeable entrypoints
Section titled “Optional, tree-shakeable entrypoints”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.
Rule tracking
Section titled “Rule tracking”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.
Performance tracking
Section titled “Performance tracking”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.
Plain script build
Section titled “Plain script build”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.
Browser privacy defaults
Section titled “Browser privacy defaults”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 decisionanalytics.stop(); // on withdrawal or when processing must stopSee privacy and compliance readiness for retention, pseudonymous identifiers, rights handling, and operator responsibilities.
Error tracking is not part of the current public SDK surface.