For the complete documentation index, see llms.txt. This page is also available as Markdown.

Properties

What to put in `properties`, what the SDK adds automatically, and how to design a clean schema.

properties is a free-form JSON object you attach to any event. It's where the interesting part of an event lives — the plan, the amount, the button label, the experiment variant.

{
  "event": "purchase",
  "properties": {
    "amount_cents": 4900,
    "currency": "usd",
    "items": 2,
    "plan": "pro",
    "discount_code": "SPRING25"
  }
}

Limits:

Limit
Value

Total size after JSON.stringify

8 KB

Property names

strings, no length cap, but be reasonable

Property values

any JSON-serialisable value — string, number, boolean, null, array, nested object

Cardinality

no server-side cap (your problem if you put request_id on every event)

If properties exceeds 8 KB, the value is replaced by {"__truncated":true,"original_bytes":N} rather than partially committed. We never ship a malformed payload.

What you put there

Anything specific to this event that you want to slice on later.

Rules of thumb:

  • One value per key. Don't pack JSON into a single string property — the query layer can't index strings as objects.

  • Numbers as numbers, booleans as booleans. amount_cents: 4900, not "4900". is_first_purchase: true, not "true".

  • Money in cents. Or a fixed-precision integer. Float arithmetic on revenue is a path to bug reports.

  • Don't double-encode. ClickHouse stores properties as a String; the API parses your object once on insert and re-serialises it. Sending properties: "{\"plan\":\"pro\"}" makes querying painful.

What you don't put there

Don't put in properties

Why

Use instead

Personally identifying info (email, full name, address)

Privacy. Once it's in events, it's hard to clean.

Keep PII in your own DB; key by user_id.

Free-text user input

Cardinality explosion.

Hash it, bucket it, or skip it.

Raw IP, exact location

We HMAC IPs and store country only — sending raw IP defeats the design.

Trust the server's country enrichment.

Secrets / tokens

They'd live forever in ClickHouse.

Anything. Just not this.

Huge blobs

8 KB cap.

Store in your own object storage; pass the id.

What the browser SDK adds for free

On every event, @millimetric/track merges these into properties:

Plus on the event itself (not in properties):

The classifier reads url + referrer to produce source / medium / campaign / source_confidence / source_rule_id server-side — see Attribution.

You can override anything by passing it explicitly:

What the Node SDK adds

Almost nothing. The Node SDK is a thin wrapper over the HTTP API — it doesn't infer browser-only context (no UA, no language, no viewport). You send what you mean.

What you do get for free, server-side:

  • country from the request IP via Cloudflare geo headers.

  • device_type / browser / os from the User-Agent header — though for backend events that's normally "the request that triggered this server-side track", which may not match the visitor's actual browser. Pass url + referrer from your request handler if you want the classifier to fire correctly.

Naming conventions

Pick one and stick to it. We recommend:

  • snake_case for property names: amount_cents, is_first_purchase, plan_tier.

  • $-prefix for system properties the SDK or server adds: $viewport_w, $language, $timezone.

  • Units in the name when ambiguous: duration_ms, amount_cents, size_bytes, latency_ms.

  • Booleans named as questions: is_paid, has_premium, was_invited.

  • Currency always alongside amount: amount_cents + currency: "usd".

Full conventions in Event & property naming.

Property semantics that downstream tools rely on

Some properties are special because the API or MCP tools index on them.

Property
Where it surfaces
When to set it

utm_source / utm_medium / utm_campaign

Drives source / medium / campaign if url doesn't carry them.

Pass-through from server-side handlers that already parsed the URL.

$pageview (event name, not property)

Drives path, page-view counts, top-paths breakdown.

Browser SDK handles automatically; call page() for virtual pageviews.

amount_cents + currency

Convention for the upcoming revenue dashboards.

On any conversion event with monetary value.

experiment_id + variant

Convention for A/B test analysis.

On every event that occurred under an active experiment.

None of these are required — but if you set them with these names, the dashboards and the MCP top_sources / get_stats tools will Just Work.

Querying on properties

properties is a String in ClickHouse holding JSON. To filter or aggregate on a property, use ClickHouse's JSON functions:

For numeric extraction: JSONExtractInt, JSONExtractFloat. For booleans: JSONExtractBool.

Right now /v1/query returns properties as a JSON string for the client to parse. /v1/stats doesn't yet support group_by on a property key — that's on the roadmap. For now, run targeted SQL via your ClickHouse credentials if you need it.

Examples by event class

Page view

Conversion

Engagement

Error

Agent step (MCP)

See also

Last updated

Was this helpful?