Skip to content

CDN & plain HTML

Facts reviewed:

Use the standalone browser bundles for plain HTML, CMS templates, and site builders. SDK @owleye/analytics@1.0.3 includes three individual bundles and the full bundle described below. Pin the version, test upgrades, and update the URL deliberately.

SDK 1.0.3’s standalone analytics script is approximately 7.8 KB gzip (gzip level 9; transfer size varies by CDN compression). Rules and performance are optional downloads. CI checks bundle-size budgets; this is not a sub-3 KB tracker.

Copy your app’s Tracking ID from the console. Add this once to your site’s shared <head> or footer template:

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

The script starts page analytics automatically, including supported SPA navigation. Do not also initialize a package-manager installation for the same page. The hosted endpoint is already configured.

Alternatively, serve the same versioned file from your own /vendor/ directory. This changes where the JavaScript is downloaded; events still go to OwlEye. For stricter supply-chain controls, the package includes dist/integrity.json with SHA-384 hashes. Use the hash for the exact downloaded file with integrity and crossorigin="anonymous".

CDN campaigns, custom data, rules, and performance

Section titled “CDN campaigns, custom data, rules, and performance”

The CDN globals are window.OwlEyeAnalytics, window.OwlEyeRules, and window.OwlEyePerformance. Each is available only after its corresponding bundle loads successfully. There is no single window.OwlEye object and no pre-load queue.

Load these scripts once with the same Tracking ID and endpoint. Keep defer (rather than async) when a later script depends on their order:

<script
defer
src="https://cdn.jsdelivr.net/npm/@owleye/analytics@1.0.3/dist/owleye.analytics.iife.js"
data-owleye-id="owl_your_tracking_id"
></script>
<!-- Optional: enabled rules configured in Console -->
<script
defer
src="https://cdn.jsdelivr.net/npm/@owleye/analytics@1.0.3/dist/owleye.rules.iife.js"
data-owleye-id="owl_your_tracking_id"
></script>
<!-- Optional: automatic Web Vitals and manual timings -->
<script
defer
src="https://cdn.jsdelivr.net/npm/@owleye/analytics@1.0.3/dist/owleye.performance.iife.js"
data-owleye-id="owl_your_tracking_id"
></script>
<script defer src="/analytics-events.js"></script>

SDK 1.0.3 automatically captures bounded utm_source, utm_medium, utm_campaign, utm_term, and utm_content values from the current page. Arbitrary query parameters and URL fragments remain excluded by default. Use broad, non-personal campaign labels; never put emails, recipient IDs, secrets, or click IDs in campaign fields. No campaign settings or browser storage are required, and attribution is not persisted after the tags disappear from the URL.

In your own analytics-events.js, use the initialized globals directly:

const analytics = window.OwlEyeAnalytics;
// Optional fields reused by custom events. Never put personal identifiers here.
analytics?.setGlobalRecords({
app_version: "1.0.0",
environment: "production",
});
// A manual custom event: values retain their string / number / boolean types.
document.getElementById("download-report")?.addEventListener("click", () => {
analytics?.track("report_downloaded", {
format: "pdf",
page_count: 12,
included_charts: true,
});
});
// Time your real asynchronous operation; no automatic page analytics needed.
async function loadReport() {
const end = window.OwlEyePerformance?.start("report_load", { format: "pdf" });
try {
const response = await fetch("/reports/latest");
end?.({ ok: response.ok, status_code: response.status });
return response;
} catch (error) {
end?.({ ok: false, failure: "network" });
throw error;
}
}

For an inline <script>, wrap setup code in a DOMContentLoaded listener: inline scripts execute immediately even when preceded by deferred external scripts. For a dynamically injected CDN script, wait for its load event before using the global. Optional chaining prevents an exception when a script is blocked; it drops the call and does not retry it. If scripts can arrive later, read the global at call time instead of caching an initially undefined reference.

