close
Skip to content

Web Analytics

Kitbase tracks pageviews, sessions, clicks, scroll depth, outbound links, and users automatically — no cookies, no banners required. Install it once and the autocapture system does the rest; add custom events and user identification when you're ready.

Pick your installation path:

Script or SDK?

Both run the same engine. The tracking script is the npm SDK, pre-bundled and served from https://kitbase.dev/lite.js — use it when you don't have (or don't want) a build step. Use the npm SDK when you want typed APIs, tree-shaking, and framework integration.

How it works

  1. Capture — the script or SDK autocaptures pageviews, sessions, clicks, scroll depth, and outbound links in the browser. See the autocapture reference for every event and its payload.
  2. Filter — obvious non-human traffic (headless browsers, testing tools, HTTP clients, known crawlers) is discarded before it's ever sent, and the server-side attribution pipeline excludes anything flagged as a bot from human counts. No configuration needed.
  3. Enrich — the backend parses the User-Agent (device, browser, OS), derives geolocation from the IP (then drops the IP unless IP logging is enabled), and extracts UTM parameters. All enriched fields are first-class filter dimensions.
  4. Explore — metrics, breakdowns, and filters live in the Analytics dashboard; raw events in the Events view.

Reporting timezone

Every date boundary in your reports — where "today" starts, which day a visit at 00:30 belongs to, which hour a heatmap cell covers — is resolved in the project's reporting timezone. Set it per project under Settings → General → Reporting; it defaults to UTC.

It is deliberately a project setting rather than something each request decides. Because the server holds it, the dashboard, the API, the CLI, exports, and the Slack assistant all cut the same period the same way — so two people in different countries reading the same project see the same numbers, and a scheduled export matches the dashboard it came from.

Two things worth knowing before you change it:

  • Past numbers are restated, not lost. Traffic is stored as exact instants; the timezone only decides where the day boundaries fall. Moving from UTC to Africa/Cairo re-cuts yesterday at a different moment, so a daily figure can move. Moving it back restores the previous figures exactly.
  • Explicit dates follow it too. from=2026-08-01&to=2026-08-01 means that calendar day in the project's zone, not in the caller's.

Set it from the CLI with:

bash
kitbase settings update --reportingTimezone Africa/Cairo

Compare to an earlier period

Every analytics report that takes a date range answers for two periods in one call: the one you asked for, and an earlier one to measure it against.

You get a comparison whether or not you ask for one. Send none of the parameters below and a report compares against the previous period — the window of the same length immediately before your selection. Send one to compare against something else.

ParameterValueNotes
comparePresetprevious_period · previous_week · previous_month · previous_quarter · previous_yearDefaults to previous_period. Wins over compareFrom/compareTo when both are sent
compareFromYYYY-MM-DDStart of a custom window, inclusive. Must be sent with compareTo
compareToYYYY-MM-DDEnd of a custom window, inclusive. Must be sent with compareFrom

Both windows are resolved in the project's reporting timezone (above), so a comparison is always cut the same way the period it compares against is. A custom pair may cover a different length than your selection; the presets are described next.

What each preset means

previous_period keeps the length of your selection and ends exactly where it starts, so the two windows never overlap — 7 days compares against the 7 days immediately before them.

The calendar presets answer a different question: how does this compare with how things have been going. Each one is the week, month, quarter or year running up to your selection, ending the day it begins — previous_month on 5–12 August is 5 July to 5 August. Month lengths clamp, so a selection starting May 31 reaches back to April 30.

Those windows are usually longer than your selection, and the figures are reported as they are — a month's traffic really is more than a week's. The delta answers "this week against the month behind it", not "this week against a like-sized week"; use previous_period when you want equal lengths.

These windows never overlap your selection

Every preset ends where your selection starts, so no row is ever counted on both sides of a comparison — whatever length each side happens to be.

A comparison window may not start more than a year before your selection. A comparison you asked for that reaches further is refused with VAL_001, as are a lone compareFrom or compareTo and a pair given back to front — half-honouring any of them would answer a question you did not ask.

The default is treated differently on purpose: select more than a year of traffic and you get the report with no comparison beside it, rather than an error. You asked for a report; the comparison was ours to add, so it is ours to drop.

What comes back

Comparison figures are added to the shape a report already had, and only when you asked for them:

FieldWhereMeaning
comparisonWindowTop level of the response{ from, to } in UTC, echoing what was actually read. from is inclusive, to is exclusive
previous…On each rowThe same figure over the comparison window — previousCount, previousRequestCount, previousAvgDurationMs, and so on, named after the metric it mirrors
absoluteChangeOn each rowCurrent minus previous, for the metric the report is ranked on. On rows ranked by a rate it is a percentage-point difference: 0.4286 against 0.2857 reads as 14.3
percentageChangeOn each rowRelative change. Null when the previous figure is 0 — no percentage describes going from nothing to something

Two conventions are worth reading before you chart any of it:

  • Zero and null mean different things. A count reads 0 when the thing existed but wasn't seen in the comparison window — a crawler that arrived this week reads previousRequestCount: 0. An average, a position, or a share reads null when it was never measured at all: a page nobody opened has no average duration, and 0 ms would claim it was read instantly.
  • Ranking is always the selected period's. Which rows appear, and in which order, is decided by the window you selected alone. A page, bot, domain or brand that showed up only in the comparison window is not listed, and each listed row's earlier figures are looked up by its own name or URL — never read off whatever occupied that position back then.

Reports that are a series rather than a list return the earlier window as its own set of points, carrying that window's own dates. Chart them against each other by position, not by date — the two windows rarely hold the same number of buckets.

Which reports accept it

ReportParameter effect
GET /web-analyticsEach metric's existing previous and change read from your chosen window instead of the implicit one
GET /web-analytics/timelineThe existing previousPeriod series reads from your chosen window, bucketed at the same interval
GET /web-analytics/breakdownAdds previousCount (plus previousVisitors / previousViews on the top_page dimension) and the change fields per row
GET /web-analytics/pages/durationAdds previousAvgDurationMs and the change fields per page
GET /web-analytics/dimension/timelineAdds comparisonData, the same daily series over the comparison window

The summary and the timeline have always reported some previous period — the implicit one immediately before your selection. These parameters decide which window that is; they don't add the field.

The same three parameters work on the crawler reports and on AI visibility, and on the matching MCP tools, so a comparison chosen in one place means the same thing everywhere.

Going deeper

Bots are filtered here — detected elsewhere

Web analytics silently excludes bots so your numbers reflect humans. If you want to see the bots — which AI crawlers and search engines visit your site, verified against spoofing — that's the separate Bot & Crawler Detection feature. The difference is explained in detection vs. filtering.

Next steps