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:
- confirm it appears in a recent sample
- confirm its JSON type is consistent
- select Verify logs or reload the collection
- 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.