Rules need no manual JS handler: configure and enable a rule in Console, load the rules bundle, and trigger the matching interaction. Configure its custom-property selectors there too. Do not add a manual .track() for the same action unless you intend two events. A JavaScript enrichRule callback is a module-import option; it cannot be passed as a CDN HTML attribute. Use manual .track() for computed custom data in plain JavaScript.

The performance bundle starts Web Vitals automatically. Manual spans use window.OwlEyePerformance.start() as shown above. To stop Web Vitals, call window.OwlEyePerformance?.observeVitals().stop(); to stop rules, call window.OwlEyeRules?.stop(). The performance global itself has no .stop(). Only load optional bundles after any required tracking-permission decision; data-owleye-auto-start="false" delays the analytics bundle alone.

  • Custom events: 10 fields total, combining global and event-specific fields; event-specific values override a matching global key.
  • Rules: 10 fields total, combining configured DOM captures and optional module-based enrichment. Configured captures take precedence.
  • Performance: 10 fields at start and 10 at end. Built-in timing fields are separate; the entire event data still must fit within 16 KiB.
  • Values: string, finite number, or boolean only. No null, undefined, arrays, nested objects, Date instances, bigint, functions, NaN, or infinities. Missing rule elements are omitted. Use an ISO string or numeric timestamp for a date; use named flat fields for structured concepts. JSON encoded as a string stays an opaque string, not queryable nested properties.

Invalid manual data is logged and dropped by the SDK without throwing into your application. The API also validates custom properties. Check the request to /v1/events and its accepted count; look for the event in Console’s Events page, UTM attribution in Campaigns, and automatic measurements in Web Vitals.

Use dist/owleye.full.iife.js when you want page analytics, Console-managed rules, Web Vitals, and manual performance timers together. It is one self-contained JavaScript payload with shared code, not a loader that downloads three more scripts. It still makes the normal rules and event API requests. Keep the smaller individual bundles when you only need some features.

Load the full bundle from the version-pinned CDN URL:

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

The npm export is @owleye/analytics/cdn/full. The full bundle is included starting with SDK 1.0.1. The CDN URL becomes available after that version is published to npm. You can also copy the built file to your own static assets. Its SHA-384 value is included in dist/integrity.json alongside the individual bundles.

All documented script attributes apply to the full bundle. They are read from the script tag and passed to all included modules. Analytics-only options (autoStart, autoTrackPageviews, trackQueryChanges, and trackHashChanges) still affect only page analytics. GPC, URL capture, mock, debug, and endpoint settings apply to all three modules. Function-based enrichRule still requires the module API; it cannot be supplied as an HTML attribute.

After the script loads, use the same globals as the individual bundles:

window.OwlEyeAnalytics.track("signup_completed", { plan: "free" });
const end = window.OwlEyePerformance.start("report_generation");
// Call after the operation completes:
end({ status: "ok" });

Rules and Web Vitals start on load. data-owleye-auto-start="false" only delays page analytics; it is not a gate for the whole bundle. If all collection must wait, load the full script only after that decision. Loading full plus individual bundles does not duplicate automatic trackers for the same Tracking ID and endpoint, but normally choose one approach to avoid redundant downloads.

To clean up the automatic trackers:

window.OwlEyeAnalytics.stop();
window.OwlEyeRules.stop();
window.OwlEyePerformance.observeVitals().stop();

There is no aggregate OwlEyeFull controller or global performance stop(). Do not invoke previously returned custom timer end functions after collection permission is withdrawn. Stopping trackers does not retract requests already started.

Remove legacy data-owleye-capture-campaigns attributes when upgrading; they are ignored, even when set to false. Review visitor notices for automatic campaign capture, and keep the frontend SDK aligned with the matching API deployment.

