Data model

Default properties

The contextual fields every SDK adds to every event for free — geo, OS, device, app, browser, session — and which ones get promoted to indexed columns.

Every Sankofa SDK enriches events with default properties before they leave the device. The engine takes the highest-value defaults and promotes them into indexed ClickHouse columns, so you can break down or filter by them at query time without paying a JSON-decode cost.

You don't configure default properties — they're free, automatic, and consistent across SDKs. This page is a complete reference for what gets attached and how it's collected per platform.

The promoted defaults

These nine fields are stored as indexed columns and queryable directly. You'll see them in the dashboard's filter / breakdown menus alongside your custom properties.

$countrystring
Two-letter ISO country code, resolved from GeoIP at ingest.
$regionstring
Subdivision (state, province) from GeoIP.
$citystring
City from GeoIP.
$timezonestring
IANA tz from GeoIP, or from device locale on mobile.
$osstring
OS family — `web`, `ios`, `android`, `darwin`, `linux`, `windows`.
$os_versionstring
OS version string (e.g. `17.4`, `34`, `Sonoma 14.4`).
$device_modelstring
Hardware identifier (mobile only). Web events leave this null.
$app_versionstring
Your app's user-facing version string from native bundles.
$session_idstring
Stable session identifier; correlates with replays.

The full default set

Beyond the promoted nine, every SDK also attaches platform-specific defaults under default_properties. These are queryable but not indexed; filtering by them is slower at scale.

Web (@sankofa/browser)

$current_urlstring
Full URL of the page when the event fired.
$hoststring
Hostname from the URL.
$pathnamestring
URL path.
$searchstring
Query string.
$referrerstring
`document.referrer` at event time, or empty for direct visits.
$browserstring
Browser family — `chrome`, `firefox`, `safari`, `edge`, `opera`.
$browser_versionstring
Browser version.
$screen_widthnumber
Device screen width in pixels.
$screen_heightnumber
Device screen height in pixels.
$viewport_widthnumber
Tab viewport width — note this is smaller than `$screen_width` for windowed tabs.
$viewport_heightnumber
Tab viewport height.
$device_pixel_rationumber
Retina / hi-DPI ratio.
$languagestring
`navigator.language`.

Mobile (Flutter, React Native, iOS, Android)

$device_manufacturerstring
Apple, Samsung, Google, etc. (Android also reports the actual brand.)
$device_modelstring
iPhone15,2, sdk_gphone64_arm64, etc. — promoted to indexed column.
$app_build_numberstring
Build number alongside $app_version.
$app_namespacestring
Bundle ID / package name — `dev.sankofa.exampleapp`.
$app_releasestring
Release channel name when set in init.
$network_typestring
`wifi`, `cellular`, `ethernet`, `none`.
$network_carrierstring
Carrier name on cellular (mobile only).
$screen_densitynumber
DPI for the active display.
$localestring
Device locale — `en-GH`, `en-US`.

Server (Node, Go, Python, Java)

Server SDKs add fewer defaults because there's no human at a screen — they focus on the runtime and the call site:

$runtimestring
`node`, `go`, `python`, `java`.
$runtime_versionstring
Major.minor.patch — `node@20.11.0`, `go@1.22.4`, `python@3.11.7`.
$hostnamestring
Process hostname (`os.hostname()`-equivalent).
$pidnumber
Process ID.
$environmentstring
From init `environment` option (e.g. `production`).
$releasestring
From init `release` option (e.g. commit SHA).

If you wired the SDK's HTTP middleware (Express, Fastify, Servlet, net/http, FastAPI, Django, Flask), the request context adds:

$request_methodstring
GET, POST, etc.
$request_pathstring
URL path of the request.
$request_idstring
Request trace ID — auto-generated or pulled from a `traceparent` header.
$user_agentstring
Client UA string. Useful for breakdowns by API client / SDK version.

How promotion works

Promotion is one of the engine's quiet superpowers. Instead of forcing you to declare which fields to index up front, Sankofa watches your traffic and promotes the highest-value defaults into materialized columns during ingest.

The current promoted set is the nine listed at the top of this page. We picked them because they're:

  • present on at least 60% of events across customers;
  • queried frequently (filtered or broken down on);
  • low cardinality (so the index is cheap).

Promotion happens at write time on the engine, not at query time. This means filtering by $country = 'GH' runs as a column scan, not a JSON parse — orders of magnitude faster on tables with billions of rows.

What's next

Edit this page on GitHub