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:
| Function | Example | Result |
|---|---|---|
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).title | title of highest-scored item |
min_by() | min_by(items, &price).name | name 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
nulland the rest of the expression is not evaluated.
Further reading
- JMESPath specification — full language reference with formal grammar
- JMESPath tutorial — interactive walkthrough of the core syntax
- jmespath.org — live expression tester where you can paste your own JSON and try queries