iq runs jq filters to query, dump, copy, diff and write data
across NoSQL databases, and their dump files, from a single static binary. See
Drivers for supported databases.
iq normalizes fetched values to JSON. The filter runs entirely client-side,
so one filter means the same thing everywhere.
The filter is also the key selector. Its top-level paths name the keys to fetch. As a result, a query reads only what it asks for (see Architecture).
Typed dumps carry native types across stores. As a result, a copy, a restore or a migration is one command instead of an export plus a conversion script.
iq is inspired by sq, whose command set it
deliberately follows to make the tool feel familiar.
Note
iq is built with AI assistance, every change passes the full test suite,
container-backed integration tests for every backend and a mutation gate before it lands
(see CONTRIBUTING.md).
Queries are read-only. --insert, --replace, iq data clear, iq data drop and
iq data delete write to the target. iq exec forwards a native command to the database,
so it can write too. Use --explain to see the query plan or --dry-run to report the
effect of a write, without changing anything. iq exec has no dry run.
Feedback and bug reports are very welcome. Report security problems privately, see the security policy.
iq ships as a single static binary (no runtime dependencies, no CGO), prebuilt for Linux,
macOS, and Windows on amd64 and arm64.
curl -fsSL https://raw.githubusercontent.com/zsltg/iq/main/install.sh | shThe script downloads the release for your OS/arch, verifies its SHA-256 against the release
checksums, and installs the binary. IQ_VERSION pins a version and IQ_INSTALL_DIR picks the
target directory. Or grab a .deb, .rpm, .apk, or Arch .pkg.tar.zst from the
releases. Each release carries a cosign signature
and SLSA build provenance, see Verify a release.
brew install zsltg/tap/iqThe Linux curl … | sh one-liner works on macOS too.
scoop bucket add zsltg https://github.com/zsltg/scoop-bucket
scoop install iqgo install github.com/zsltg/iq@latestgit clone https://github.com/zsltg/iq
cd iq && make buildiq ships an Agent Skill, a single Markdown file
(skills/iq/SKILL.md) that any agent reading the Agent Skills format
can load. It covers these topics:
- Finding a source
--explainbefore every scan--dry-runbefore every write- The machine-readable output flags and the JSON error shape
- The rules around destructive commands.
npx skills add zsltg/iqFor an agent that speaks MCP, iq mcp
serves the same core over stdio.
It is read-only by default. --allow writes|exec|destructive opens the rest.
A tool that is not allowed is never registered. Every result is capped, every
error is redacted, and every destructive call is confirmed.
Claude Code:
claude mcp add iq -- iq mcp --timeout 30sCodex CLI:
codex mcp add iq -- iq mcp --timeout 30sGemini CLI:
gemini mcp add iq iq mcp -- --timeout 30sCursor, Cline and Antigravity:
{
"mcpServers": {
"iq": {
"command": "iq",
"args": ["mcp", "--timeout", "30s"]
}
}
}Copilot in VS Code, which names the map servers and wants the transport spelled out:
{
"servers": {
"iq": {
"type": "stdio",
"command": "iq",
"args": ["mcp", "--timeout", "30s"]
}
}
}OpenCode, which names it mcp and takes the command as one array:
{
"mcp": {
"iq": {
"type": "local",
"command": ["iq", "mcp", "--timeout", "30s"],
"enabled": true
}
}
}Check AI agents for each client's config file and the key it wants around the block.
Shell completions
The .deb, .rpm, .apk and .pkg.tar.zst packages install bash, zsh and fish completions
for you. For a brew, scoop, go-install or source build, iq completion <shell> prints a script to install by
hand:
# bash: load in the current session, or drop it on the completion path
eval "$(iq completion bash)"
iq completion bash | sudo tee /usr/share/bash-completion/completions/iq >/dev/null
# zsh: write to a directory on your $fpath, then restart the shell
iq completion zsh > ~/.zsh/completions/_iq
# fish
iq completion fish > ~/.config/fish/completions/iq.fish
# powershell: append to your profile
iq completion powershell >> $PROFILECompletions cover the commands, their sub-subcommands and flags. They also cover your saved
source handles, groups, and config-option keys, which they read live from your config. As a
result, iq --src <TAB> offers the sources iq ls lists.
In zsh, fish, and PowerShell, a candidate shows a short description next to it. Bash shows the
descriptions when it lists more than one candidate. A source handle shows its driver, its keyspace,
and active for the active source, for example prod/books mongo, orders, active. It never shows
the host, the user, or the URI. A group shows its number of sources, and a config key shows the
help text of its flag. To turn the descriptions off, generate the script with
iq completion <shell> --no-descriptions.
A flag that takes a closed set completes its values (--format, --from-format,
--format.decimal, --log.level, --log.format, --error.format, --debug.pprof,
iq add --driver/--store, iq schema --format). iq config set <option> <TAB> offers that
option's own values. iq inspect --only <TAB> and iq diff --section <TAB> offer the
introspection subcommands of the selected source's backend, worked out from its saved URI.
iq add <TAB> completes the connection URI part by part. It offers the schemes, then the names
of the URI options that iq itself reads, then the values of an option that has a closed set of
values. A shell gives ? and & a special meaning, so type them with a backslash, for example
iq add cassandra://host/ks\?consistency=lo<TAB>, which offers local_quorum and local_one.
Completion does not work inside quotes, because the bash and zsh completion scripts pass the quote
character to iq. A quoted URI still runs correctly. Completion does not offer the host, the path,
or the options that a backend SDK reads, such as authSource for MongoDB. It also offers nothing
when the typed text holds a password, so a password never appears in a candidate.
The jq filter itself is a program, not a completable value. As a result, iq offers no
candidates there (and never falls back to filenames). iq also offers no candidates for the
backend verb of iq exec and its operands.
Every completion is offline. It reads your config file and nothing else. As a result, a <TAB>
never opens a connection, never reads the OS keyring, and cannot hang. That is why a collection
suffix does not complete. iq --src shop.<TAB> offers nothing, because listing collections
needs a connection.
Man page
The packages also install an iq(1) manual page, so man iq works after a package install. For
a non-package install, pipe it into your man path:
iq man | sudo tee /usr/share/man/man1/iq.1 >/dev/nullDriver list:
iq driver lsAdd a collection named "books" from a MongoDB source and make it active:
iq add -a 'mongodb://localhost:27017/iq?collection=books'Inspect a source:
iq inspect booksList sources:
iq lsCheck Sources for more details.
Fetch an item with the ID "2":
iq '.["2"]'Filter a scan and reshape each item:
iq '.[] | select(.year > 2015) | {title, price}'Print a formatted query plan:
iq '.[] | select(.year > 2015) | {title, price}' --explain -vCheck Query data for more details.
Diff schema of two sources:
iq diff --schema dev qaDiff the items with the same ID from two sources:
iq diff 'dev=.["1"]' 'qa=.["1"]'Diff items key by key:
iq diff dev qaCheck Diff for more details.
Copy one source into another, keys preserved (a cross-driver copy carries values only):
iq --src books --insert books2Create a lossless (typed) dump:
iq --src cache --typed -o dump.jsonlAdd a dump as a source and restore it to a live source:
iq add file:///dump.jsonl -n snap
iq --src snap --insert cacheCopy only the items a filter selects:
iq '.[] | select(.year > 2015)' --src books --insert recentCheck Write data for more details.
Compose across sources:
iq 'INDEX(source("users"; ".[]"); .id) as $u
| source("orders"; ".[] | select(.total > 99)")
| {name: $u[.userId].name, total}'Combine across sources:
iq combine 'users=.[] | {id, name}' \
'orders=.[] | select(.total > 99)' \
--with '($users | INDEX(.id)) as $u | $orders[] | . + {name: $u[.userId].name}'Check Cross-source queries for more details.
Delete two items:
iq data delete cache book:1 book:2Empty a source called "cache":
iq data clear cacheDrop a collection called "orders":
iq data drop shop.ordersCheck Delete, Clear and Drop for more details.
Implicit stdin source:
cat dump.jsonl | iq '.[]'Check Insert for more details on piped stdin.
iq picks the backend from a source's URI scheme. The query core is driver-agnostic, so
further backends slot in behind the same port. The
Drivers page documents these topics for each driver:
- Keyspace mapping
- Value encoding
- Predicate pushdown
- Raw commands.
| Name | Database | Versions |
|---|---|---|
cassandra |
Apache Cassandra | 3.11+ |
couchbase |
Couchbase | 7.x, 8.x (Community or Enterprise) |
couchdb |
Apache CouchDB | 2.x, 3.x |
dynamodb |
Amazon DynamoDB | AWS (managed) |
elasticsearch |
Elasticsearch | 8.x |
file |
Local dump file, read-only (see Backups and dumps) | — |
hbase |
Apache HBase | 1.0+ |
mongo |
MongoDB | 4.2+ |
neo4j |
Neo4j | 5.x |
opensearch |
OpenSearch | 2.x, 3.x |
redis |
Redis | 7.0+ |
The Versions column lists the range of backend server versions the bundled client library supports.
Drivers differ in encoding and pushdown detail, but every backend honors the same contract:
- One URI, native nouns. The URI scheme picks the driver. The keyspace rides in the URI as the
backend's own noun (
?collection=,?table=,?database=,?label=/?rel=,?index=). A query overrides it per run with the dottedhandle.<keyspace>suffix. - One jq interface. A bounded filter fetches exactly the named keys. A missing key reads as
null, never an error. A.[]-rooted filter streams the keyspace in bounded pages. A filter that collapses the keyspace into one value materializes only behind--unbounded. - Pushdown never changes results. A pushed predicate is only ever a conservative pre-filter.
Where the backend can filter, the pre-filter is server-side. Where the backend cannot, the
pre-filter is a client-side raw-byte prefilter that drops a provable non-match before decode
(Redis, on RedisJSON values, Elasticsearch/OpenSearch and Couchbase, over the residual their
server-side query cannot narrow). The full jq always re-runs client-side. As a result, output is
identical with or without the pushed predicate, and
--explainshows exactly what was pushed. - Capabilities are explicit. Filtered scans, count estimates, writes, clear, drop and per-key
delete are opt-in ports. A backend implements what its model supports. A command against a
missing capability fails with a clear message instead of emulating it. For example, Redis, whose
DB index cannot be removed, has no
drop. The read-only file dump has no per-keydelete. - Values round-trip. Every value normalizes to JSON under a frozen per-backend encoding
contract. A
--typeddump restores through--insertlosslessly. - Bounded and redacted.
--timeoutbounds every backend call.iqredacts a URI's password from every listing, log line and error. - Native commands.
iq execspeaks the backend's own language, verbatim where one exists (Redis commands, Mongo command documents, CQL, PartiQL, Cypher, Mango, the Elasticsearch DSL). Where none exists,iq execspeaks a small fixed verb set (HBase). See each driver's Raw commands section on the Drivers page. Everyiqflag must come beforeexec.iqforwards everything afterexecto the backend untouched.
A file:// source reads a database dump straight from disk. As a result, you can do these
operations on a snapshot through the same jq interface, with no running server:
- Query it
- Inspect it for shape
- Diff it against a live source
- Restore it.
A file:// source is read-only. A file:// endpoint is never a copy destination. iq exec and
iq inspect, which need a live server, do not apply.
Register a dump like any other source:
iq add -n snap file:///backups/prod.rdbQuery, diff and restore it with no server running:
iq --src snap '.["session:42"]'
iq diff snap cache --data
iq --src snap --insert cache| Type | Description |
|---|---|
jsonl |
iq typed JSON Lines / array |
yaml |
iq typed YAML |
mongoexport |
mongoexport Extended JSON |
bson |
mongodump BSON |
rdb |
Redis RDB snapshot |
?format=dynamodb-json |
DynamoDB S3 export / scan JSON |
?format=cassandra-csv |
cqlsh COPY TO CSV |
?format=neo4j-json |
Neo4j APOC JSON export |
A type with a bare name auto-detects (file:///<file_path>). For a type in the ?format= rows,
you must pass the ?format= form (file:///<file_path>?format=<source_format>). The
Drivers page documents what produces each
format, the options some of them need, and the round-trip caveats.
The URI scheme chooses the backend. The query core is driver-agnostic, so further backends slot in behind the same port.
The filter is both the transform and the key selector. Its top-level paths name the keys to
fetch, so a normal query reads a bounded set of keys. A .[]-rooted filter streams the
keyspace in pages. A filter that collapses the keyspace into one value materializes only behind
--unbounded. iq normalizes fetched values to JSON. The filter then runs entirely
client-side, so its semantics are identical for every backend.
The CLI and iq mcp are two thin delivery mechanisms over that one core. The MCP server
exposes the CLI's own operations as tools, resolves the same saved sources, and runs the same
engine. As a result, it adds no port and changes no classification. It adds only its own bounds,
a tool set fixed at startup by --allow, per-result item and byte caps, and the CLI's redacted
error shape.
The selector classifies a jq filter. iq optionally decomposes a scan
into a native predicate. Each backend then maps that predicate its own way.
Regardless of pushdown, the full jq re-runs client-side. As a result, the pushed predicate is only ever a conservative pre-filter, and results are identical with or without it.
Pushdown reads only the select() stages that test each item itself: the stages
after .[] up to the first stage that changes the item, such as .b, map(...)
or an object construction. A select() after such a stage tests a derived value,
so pushdown leaves it to the client-side filter.
graph TD
F["jq filter (CLI)"] --> SEL["selector.Keys<br/>(AST analysis)"]
SEL -->|bounded| GET["KVStore.Get(keys)"]
SEL -->|"streamable scan"| CMP{"FilteredScanner?<br/>(pushdown on)"}
SEL -->|"holistic scan"| MAT["materialize<br/>(--unbounded)"]
CMP -->|yes| PD["pushdown.Compile → predicate.Node<br/>(adapter pre-filters server-side)"]
CMP -->|no| RS["KVStore.ScanBatches<br/>(full scan, no pushdown)"]
GET --> JQ["run full jq<br/>client-side, per batch"]
MAT --> JQ
PD --> JQ
RS --> JQ
JQ --> OUT["format renderer<br/>→ output"]
A scan emits per-page progress to a stderr spinner (CLI only, off unless attached to a terminal). An unfiltered scan can also fetch a cheap up-front total estimate (where the backend metadata makes it possible).
A bounded filter runs client-side over only the named keys, so its cost is O(keys requested). A
streamable scan runs in O(page) memory.
gojq (pure Go, no cgo) provides jq and exposes the AST the key selector walks.
Writes ride the query command. There is no separate copy tool. Items arrive
from --src (a live source or a file:// dump) or piped stdin. iq reads
each item as a typed record, so the native type survives the trip.
The jq filter transforms each item with its key preserved. This transform is
the one place where a filter runs per item rather than over the whole keyspace.
Iteration is implicit, and you do not write .[].
graph TD
MV["iq --insert / --typed (CLI)"] --> MSRC["source: --src (live or file:// dump) / stdin → TypedScan"]
MSRC --> TX["per-item jq transform + re-key"]
TX --> DST{"--insert or --typed?"}
DST -->|--insert| PUT["Putter.Put (upsert / insert-only)"]
DST -->|--typed| ENC["emit {key,type,value} → jsonl / json / jsona / yaml"]
PUT --> BW["backend adapter:<br/>type-aware native writes"]
LF["iq data clear / drop / delete (CLI)"] --> CAP["Clearer.Clear / Dropper.Drop / Deleter.Delete (capability-gated)"]
Where iq sits among other tools, and the jq ecosystem its filter language carries over.
- jq manual: the language reference for the filters
iqruns. - awesome-jq: a curated list of jq tools, guides, and resources.
- sq: jq-style queries over SQL databases and document files.
- gojq: the pure-Go jq implementation
iqembeds. - jaq: a Rust jq clone focused on speed and stricter semantics.
- yq: jq-style filters for YAML, TOML, and XML.
- fq: jq for binary formats.
- jc: converts classic CLI output to JSON.
- jqp: a TUI playground that live-previews a filter as you type.
- ijq: interactive jq with a side-by-side input and output view.