Integration Guide

Detailed setup instructions for different frameworks and platforms.

First-site tutorial

Steps

  1. Open your site in the dashboard, then choose Setup & tracking code from the site menu.
  2. Press Copy to take the tracking code. Copying it does not install anything, and Klar does not count it as done.
  3. Paste the code into the head of every page of your site, then publish the change.
  4. Open your own site in another tab so that it sends a visit to Klar.
  5. Back in the setup sheet, press Check for a first pageview. The check only reads, and it runs only when you press the button.
  6. Observed means Klar stored a visit from your site. Not observed means nothing has arrived yet: imported history, older visits and visits to other sites do not count, so open your site and check again. Unavailable means the check could not run, which does not mean your installation is wrong.

Static HTML

The simplest setup — just add the script tag before </body>:

<!DOCTYPE html>
<html>
<head>
  <title>My Site</title>
</head>
<body>
  <h1>Hello World</h1>
  
  <script 
    src="https://klar.alphabros.eu/klar.v2.js" 
    data-site-id="your-site-id"
    data-key="your-api-key"
    defer
  ></script>
</body>
</html>

React / Next.js

Next.js (App Router)

Add to your root layout.tsx:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <script 
          src="https://klar.alphabros.eu/klar.v2.js" 
          data-site-id="your-site-id"
          data-key="your-api-key"
          defer
        />
      </body>
    </html>
  )
}

React (Vite / CRA)

Add to index.html or use a component:

import { useEffect } from 'react';

function Analytics() {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://klar.alphabros.eu/klar.v2.js';
    script.setAttribute('data-site-id', 'your-site-id');
    script.setAttribute('data-key', 'your-api-key');
    script.defer = true;
    document.body.appendChild(script);
  }, []);
  
  return null;
}

export default Analytics;

Vue / Nuxt

Nuxt 3

Use the useHead composable:

// app.vue or layouts/default.vue
<script setup>
useHead({
  script: [
    {
      src: 'https://klar.alphabros.eu/klar.v2.js',
      'data-site-id': 'your-site-id',
      'data-key': 'your-api-key',
      defer: true
    }
  ]
})
</script>

Vue 3

Add to index.html or create a plugin:

// plugins/klar.ts
export default {
  install: (app: any) => {
    const script = document.createElement('script');
    script.src = 'https://klar.alphabros.eu/klar.v2.js';
    script.setAttribute('data-site-id', 'your-site-id');
    script.setAttribute('data-key', 'your-api-key');
    script.defer = true;
    document.body.appendChild(script);
  }
};

Svelte / SvelteKit

SvelteKit

Add to src/app.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%sveltekit.assets%/favicon.png" />
    <meta name="viewport" content="width=device-width" />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
    <script 
      src="https://klar.alphabros.eu/klar.v2.js" 
      data-site-id="your-site-id"
      data-key="your-api-key"
      defer
    ></script>
  </body>
</html>

Angular

Add the script tag to src/index.html, before </body>. The Angular router uses the History API, so route changes are tracked automatically — no service or router subscription needed:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <base href="/" />
  </head>
  <body>
    <app-root></app-root>
    <script 
      src="https://klar.alphabros.eu/klar.v2.js" 
      data-site-id="your-site-id"
      data-key="your-api-key"
      defer
    ></script>
  </body>
</html>

Custom events work from any component or service: window.klar?.track('signup'). If you use hash-based routing (withHashLocation()), call window.klar?.page(path) yourself on NavigationEnd: Klar detects navigations by pathname, which hash routes never change.

WordPress

Add to your theme's footer.php before <?php wp_footer(); ?>:

<script 
  src="https://klar.alphabros.eu/klar.v2.js" 
  data-site-id="your-site-id"
  data-key="your-api-key"
  defer
></script>
<?php wp_footer(); ?>

Or use a plugin like "Insert Headers and Footers" to add the script.

Ghost

Go to Settings → Code injection and paste in the Site Footer field:

<script 
  src="https://klar.alphabros.eu/klar.v2.js" 
  data-site-id="your-site-id"
  data-key="your-api-key"
  defer
></script>

Webflow

In your project settings, go to Custom Code → Footer Code and add:

<script 
  src="https://klar.alphabros.eu/klar.v2.js" 
  data-site-id="your-site-id"
  data-key="your-api-key"
  defer
></script>

Verifying Installation

After installing, verify Klar is working:

  1. Visit your website
  2. Open browser DevTools (F12)
  3. Check the Network tab for a request to klar.v2.js
  4. Look for a POST request to /api/collect
  5. Check your Klar dashboard for the pageview

Excluding your own visits and test traffic

Klar stores nothing on a visitor's device, so there is no cookie to opt out with. The exclusion is a per-site setting on the server: a beacon that matches it is not recorded and does not count toward your monthly pageviews. Visitors who do not match are counted as usual.

  • IP addresses (excludeIps): an IPv4 or IPv6 address, or an IPv4 range from /16 to /32. Up to 20 entries.
  • Test and staging hosts (excludeHosts): a hostname such as staging.example.com or localhost. A beacon sent from that host or any of its subdomains is dropped. The site's own domain is refused. Up to 20 entries.

