Judgment Labs Logo

Queries and terminals

TypeScript JQL roots, time bounds, select terminals, and result row shapes.

judgeval-js JQL source on GitHub

Root builders

traces(options?: { filter?: FilterInput }): QueryBuilder
spans(options?: { filter?: FilterInput }): QueryBuilder
sessions(options?: { filter?: FilterInput }): QueryBuilder
SourceGrainCanonical fields
tracesOne row per tracetrace_id, span_id, span_name, name, input, output, duration, status, timestamp, session, customer, error
spansOne row per spanCommon fields plus cost, model, parent_span_id
sessionsOne row per sessionCommon fields plus error

Field catalogs can evolve. Use client.discover("fields", { source: "spans" }) before generating dynamic query UIs or relying on project-specific attributes.

Builder methods

.where(filter: FilterInput): QueryBuilder
.last(window: string): QueryBuilder
.since(since: string): QueryBuilder
.between(start: string, end: string): QueryBuilder
.rows(options?: { fields?: string[]; limit?: number }): QueryBuilder
.ids(): QueryBuilder
.count(by?: string): QueryBuilder
.recent(n: number): QueryBuilder
.top(n: number, by: string): QueryBuilder
.ranked(options?: { within?: string; by?: string; pick?: number | [number, number] }): QueryBuilder
.agg(options: { func; field; q? }): QueryBuilder
.agg(func, field, q?): QueryBuilder
.trend(options?: { metric?: "count" | "rate"; bucket?: string }): QueryBuilder
.pipe(): PipelineBuilder
.toJSON(): SourceQuery

Builders are immutable: every method returns a new builder. Calling .where() multiple times combines the root filters with canonical all (AND). Calling .last(), .since(), or .between() replaces any earlier time bound.

Serialize without executing with query.toJSON().

Time object

The JSON time object must contain exactly one key:

BuilderJSONInput
.last(window){"last":"7d"}Relative duration string
.since(since){"since":"2026-07-01"}Date/time string accepted by the server
.between(start, end){"between":["2026-07-01","2026-07-08"]}Exactly two date/time strings

Omitting time searches all available history unless a deployment-level policy adds a default window.

Select terminals

A source query can contain at most one select. In TypeScript, a second select silently replaces the first — the builder does not throw. The server also rejects a query containing both select and pipe.

TerminalInputs and defaultsResult rows
rowsfields?: string[], limit?: numberRequested fields, or the source's default row projection
idsNoneDistinct ID columns for the grain: traces returns trace_id; spans returns both trace_id and span_id; sessions returns session_id
countby?: stringNo by: {"count": number}; with by: {"key": value, "count": number}
recentn: intSource rows ordered by newest timestamp
topn: int, by: stringSource rows ordered by the numeric field descending
rankedwithin?: string, by: "timestamp", pick: 1Traces/spans only. Positional rows plus _rn; pick is a nonzero rank or two-item range 1 <= a <= b
aggfunc, field, q?Traces/spans only. Exactly one row: {"value": number | null}
trendmetric: "count", bucket: "1d"Count: {"bucket": datetime, "n": number}; rate: adds total and rate

Aggregate rules

func is one of avg, sum, min, max, quantile, or count_distinct. field is always required. q is required only for quantile, rejected for every other function, and must satisfy 0 < q < 1.

Trend rules

metric is count or rate. bucket is a duration string such as 1d or 1w. Rate trends require a filter that defines the numerator.