JMESPath

JMESPath is a query language for JSON. In Litmus Lab you use it in two places: Context Mapping (to extract a subset of scenario data into a variable) and Extract steps in the Flow (to pull values out of a request or response body). The same syntax applies in both. The one exception is extracting from headers instead of a body — see the last example below, which uses a plain header name instead.

Basic field access

username           →  "alice"
profile.firstName  →  "Alice"
address.city       →  "Brooklyn"

Use dot notation to reach nested fields.

Array indexing

items[0]           →  first element
items[-1]          →  last element
items[0].title     →  field from first element

Wildcard projection

Pull a single field from every element in an array:

watch_history[*].title
→  ["Barbie", "Oppenheimer", "The Batman"]

The result is always an array. If the field doesn't exist on some elements, those are omitted.

Multi-field projection

Pick multiple fields from each element:

watch_history[*].{title: title, rating: rating}
→  [{"title": "Barbie", "rating": 5}, ...]

Filter expressions

Select only elements matching a condition:

watch_history[?rating==`5`].title
→  titles the user rated 5 stars

items[?status=='active']
→  only active items (full objects)

orders[?total>`100`]
→  orders over $100

Backticks delimit literal numbers and booleans. Single quotes delimit literal strings.

Compound filter conditions

Combine conditions with && and ||:

watch_history[?days_ago<=`30` && rating>=`4`].title
→  recently watched and well-rated titles

users[?role=='admin' || role=='owner'].email
→  emails for admins and owners

Functions

JMESPath includes a small set of built-in functions:

FunctionExampleResult
length()length(items)count of items
sort_by()sort_by(items, &rating)sort by field
reverse()reverse(sort_by(items, &date))sorted descending
max_by()max_by(items, &score).titletitle of highest-scored item
min_by()min_by(items, &price).namename of cheapest item
join()join(', ', items[*].title)comma-joined string
keys()keys(metadata)array of object keys
values()values(metadata)array of object values
contains()contains(tags, 'featured')boolean membership check
starts_with()starts_with(name, 'Dr.')boolean prefix check
to_string()to_string(id)coerce number to string
to_number()to_number(amount_str)coerce string to number

Practical Litmus Lab examples

Context Mapping — inject readable list into a rubric

watch_history[?rating>=`4`].title
→  variable: liked_titles
→  rubric: "Must not recommend anything in: {{liked_titles}}"

Context Mapping — pick a single nested value

preferences.language
→  variable: USER_LANG
→  URL: /api/content?lang={{USER_LANG}}

Context Mapping — summarise recent activity

orders[?days_ago<=`7`][*].{product: product, qty: quantity}
→  variable: RECENT_ORDERS
→  rubric: "Response must reference these recent orders: {{RECENT_ORDERS}}"

Extract — pull a token from a login response

data.accessToken
→  variable: token
→  used later as {{token}} in a later step's Authorization header

Same JMESPath syntax as Context Mapping — no $. root prefix, just the field path. Extract's Source selector decides which JSON is searched: Response (the default) or Request, both parsed from the step's own body.

Extract — pull a header instead of a body field

Extract's Source selector also offers Request Headers / Response Headers, for values that live in headers rather than the body — an auth token off a captured request, a trace id off a response, and so on. These do not use JMESPath: headers are a flat set, so the path is just the header's literal name, matched case-insensitively —

authorization  (Source: Request Headers)
x-trace-id      (Source: Response Headers)

Don't reuse a JMESPath expression here (headers.authorization, headers["x-trace-id"]) — it won't match anything. This split exists because a hyphenated header name like x-trace-id isn't a valid bare JMESPath identifier at all (JMESPath would parse x-trace-id as subtraction of three identifiers) — a direct name lookup sidesteps that entirely rather than requiring every header path to be quoted.

Tips

  • Test your expressions against a sample scenario using the live preview in the Context Mapping tab — it resolves the expression against your first active scenario as you type.
  • If a filter returns no matches, the variable resolves to an empty array []. Rubrics should handle the empty case gracefully.
  • Use join(', ', expr) to turn an array result into a readable comma-separated string for injection into LLM rubrics.
  • Avoid deeply chained expressions that depend on optional fields — a missing intermediate field returns null and the rest of the expression is not evaluated.

Further reading