Facts reviewed: 2026-10-21


Add page views and custom events with `@owleye/analytics`. Start with one script tag, or choose npm below for a bundled JavaScript app. Use only one installation method. See the [CDN guide](/docs/cdn/) for more options and the [WordPress guide](/docs/wordpress/) for a CMS site.

## 1. Create a site

Open the [console](https://app.owleye.dev/), create an app, and copy its **Tracking ID** (`owl_…`). This public ID belongs in browser code; secret API keys do not.

Allowed domains are optional. An empty list allows any valid browser origin; to restrict collection, add each production and preview hostname in app Settings. Matching uses hostnames, so it does not distinguish HTTP from HTTPS or one port from another. Multiple domains and explicit wildcard subdomains are supported.

For a restricted app, collecting from localhost or another loopback address also requires server-side loopback permission; adding it to the domain list alone does not enable it. Prefer a separate test app or `mock: true` during development.

## 2. Add one script tag

Add this to your HTML after your lawful-basis or consent decision permits analytics, replacing the placeholder with your Tracking ID:

```html
<script
  defer
  src="https://cdn.jsdelivr.net/npm/@owleye/analytics@1.0.3/dist/owleye.analytics.iife.js"
  data-owleye-id="your-tracking-id"
></script>
```

This records page views and pathname navigation. For dashboard-managed click, form and visibility rules, add the optional rules collector from the [CDN guide](/docs/cdn/). Preserve your existing consent gate; a static script tag starts collection when loaded, subject to Global Privacy Control.

### Or use npm for a bundled app


### npm

```sh
npm install @owleye/analytics@1.0.3
```


### pnpm

```sh
pnpm add @owleye/analytics@1.0.3
```


### Bun

```sh
bun add @owleye/analytics@1.0.3
```




Run this once in your browser entrypoint, after your site's lawful-basis or consent decision allows analytics to begin:

```ts
import { useAnalytics } from "@owleye/analytics";
import { trackRules } from "@owleye/analytics/rules";

const analytics = useAnalytics("your-site-id");
const rules = trackRules("your-site-id");

// Call both during app teardown or when permission is withdrawn.
function stopAnalytics() {
  rules.stop();
  analytics.stop();
}
```

Page analytics records the initial page view and SPA route changes automatically. The rules entrypoint is optional and adds its own code; the core SDK alone does not load console rules. Call `trackRules()` only when tracking may start. Its controller stops permanently; create a new one if permission is granted again.

## 3. Verify the request before trusting the chart

The SDK connects to Owleye Analytics automatically. No `server` option is needed.

Open your browser's Network panel and reload the page. Look for `POST /v1/events` and, if enabled, `GET /v1/rules`. The payload should contain your site ID and no unexpected query or fragment. Check the ingestion response's `accepted` count and `status`. An HTTP 202 with `accepted: 0` and `status: "monthly_event_limit_reached"` means the allowance prevented storage. Global Privacy Control is checked by the SDK before sending; the API does not suppress requests based on privacy-signal headers.

Use the Events page in the console and the correct time range to check delivery. Your browser's Global Privacy Control setting can intentionally prevent collection. Do not disable your visitors' privacy choices to make a graph move.

## 4. Add the button you actually care about

Suppose the existing button has `id="signup-button"`. In Rules, create a click rule named `signup_clicked` with selector `#signup-button`, leave text capture off, and enable it. Reload the test page so the rules entrypoint fetches the new catalog, then click the button.

That is the useful part: later changes to supported rules happen in the console. You need a site deploy only if the initial SDK or suitable selector is missing, or the event needs new application code. Open tabs retain their loaded catalog until reinitialization.

Avoid selectors tied to generated styling classes. Do not capture input values, account names, email addresses, or free text merely because a DOM selector can reach them.

## Something missing?

| Symptom | Check |
| --- | --- |
| No request | Client initialization, privacy signals, content blockers, and `mock` mode |
| 400 | Public site ID, event name, field counts, and payload types |
| 401 on secret ingestion | API key validity, expiration, and write scope |
| 403 | Allowed domain, site scope, and whether tracking is paused |
| 429 | Rate limits and the `Retry-After` header |
| 202 with zero accepted | Monthly-limit status in the response |
| Rule changed but nothing happened | Reload after saving; confirm the selector matches the rendered element |

[React and Next.js setup](/docs/react-nextjs/) · [Nuxt setup](/docs/nuxt/) · [Full SDK reference](/docs/sdk/)