Site owners and admins set both with PUT /api/sites/<site id>/exclusions while signed in:

{ "excludeIps": ["203.0.113.7", "198.51.100.0/24"], "excludeHosts": ["staging.example.com"] }

Send { "addMyIp": true } to add the address you are connecting from, and { "excludeIps": [] } to clear a list. A field you leave out is unchanged. GET on the same path returns the current rules and yourIp. An excluded beacon still gets a 202 answer, so a page cannot tell it was excluded.

Note that a beacon is matched on the address it arrives from: someone on the same office network as an excluded address is excluded too, and your own visits from another network are counted until you add that address.

Native and Offline Apps

An iOS, Android, desktop or offline-first app can send activity to the same site and read it in the same dashboard. The @klar/collect SDK (the sdk/js folder of the repository) wraps the collection API: it queues events while the device is offline and sends them when the connection returns, with the time they were recorded.

import { createKlar } from '@klar/collect';

const klar = createKlar({
  endpoint: 'https://klar.alphabros.eu',
  siteId: 'YOUR-SITE-ID',
  storage: AsyncStorage, // optional: keeps the queue across restarts
});

await klar.pageview('/settings');
await klar.track('purchase', { plan: 'pro' }, '/checkout');

Events recorded offline

Each event is stamped with the time it happened (t, epoch milliseconds) and delivered after reconnect in the order it was recorded. Klar stores that time, so a delayed event lands on the day it happened. Klar accepts a timestamp up to 7 days old; the SDK drops an older event. A timestamp ahead of Klar's clock by a few minutes is treated as clock skew and set to the moment of receipt.

No persistent device identifier

The SDK stores no persistent device identifier. It does not create, read or send a device id, advertising id, install id or session id, and it sets no cookie. The only thing it can keep on the device is the queue of events not yet delivered, and that queue is emptied as they are delivered. Visitors are counted on the server from a hash that rotates every UTC day, so a person cannot be followed from one day to the next.

Collection API contract

Without the SDK, send POST https://klar.alphabros.eu/api/collect with a JSON body: s (site id), type (pageview or event), p (screen path), e and d (event name and data), t (when it happened) and data-key (the site's API key, if it has one). A 201 means recorded; 400 an unknown site or an invalid or too-old timestamp; 402 the plan limit; 403 a wrong key. Do not send a User-Agent that names a bot or a scripting client: such a request is acknowledged and not counted.

AI Crawlers and Agents

A visit whose User-Agent names a known AI crawler or AI agent is reported apart from your human visitors, in the AI agents view of the Sources card: each agent, its purpose, its visits and the pages AI agents fetch most. These visits never count as visitors, pageviews, bounces or goal conversions, and they do not use your quota.

The match reads the User-Agent only: Klar stores the agent name, its purpose, the page and the time of the visit, and nothing else. No cookie, no visitor or session identifier, no IP address and not even the User-Agent string is kept for an AI agent visit. The list below is versioned with the code (version 2026-10-27); each agent is matched when its token appears, case-insensitively, anywhere in the User-Agent.

AgentOperatorPurposeMatched on
ChatGPT-UserOpenAIOn-demand agentchatgpt-user
OAI-SearchBotOpenAISearch indexoai-searchbot
GPTBotOpenAITraininggptbot
Claude-UserAnthropicOn-demand agentclaude-user
Claude-SearchBotAnthropicSearch indexclaude-searchbot
ClaudeBotAnthropicTrainingclaudebot, claude-web, anthropic-ai
Perplexity-UserPerplexityOn-demand agentperplexity-user
PerplexityBotPerplexitySearch indexperplexitybot
Google-ExtendedGoogleTraininggoogle-extended
Meta-ExternalAgentMetaTrainingmeta-externalagent
Meta-ExternalFetcherMetaOn-demand agentmeta-externalfetcher
AmazonbotAmazonSearch indexamazonbot
Applebot-ExtendedAppleTrainingapplebot-extended
BytespiderByteDanceTrainingbytespider
CCBotCommon CrawlTrainingccbot
cohere-aiCohereTrainingcohere-ai
DiffbotDiffbotTrainingdiffbot
DuckAssistBotDuckDuckGoSearch indexduckassistbot
ImagesiftBotHiveTrainingimagesiftbot
MistralAI-UserMistral AIOn-demand agentmistralai-user
OmgiliWebz.ioTrainingomgilibot, omgili
TimpibotTimpiSearch indextimpibot
YouBotYou.comSearch indexyoubot

Training crawlers fetch pages to build a model's training data, search index crawlers index pages for an AI search product, and an on-demand agent fetches a page because a person asked an assistant about it. An AI product that sends an ordinary browser User-Agent is not recognised and is counted as a visit.

Automated Traffic and Bots

