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 for more options and the WordPress guide for a CMS site.
1. Create a site
Section titled “1. Create a site”Open the console, 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
Section titled “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:
<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. 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
Section titled “Or use npm for a bundled app”npm install @owleye/analytics@1.0.3pnpm add @owleye/analytics@1.0.3bun add @owleye/analytics@1.0.3Run this once in your browser entrypoint, after your site’s lawful-basis or consent decision allows analytics to begin:
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
Section titled “3. Verify the request before trusting the chart”The SDK connects to OwlEye 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
Section titled “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?
Section titled “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 |