jq Playground · Logs
jq Playground — test jq expressions online.
Not a re-implementation and not a server round-trip: the real jq 1.8.2 binary, compiled to WebAssembly and running inside this tab. Paste your JSON, write the filter, and see every output jq emits — plus the exact error it would print if the filter is wrong.
Runs in your browser — nothing you paste leaves this page. How we prove that
jq Playground
-r prints string results without JSON quotes · -s slurps the whole input stream into one array · -n runs the filter once with null as input (read the stream with inputs) · -c prints one line per result instead of pretty-printing.
The version above is read out of the WebAssembly binary at runtime, not written into this page — it is real jq, not a JavaScript re-implementation.
Results update as you type — press Enter to run now.
Press Esc to release keyboard focus from either editor; ⌘/Ctrl + Enter runs and leaves the editor. Nothing you paste is uploaded — jq runs inside this tab.
A jq filter produces a stream of results, not one value: each card below is one output, exactly as jq would print it on its own line. Input is capped at 2 MB, and a filter that never terminates (an unguarded repeat) will freeze this tab until you reload it.
Loading jq — the real binary, compiled to WebAssembly (250–340 KB over the wire, cached after your first visit). The example below runs as soon as it lands.
The Gap
Invented builtins, and someone else's server.
Ask an assistant for a jq one-liner and you get something that looks exactly right and fails on the first run. The favourite failure is a builtin that does not exist: leaf_paths is gone from jq 1.8.2, so jq answers leaf_paths/0 is not defined — and the fix, paths(scalars), is not what you were told. The same happens with flags borrowed from jaq and gojq, and with confident claims about // that quietly break on 0.
The other option is a server-side playground, which works — by uploading the JSON you wanted to inspect. That is a strange trade for output from kubectl get secret, an API response with a bearer token in it, or a log line with a customer's email in it.
This page answers with ground truth instead. The jq that runs here is the C program, compiled to WebAssembly, fetched once from this site and executed in your tab — and the version badge beside the flags is read out of that binary rather than typed into the page. Every builtin result in the reference table below came out of the same binary, and each one is pinned by a test, so this page cannot drift away from what jq does.
Working with logs rather than JSON? The Regex Log Tester does the same job for unstructured lines.
The Pipeline
How it works.
Four steps, all of them inside your browser tab — and the third one is the actual jq binary, not an emulation of it.
-
Fetch the real binary, once.
jq 1.8.2 as WebAssembly — 907 KB uncompressed, 250–340 KB over the wire depending on the encoding your browser negotiates — served from this site as a hashed static asset and cached by your browser. It loads while you read, not while you wait.
-
Compile the filter.
jq compiles your program before it sees any input. A syntax error or an undefined function exits 3, and you get jq's own message, line, column and caret excerpt — not "invalid expression".
-
Run it over your input.
Your JSON goes to jq on stdin, with only the flags shown in the flags row. jq emits a stream of results, and each one becomes its own card — because a stream of four values is not the same answer as one array of four.
-
Classify what came back.
jq overloads exit code 5 for both a runtime error and an unparseable input, so the two are told apart by the stderr prefix and pointed at the editor that owns them. Outputs produced before a failure are kept, and anything jq wrote to stderr is shown even when it exited 0.
By Example
jq can fail twice and still exit 0.
jq's exit code reflects only the LAST input it processed. Feed it a stream where two values break the filter and the last one does not, and a CI step that checks $? sees a clean run.
Five inputs, two of them wrong
A stream of five JSON values and the filter .+1. Strings cannot be added to numbers, so two of the five fail.
1 "x" 2 "y" 3 Three results, two errors, exit 0
The playground shows all three outputs AND both stderr lines, and says why the exit code is 0 — move the string to the end of the stream and the same filter exits 5.
3 outputs · 3 ms · 2 stderr lines
output 1 2
output 2 3
output 3 4
jq also wrote to stderr 2 lines
string ("x") and number (1) cannot be added
string ("y") and number (1) cannot be added Run it above: the results panel lists both stderr lines under "jq also wrote to stderr", with the exit code beside them. Nothing is hidden just because jq called the run a success.
Reference
The jq builtins you actually use.
26 filters, each with a real input and the exact output jq 1.8.2 produced for it. Tap any row's filter into the playground above to watch it run on your own data.
| Filter | Input | Output — jq 1.8.2 |
|---|---|---|
| .a.b | {"a":{"b":42}} | 42 Path access. A missing key is null, not an error — but indexing a non-object is: .a on [1,2] fails with "Cannot index array with string". |
| .items[] | .name | {"items":[{"name":"web"},{"name":"api"}]} | "web" "api" TWO outputs, not an array. Wrap the whole filter in [ … ] when you want one. |
| .[] | select(.ms > 100) | [{"ms":41},{"ms":998}] | {"ms":998} select keeps the input when the condition is true and emits nothing when it is false. |
| map(.ms) | [{"ms":41},{"ms":998}] | [41,998] map(f) is [.[] | f] — it takes an array and returns an array. |
| keys | {"b":1,"a":2} | ["a","b"] keys SORTS. Use keys_unsorted for document order — the same input gives ["b","a"]. |
| length | {"a":1,"b":2} | 2 Keys for an object, elements for an array, code points for a string ("héllo" is 5), absolute value for a number, 0 for null. |
| add | [1,2,3] | 6 Adds the elements of an array — which also concatenates strings and merges objects. |
| group_by(.k) | map({ k: .[0].k, n: length }) | [{"k":"a"},{"k":"a"},{"k":"b"}] | [{"k":"a","n":2},{"k":"b","n":1}] The count-by-field idiom. group_by sorts first, so the groups come back in key order. |
| unique | [3,1,3] | [1,3] Sorts as well as de-duplicates. unique_by(f) for objects. |
| sort_by(-.n) | [{"n":1},{"n":9}] | [{"n":9},{"n":1}] Negating the key is how you sort descending; there is no reverse flag on sort_by. |
| to_entries | {"a":1} | [{"key":"a","value":1}] The object ⇄ list bridge. from_entries goes back; on an ARRAY the keys are indices. |
| with_entries(.value += 1) | {"a":1,"b":2} | {"a":2,"b":3} to_entries | map(f) | from_entries in one step — the way to map over an object's values. |
| del(.b) | {"a":1,"b":2} | {"a":1} Takes a path, so del(.a[1]) and del(.a, .b) both work. |
| .a // "fallback" | {"a":null} | "fallback" Falls through on null, false and no-output only. {"a":0} gives 0 — zero is truthy in jq. |
| has("a") | {"a":null} | true Asks about the KEY, not the value — which is how you tell "missing" from "null". |
| [paths(scalars)] | {"a":{"b":1}} | [["a","b"]] Every path to a leaf. leaf_paths no longer exists in jq 1.8.2; this is its replacement. |
| flatten | [[1,[2]],[3]] | [1,2,3] All the way down by default; flatten(1) for one level. |
| test("^ERR"; "i") | "error: x" | true Oniguruma regex with a flags string. match / capture / sub / gsub / splits take the same pair. |
| capture("(?<code>[0-9]{3})") | "status 503 here" | {"code":"503"} Named groups become object keys — the fastest way to turn a log line into fields. |
| @csv | ["web",3] | "web",3 With -r. Strings are quoted, numbers bare, null empty; a nested array or object is a runtime error. @tsv uses a real tab. |
| @base64d | "aGVsbG8=" | hello With -r. The Kubernetes secret decoder. Invalid base64 is a runtime error, not silence. |
| todate | 1700000000 | 2023-11-14T22:13:20Z fromdate goes back. strftime("%Y-%m-%d") for any other format. |
| limit(2; .[]) | [1,2,3] | 1 2 Stops the generator after n outputs — also the guard that makes an unbounded filter safe. |
| .[1:3] | [1,2,3,4] | [2,3] Slices arrays and strings; .[-1:] takes the last element. |
| .. | numbers | {"a":[1,2]} | 1 2 .. is recursive descent; numbers / strings / objects / arrays / nulls / booleans filter by type. |
| $__loc__ | null | {"file":"<top-level>","line":1} Where you are in the program. The file is "<top-level>" — not "<stdin>", whatever an autocomplete tells you. |
Outputs are shown as jq prints them with -c; the @csv, @base64d and todate rows assume -r, because without it jq prints the result as a quoted JSON string.
Next Step
Your input is YAML? Convert it first.
jq reads JSON, and a Kubernetes manifest or a CI config is YAML. Run it through the JSON ↔ YAML Converter — which also reports every comment, anchor and timestamp the conversion costs — then bring the JSON back here and slice it.
4 outputs · -r · 3 ms
output 1 web-7d9f8c-2xk4t
output 2 web-7d9f8c-9pl2m
output 3 api-5b4c7d-qq8rn
output 4 batch-1a2b3c-zzz01 FAQ
Questions, answered.
Tap a question to expand the answer.
Is this real jq, or a JavaScript re-implementation?
It is real jq. The page loads jq 1.8.2 compiled to WebAssembly — the actual C program, 907 KB uncompressed and 250–340 KB over the wire depending on whether your browser negotiates brotli or gzip — and runs your filter through it. The version badge next to the flags is read out of that binary at runtime, not written into the page, so it cannot drift. Nothing here approximates jq's behaviour, because nothing here reimplements it: the same code that runs on your laptop runs in your tab.
Does my JSON leave my browser?
No. There is no server, no API and no logging. jq is fetched once from this site as a static asset and then executes entirely inside your tab, which is the difference between this and a server-side playground: with jqplay.org and friends, the JSON you paste is sent to someone else's machine to be evaluated. You can safely paste production kubectl output, API responses with tokens in them, and logs with customer data.
Which flags are available, and which are not?
Four toggles: -r (raw string output), -s (slurp the input stream into one array), -n (run once with null input) and -c (compact, one line per result). Not exposed in this version: --arg and --argjson, module loading (-L, include, import — jq answers "module not found"), --seq, colour output, file arguments and reading from a URL. You can replace --arg inside the program itself: ("prod") as $ns | … binds a value the same way.
Why does the jq an AI assistant wrote fail here?
Usually because a builtin it used does not exist in jq 1.8.2. leaf_paths is the classic one — it was removed, and jq now answers "leaf_paths/0 is not defined"; the working spelling is paths(scalars). The same happens with invented flags and with builtins from other tools (jaq, gojq, JMESPath) that never existed in jq. A compile error here quotes jq's own message, line, column and caret, so you can see exactly which token it rejected rather than guessing.
What is the difference between -s and inputs?
-s reads every value in the input stream into one array and runs your filter once on that array: 1 2 3 becomes [1,2,3]. -n runs the filter once with null as input and hands you the stream through the inputs generator, so [inputs] on 1 2 3 also gives [1,2,3] — but you control when each value is pulled, which is what lets you pair values, skip a header record, or stop early. -s buffers everything first; -n plus inputs streams.
Why does -r still print JSON for my object?
Because -r only removes the quotes around string results. Anything that is not a string — an object, array, number, boolean or null — is still printed as JSON, which is why . on {"a":1} looks identical with and without -r. If you want text out of a non-string, convert it in the filter: tostring, @csv, @tsv, join(","), or an interpolated string like "\(.name) → \(.image)".
How do I get CSV or TSV out of jq?
Build an array per row and pipe it through @csv or @tsv, then turn on -r so the row is printed as text rather than as a quoted JSON string. ["web",3] | @csv gives "web",3 — strings quoted, numbers bare — and @tsv gives web then a real tab then 3. Both refuse nested values: an array or object inside a row is a runtime error ("array ([\"a\"]) is not valid in a csv row"), and null becomes an empty field.
What does // actually do?
a // b produces a unless a is false, null, or produces no output at all — in which case it produces b. The trap is that it is not "if empty": 0 and the empty string are truthy in jq, so {"a":0} | .a // "fallback" is 0, not "fallback". If you specifically want "when the key is missing", test for it: if has("a") then .a else "fallback" end.
Does jq round large numbers the way JavaScript does?
Not while the value is untouched. jq 1.8 preserves the literal you gave it, so {"n":9007199254740993} | .n prints 9007199254740993 exactly — a number JavaScript cannot represent — and 1.0 stays 1.0 rather than collapsing to 1. Do arithmetic on it and the guarantee ends: .n + 0 becomes 9007199254740992, because the maths goes through a 64-bit double. That is a jq property, not a browser one; you get the same answer from jq on the command line.
How large an input can I paste, and what if my filter never finishes?
Input is capped at 2 MB (2,000,000 bytes) and the results panel renders the first 200 outputs, always with the true count next to it — "Copy all" still copies every one. A filter that never terminates is the one thing that genuinely hurts: jq runs synchronously in this tab, so it blocks the page until you reload. Whether you get an error card instead depends on the shape. If the endless stream is collected — [repeat(1)], [recurse(.a)], length, last — jq fills its WebAssembly heap and aborts after a second or two, and the playground turns that into a normal error message. Left streaming, a bare repeat(1) or recurse(.a) just keeps running: we measured no abort after 40 seconds. The two shapes to watch for are an unguarded repeat and recurse(.field), which walks onto null and then recurses on null for ever — write recurse(.field?; . != null), or wrap the generator in limit(n; …).
More free, private DevOps tools.
The jq Playground is one tool in OpsCanopy — a growing canopy of browser-based validators, converters and testers that never touch a server.
39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.
Related: the Regex Log Tester for the log lines that are not JSON yet, the JSON ↔ YAML Converter for turning a manifest into something jq can read, and the JWT Decoder when the interesting JSON is inside a token — or browse the full tools directory.
Provided as-is for convenience; jq is a separate open-source project and this page is not affiliated with it. OpsCanopy is free and open.