Klar drops a visit before it is counted when the browser announces itself as a known automated client, or when the visit's referrer is a known referral-spam domain. Dropped visits never count as visitors, pageviews, bounces or goal conversions, do not appear in any report and do not count toward your plan's pageview quota; the tracker still gets a normal success answer. Both lists are versioned with the code and the filter is always on.

What is filtered

  • Search engine and SEO crawlers, for example Googlebot, Bingbot, DuckDuckBot, Baiduspider, YandexBot, AhrefsBot and SemrushBot
  • AI crawlers and agents are not dropped: they are stored as AI agent visits and reported apart, see above
  • Link-preview fetchers, for example facebookexternalhit, Twitterbot, LinkedInBot, Slackbot, Discordbot and the WhatsApp preview
  • Uptime and speed monitors, for example UptimeRobot, Pingdom, StatusCake and Lighthouse
  • Scripting HTTP clients, for example curl, wget, python-requests, Go-http-client, Java and axios. Not filtered when the request is server-side collection: it carries the site's data key and no Referer or Origin header, see the REST API
  • Any User-Agent that contains the whole word bot, crawler, spider or scraper

What is not filtered

  • A bot that sends an ordinary browser User-Agent. The filter reads what the client announces; it does not fingerprint behavior, IP ranges or JavaScript execution, so deliberately disguised traffic is counted.
  • Headless browsers such as headless Chrome, Playwright and Puppeteer. They run the tracker like a person would, and automated tests use them, so they are counted.
  • Visits with an empty User-Agent. Some privacy tools remove it, so these are counted rather than risk dropping real people.
  • Most crawlers never run JavaScript at all, so they never load the tracker; the filter is a second line of defense for the ones that do.

Real browsers, including mobile browsers and in-app browsers, are never filtered. The list is matched by name only, so it can lag behind brand-new bots; a bot missing from it is counted until the list is updated.

Referral spam

Referral spam is traffic sent to your site only so that its domain shows up in your reports: "free SEO" tools, fake share buttons and traffic resellers. It comes with an ordinary browser User-Agent, so the bot list above cannot see it. Klar drops a visit whose referrer host is one of the domains below, or any subdomain of one (version 2026-10-28). The match reads the referrer host only: not the path, the query string, the IP address or any behavior.

semalt.com, buttons-for-website.com, buttons-for-your-website.com, best-seo-offer.com, best-seo-solution.com, seo-platform.com, free-share-buttons.com, simple-share-buttons.com, get-free-social-traffic.com, free-social-buttons.com, free-floating-buttons.com, share-buttons.xyz, 100dollars-seo.com, o-o-6-o-o.com, site-auditor.online, seo-2-0.com, top1-seo-service.com, darodar.com, ilovevitaly.com, ilovevitaly.ru, blackhatworth.com, hulfingtonpost.com, priceg.com, econom.co, cenoval.ru, savetubevideo.com, kambasoft.com, bestwebsitesawards.com, theguardlan.com, 7makemoneyonline.com, floating-share-buttons.com, social-buttons.com, event-tracking.com, traffic2money.com, trafficmonetizer.org, rankings-analytics.com, success-seo.com, law-enforcement-bot-ff.com, law-enforcement-ee.com, bot-traffic.icu, bot-traffic.xyz, keywords-monitoring-your-success.com, boom.ltd, trafficbot.life, ranksonic.org, ranksonic.info, traffic-cash.xyz, news.grets.store, grets.store

What is not filtered: a visit with no referrer (a direct visit is always counted), a referrer whose domain is not on the list, and a spam domain that merely contains a listed name. A new spam domain is counted until the list is updated; the list is maintained in the Klar repository, so every change to it is versioned.

Troubleshooting

Script not loading

  • Check the script URL is correct
  • Check that your Klar instance is reachable: https://klar.alphabros.eu/klar.v2.js should open in your browser. On the hosted service there is nothing to run on your side; if you run your own instance, make sure it is up and that the script URL points to it
  • Check for Content Security Policy (CSP) blocking the script

Pageviews not appearing

  • Verify data-site-id matches your site ID in Klar
  • Verify data-key is your site's API key — copy the full snippet from your site's dashboard; a missing or wrong key gets a 403 from /api/collect
  • In DevTools, the POST /api/collect request must go to https://klar.alphabros.eu, not to your own site
  • Testing from localhost? Klar only accepts events from the domain (or aliases) registered for the site, so local pages get 403 Invalid referer — test on the real domain
  • Check browser console for errors
  • Ensure ad-blockers aren't blocking requests
  • Check if DNT/GPC honoring is enabled (data-honor-dnt="true") and the visitor's browser sends a signal — honoring is opt-in, off by default

SPA navigation not tracked

  • Klar automatically tracks pushState and back/forward (popstate) when the pathname changes; replaceState is deliberately not tracked
  • Hash-based routes (#/page) are not auto-tracked: call klar.page(path) on each route change (SPA guide)
  • If using a custom router, manually call klar.trackPageview()

Need Help?

Check out these resources: