Query what you send
Contents
Note: Metrics is in open alpha. Any team can turn it on — open Metrics and select Enable metrics in the onboarding view. The details on this page describe current behavior and may change before general availability.
This page shows how a metric you record with posthog.metrics in posthog-js, posthog-node, or posthog-python reads back when you query it. The most important thing to know first: counters and histograms arrive as deltas, not running totals.
What the SDKs send
The SDKs pre-aggregate in memory and flush one OTLP data point per series every 10 seconds by default. Each type is sent like this:
| Call | OTLP type | Temporality | What one data point holds |
|---|---|---|---|
metrics.count() | Monotonic sum | Delta | The total added in that flush window |
metrics.gauge() | Gauge | – | The last value recorded in that window |
metrics.histogram() | Histogram | Delta | The count, sum, min, max, and per-bucket counts for that window |
Delta temporality means each flush carries only what happened in that interval. It isn't a running total since process start. The viewer and increase or rate aggregations account for this. If you query through a Prometheus-style tool, they don't, as the next sections explain.
Resource attributes and names
Everything you set in serviceName, environment, serviceVersion, and resourceAttributes becomes an OpenTelemetry resource attribute (service.name, deployment.environment, service.version, plus your own). The SDK also adds telemetry.sdk.* attributes. Attributes you pass on individual calls become data point attributes.
Metric names are sent as you write them, so http.client.request.duration stays dotted. The default name from posthog-js metrics.network and its attributes (url.template, http.request.method) use dotted OpenTelemetry names too.
Default histogram buckets
Histograms use these bucket boundaries, in the unit you record (milliseconds for durations):
There's one more bucket above the last boundary, so values over 10000 are still counted. To use different boundaries, pass bounds when you record (posthog-node 5.63.0 or later, posthog-js 1.447.0 or later):
The first sample of a histogram (its name and unit) fixes the bounds until the client resets. A later sample with different bounds is recorded in the fixed buckets and logs a warning, and a sample with invalid bounds is dropped.
Query with SQL
Every data point lands in the posthog.metrics table, scoped to your project. Use the SQL tab in Metrics to read it back directly. See Use your metrics for the viewer and SQL tab.
Query with PromQL
Note: The behavior in this section comes from querying PostHog's Prometheus-compatible API with metrics sent by the SDKs. It describes what you see today and may change during the alpha.
When you read metrics back as Prometheus series, expect this:
- A histogram named
dash_query_duration_msreads back asdash_query_duration_ms_bucket,dash_query_duration_ms_count, anddash_query_duration_ms_sum, plus a baredash_query_duration_msseries. - A counter keeps its name. PostHog doesn't add
_total. - Resource attributes become labels, so you can filter on
service_nameorregion(fromresourceAttributes). Each series carries both the dotted OpenTelemetry label (service.name) and an underscore form (service_name), plusdeployment.environmentandtelemetry.sdk.*. - Dotted names and labels must be quoted in PromQL:
{"deployment.environment"="production"}andsum by ("url.template", le). Grafana legend templates reference them as-is, for example{{url.template}}.
Counters: sum deltas, don't use rate()
Because each point is a delta, rate() and increase() give wrong answers without an error. Sum the points instead:
Histograms: percentiles from buckets
For the average duration, divide the summed _sum by the summed _count:
Gauges
A gauge is the last value in each window, so you can query it as is, or smooth it:





