DocsCore concepts

Core concepts

Event data model

2 min readReviewed July 2026

GraphJSON preserves each event as JSON and discovers fields for the visualizer from recent data. The SQL interface exposes the event time as timestamp and the payload as json.

Field types#

Use JSON types intentionally:

JSON type Good uses Visualizer behavior
String IDs, names, categories, enum-like state Filter and split
Number Counts, durations, prices, measurements Filter, sum, average, percentile
Boolean Feature flags and binary state Filter
Null Explicitly missing value Not suitable for aggregation
Object / array Structured context Flattened by default; best kept shallow

GraphJSON does not coerce formatted numeric strings into reliable metrics. Send 4900, not "49.00 USD", and include currency: "usd" separately.

Top-level storage#

In the SQL notebook, a collection behaves like a table with:

Column Meaning
timestamp Event time as Unix seconds
json The event payload

Extract fields with ClickHouse JSON functions:

SELECT
  timestamp,
  JSONExtractString(json, 'event') AS event,
  JSONExtractString(json, 'user_id') AS user_id,
  JSONExtractFloat(json, 'amount') AS amount
FROM billing_events
LIMIT 100

GraphJSON scopes collection tables to the authenticated workspace before executing the query.

Nested JSON is flattened#

By default:

{
  "event": "page_viewed",
  "context": {
    "page": {
      "path": "/pricing"
    },
    "device": "mobile"
  }
}

is stored with dot-separated keys:

{
  "event": "page_viewed",
  "context.page.path": "/pricing",
  "context.device": "mobile"
}

Query the flattened key literally:

JSONExtractString(json, 'context.page.path')

This makes nested properties usable by the visualizer. Arrays can produce indexed keys and are rarely good analytics dimensions; log a separate summary field when you need to filter or aggregate on array content.

Field discovery#

The visualizer infers available string and numeric fields from recent events. If a field is absent from the selector:

  1. confirm it appears in a recent sample
  2. confirm its JSON type is consistent
  3. select Verify logs or reload the collection
  4. widen the range if the field is only present in older data

A field can exist in SQL even when it is not currently suggested by the visualizer.

Missing fields#

Events do not need identical shapes. A filter or numeric aggregation only matches events where the referenced value can be extracted in the expected way.

This flexibility is useful during evolution, but uncontrolled drift makes results hard to interpret. Add a schema_version when you make a breaking shape change and document how old and new events differ.

Reserved concepts#

Use the request-level timestamp for event time. Avoid also using timestamp as a JSON property because it is easy to confuse with the top-level SQL column and is not treated as a normal discovered field.

Similarly, use a stable event field for the action name. Although GraphJSON does not require that name, shared conventions make charts and SQL much easier to reuse.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support