JMESPath Query
Query JSON with JMESPath: filter, project, sort and reshape, and see the result and its type as you type. Uses the JMESPath Community Edition dialect. Runs in your browser.
Where your data goes
| Using this page | Using the API, MCP or A2A | |
|---|---|---|
| Where it runs | In your browser. The JSON is parsed and the JMESPath expression evaluated by JavaScript on this page; nothing is sent anywhere. | In a Cloudflare Pages Function at the Cloudflare data centre nearest the caller. |
| What leaves your device | Nothing you enter. After the page and its scripts load, no request
carries your input. The browser enforces this: the page's Content-Security-Policy
sets connect-src 'none' and form-action 'none', which
block fetch, XHR, WebSocket, EventSource, beacons and form posts.
| The server receives the JSON document and the expression, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing. |
| Stored | Nothing leaves the page. No cookies, analytics or third-party scripts; the browser keeps only your View → Inline config choice. | Your inputs and results: never. What is stored is your client record (client ID, a SHA-256 hash of your secret, expiry, and the profile you registered with) and one metadata row per call: operation, time, transport, status, error code, duration, request and response sizes, and country. |
| Logged | Nothing (no requests are made). | Only the per-call metadata above, attributed to your client ID. Never parameters, bodies, results, or credentials. Cloudflare, as the host, also processes request metadata (IP address, full URL including the query string, headers) under its own policies, so put inputs in POST bodies. |
| Access | Open to anyone. | Requires a client ID and secret (how to get one). HTTPS only; TLS terminates at Cloudflare. |
Details and how to verify this yourself: Security & privacy.
User guide
The tool sits in a window that works like a desktop app: menus on the top row and the full-screen icon at its right (Esc leaves full screen). The set-up options described below are in Settings → Options…, an overlay, and the Settings menu also lists the common choices directly. To keep the options in view instead, choose View → Inline config; the page remembers that choice. Help → Keyboard shortcuts… lists the keys.
- Paste JSON into the left box (or click Sample).
- Type a JMESPath expression above it. The result and its type appear on the right as you type. The buttons next to the expression load common patterns.
- If the expression is wrong, the error shows the column and the expression with a caret under the problem. If the JSON is wrong, Go to error selects the bad character.
A path that does not exist is not an error: it gives null. A filter that matches nothing gives [].
Three things that trip people up.
- Literals need backticks.
price > `10`compares with the number 10;price > 10is a syntax error. Strings can be raw literals in single quotes:name == 'bread'. A backtick literal must be valid JSON:`"text"`,`[1, 2]`,`true`. - Projections keep going until a pipe.
items[*].name[0]applies[0]to each name (a string, so each gives null and is dropped). To take the first element of the list of names, stop the projection with a pipe:items[*].name | [0]. - Ordering comparisons are for numbers.
<,<=,>,>=between strings givenull, and a filter treatsnullas false.
Specification
Dialect. JMESPath Community Edition, as implemented by @jmespath-community/jmespath 1.3 (a tree-walking interpreter: no code generation). It is the original JMESPath language (identifiers, sub-expressions, index and slice expressions, wildcard and flatten projections, filters, multi-select lists and hashes, pipes, ||, &&, !, comparisons, functions, expression references with &) plus these Community extensions:
| Extension | Example |
|---|---|
Root reference $ (the whole document, even inside a projection) | items[*].[name, $.owner.name] |
let expressions with $variables | let $limit = `3` in items[?price > $limit].name |
Arithmetic: +, - (also unary), *, /, %, // (integer division) | items[2].price * `10` |
Ternary condition ? a : b (experimental in the library) | length(items) > `2` ? 'many' : 'few' |
| Raw string literals in single quotes | 'fresh' |
Functions. abs, avg, ceil, contains, ends_with, find_first, find_last, floor, from_items, group_by, items, join, keys, length, lower, map, max, max_by, merge, min, min_by, not_null, pad_left, pad_right, replace, reverse, sort, sort_by, split, starts_with, sum, to_array, to_number, to_string, trim, trim_left, trim_right, type, upper, values, zip. No custom functions are registered. Calling any other name is reported before evaluation, with its position.
Errors.
- Syntax errors give the column of the token where the problem was detected; an incomplete expression points just past the end. The library’s own messages have no position, so the position is found by re-parsing prefixes of the expression and taking the first one that fails the same way. Unterminated quoted identifiers and invalid backtick literals point at their opening quote or backtick. (The library accepts an unterminated raw string such as
'abcas if it were closed.) - Evaluation errors (a function given the wrong type, such as
lengthof a number; division by zero; a slice step of 0) are reported with the position of the function involved when it can be identified. - JSON errors give line, column, offset and a JSON Pointer, from the same strict RFC 8259 parser as the JSON tool. Duplicate keys and integers beyond 2^53 − 1 are reported as warnings; the query sees the last value of a duplicate key, and numbers as IEEE 754 doubles.
Result. result is the value; type is its JSON type (object, array, string, number, boolean or null); the formatted text uses 2 or 4 spaces or no whitespace. Object keys that look like array indexes ("2", "10") come first, in numeric order, in any object the query returns: this is how JavaScript orders object keys. Inherited JavaScript property names are never visible: constructor on {} is null.
Examples. All results below are produced by this tool against this document:
{
"items": [
{ "name": "apple", "price": 1.2, "tags": ["fruit"], "stock": { "qty": 0 } },
{ "name": "bread", "price": 3.5, "tags": ["bakery", "fresh"], "stock": { "qty": 12 } },
{ "name": "cheese", "price": 12, "tags": ["dairy"], "stock": { "qty": 4 } }
],
"owner": { "name": "shop", "email": "[email protected]" }
}
| What | Expression | Result |
|---|---|---|
| Field of the first element | items[0].name | "apple" |
| Negative index | items[-1].name | "cheese" |
| Projection | items[*].name | ["apple","bread","cheese"] |
| Flatten nested lists | items[].tags[] | ["fruit","bakery","fresh","dairy"] |
| Filter by number | items[?price > `3`].name | ["bread","cheese"] |
| Filter by equality | items[?stock.qty == `0`].name | ["apple"] |
| Filter with a function | items[?contains(tags, 'fresh')].name | ["bread"] |
| Combined conditions | items[?price < `5` && stock.qty > `0`].name | ["bread"] |
| Negation | items[?!(stock.qty > `0`)].name | ["apple"] |
| Nested filter | items[?tags[?@ == 'fresh']].name | ["bread"] |
| Multi-select list | items[*].[name, price] | [["apple",1.2],["bread",3.5],["cheese",12]] |
| Multi-select hash (reshape) | items[*].{name: name, qty: stock.qty} | [{"name":"apple","qty":0},{"name":"bread","qty":12},{"name":"cheese","qty":4}] |
| Slice | items[:2].name | ["apple","bread"] |
| Reverse slice | items[::-1].name | ["cheese","bread","apple"] |
| Sort by a field | sort_by(items, &price)[*].name | ["apple","bread","cheese"] |
| Largest by a field | reverse(sort_by(items, &stock.qty))[0].name | "bread" |
max_by / min_by | max_by(items, &price).name | "cheese" |
| Count | length(items) | 3 |
| Count matches | length(items[?stock.qty > `0`]) | 2 |
| Sum | sum(items[].stock.qty) | 16 |
| Maximum | max(items[].price) | 12 |
| Summary object | {count: length(items), qty: sum(items[].stock.qty)} | {"count":3,"qty":16} |
| Object keys | keys(owner) | ["name","email"] |
| Missing path | items[0].missing | null |
| No matches | items[?price > `100`] | [] |
| Default value | owner.phone || 'none' | "none" |
| First non-null | not_null(owner.phone, owner.email) | "[email protected]" |
| Pipe | items | [0].name | "apple" |
| Projection continues | items[*].name[0] | [] |
| Pipe stops the projection | items[*].name | [0] | "apple" |
| Join strings | join(', ', items[*].name) | "apple, bread, cheese" |
| Membership | contains(items[*].name, 'bread') | true |
| String functions | split(owner.email, '@')[1] | "example.com" |
| Map an expression | map(&upper(name), items) | ["APPLE","BREAD","CHEESE"] |
| Object to pairs | items(owner) | [["name","shop"],["email","[email protected]"]] |
| Pairs to object | from_items(items[*].[name, price]) | {"apple":1.2,"bread":3.5,"cheese":12} |
| Group | group_by(items, &to_string(stock.qty > `0`))."true"[*].name | ["bread","cheese"] |
let variable | let $limit = `3` in items[?price > $limit].name | ["bread","cheese"] |
| Root reference | items[*].[name, $.owner.name] | [["apple","shop"],["bread","shop"],["cheese","shop"]] |
| Arithmetic | items[2].price * `10` | 120 |
| Ternary | length(items) > `2` ? 'many' : 'few' | "many" |
| Type of a value | type(items[0].price) | "number" |
| String comparison | 'a' < 'b' | null |
API. POST /api/json/query with input (the JSON text, at most 500,000 characters), expression (at most 2,048 characters) and indent (2, 4, tab or none). The response has result, type, json (the formatted text) and warnings. Invalid JSON, an invalid expression and an evaluation error are 422 errors whose message gives the position.
API & MCP
The same logic is available over HTTP and as MCP tools. Unlike this page, these run on the server: see where your data goes.