Attribute Default Meaning
data-owleye-id Required Public app Tracking ID
data-owleye-server Hosted service Custom API origin or API-base override; requires a correctly configured endpoint
data-owleye-auto-start true Start page analytics immediately; only affects the analytics bundle
data-owleye-capture-query false Include the full query string; review its contents first
data-owleye-capture-hash false Include URL fragments
data-owleye-respect-global-privacy-control true Stop tracking for Global Privacy Control in the SDK; the API does not filter the header
data-owleye-auto-track-pageviews true Disable automatic views for manual/memory-router navigation
data-owleye-track-query-changes false Count query-only navigation; does not enable query capture
data-owleye-track-hash-changes false Count hash-only navigation; does not enable fragment capture
data-owleye-debug false Explain delivery decisions and numeric acknowledgements without payloads
data-owleye-mock false Do not send requests

Use explicit "true" or "false" values. data-owleye-server is an advanced endpoint override, not a hosted setup requirement or a supported first-party proxy product.

Endpoint overrides are not a proxy product

Section titled “Endpoint overrides are not a proxy product”

Hosting the script yourself changes where JavaScript is downloaded. data-owleye-server changes where API requests are sent. Neither guarantees blocker recovery or makes a generic rewrite preserve visitor identity and geography.

OwlEye trusts a forwarded client address only when the immediate peer is configured as a trusted proxy by the API operator. That operator must control the hop and sanitize client-supplied forwarding headers. Hosted customers cannot establish this trust merely by changing a script attribute. An untrusted proxy can make visitors share the proxy address, distorting visitor/session counts, geography and rate limits.

A proxy also adds latency, bandwidth cost, possible request logging and another failure point. Keep the normal hosted origin unless the routing and trust chain have been explicitly tested with the service operator. Preserve permission decisions and Global Privacy Control behavior; endpoint changes are not a mechanism for overriding visitor choices. OwlEye does not currently offer a managed first-party proxy setup.

If your site must wait for permission, load scripts only after that decision. For page analytics alone, data-owleye-auto-start="false" creates a controller without starting collection; call window.OwlEyeAnalytics?.start() when allowed, and .stop() on withdrawal.

The rules and performance scripts start their respective modules on load. Delaying the analytics script alone does not delay them. For precise lifecycle ownership, use the package entrypoints instead. Web Vitals stop() discards pending measurements and removes observers/listeners; it does not retract requests already started. Hidden/page-exit events flush lifecycle values only while observation remains active.

Each successfully initialized bundle exposes a controller:

Global Methods
window.OwlEyeAnalytics track(), pageview(), setGlobalRecords(), start(), stop()
window.OwlEyeRules stop()
window.OwlEyePerformance start() for a manual timer, observeVitals() for the shared Web Vitals controller

For example, after the performance bundle loads:

const end = window.OwlEyePerformance?.start("report_generation");
// After your operation finishes:
end?.({ status: "ok" });

observeVitals() returns the existing active observer when called after bundle initialization; retain that controller and call its stop() during teardown. The performance global itself has no stop() method. Rules cannot be restarted through their stopped controller; module imports give explicit control over reinitialization.

The script bundles do not install global TypeScript declarations in your app. If TypeScript code accesses these globals, install the package for its types and add an application declaration file:

import type { AnalyticsController } from "@owleye/analytics";
import type { RulesController } from "@owleye/analytics/rules";
import type { PerfController } from "@owleye/analytics/performance";
declare global {
interface Window {
OwlEyeAnalytics?: AnalyticsController;
OwlEyeRules?: RulesController;
OwlEyePerformance?: PerfController;
}
}

These optional properties reflect that a script can be blocked or not loaded yet. Type-only imports do not initialize another SDK copy. Prefer regular module imports when using a bundler.

Check browser Network requests to /v1/events; confirm accepted is greater than zero. If requests are missing, check content blockers, browser privacy signals, CSP, and whether the script actually loaded. If you restrict allowed origins in app Settings, include every intended origin. See WordPress for CMS-specific placement and caching checks.

For memory routers, disable automatic pageviews and call window.OwlEyeAnalytics.pageview({ path, title }) once per committed screen, including the initial screen. See SPA and memory routers.