Mango Queries in CouchDB: Faster Finds Without Writing Map Functions
Mango lets you query CouchDB with JSON selectors instead of JavaScript map functions. Learn how indexes are created, how to verify they are used, and the write and disk trade-offs to plan for.
29 Aug 2026, 14:46 UTC

You have a CouchDB database full of documents and a product team asking for ad-hoc filters like "all orders for tenant A with status pending created after 2025-01-01". With classic MapReduce views you would write a JavaScript map function, emit a composite key, redeploy the design document, and wait for the view to build. Mango changes that workflow: you describe the filter as JSON, CouchDB builds a materialized index for it, and queries run against the index via the RESTful _find endpoint.
The useful takeaway is that Mango trades the flexibility of JavaScript views for a simpler JSON selector and predictable index-based performance, at the cost of disk usage and write overhead.
From JavaScript views to JSON selectors
CouchDB views are MapReduce functions stored in design documents. They are powerful but require JavaScript, careful key design, and a full view rebuild when the map changes. Mango was added to let applications query with a JSON selector similar to MongoDB, without writing map functions.
A selector is a JSON object with field operators like $eq, $gt, $in, $and, $or. CouchDB evaluates the selector against an index. If no suitable index exists, CouchDB can build a temporary index on the fly, which is a warning sign for production.
How Mango indexes work
Each Mango index is stored as a design document with type "json". CouchDB builds the index incrementally as documents are added or updated, using a B-tree structure. When a query matches an index, lookup cost is logarithmic in the number of documents rather than a full scan.
Index selection is automatic. CouchDB chooses the best existing index for the selector. You can inspect the plan with the _explain endpoint to verify which index is used and avoid temporary indexes.
Important constraints: selectors are case-sensitive by default, Mango does not support joins, and the operator set is smaller than MongoDB's. Regex and full-text search still require the _search endpoint or a MapReduce view.
Worked example: index and query for orders
Create an index on tenant_id and created_at so you can filter by tenant and date range.
Run as a user with database write permission, for example via curl against your CouchDB host.
curl -X POST http://<couchdb-host>:5984/<db-name>/_index \
-H "Content-Type: application/json" \
-d '{
"index": {"fields": ["tenant_id", "created_at"]},
"name": "tenant_created_idx",
"type": "json"
}'Expected check: HTTP 201 Created and a response containing result "created" and the design document id. The index is built asynchronously. Risk: on a large database the initial build consumes I/O and can slow writes.
Query the index with _find:
curl -X POST http://<couchdb-host>:5984/<db-name>/_find \
-H "Content-Type: application/json" \
-d '{
"selector": {
"tenant_id": "A",
"status": "pending",
"created_at": {"$gt": {"$date": "2025-01-01T00:00:00Z"}}
},
"sort": [{"created_at": "desc"}],
"limit": 100
}'Verify the plan with _explain using the same selector payload posted to /_find/_explain. Check that "index" is the named index and not "temporary". If temporary is used, create a matching index.
Trade-offs you will feel in production
Materialized indexes speed reads but cost disk space and write latency. Each indexed field adds work on every document update. Under heavy write load, index updates happen asynchronously, so very recent writes may not be visible to Mango queries immediately.
Missing or mismatched indexes cause CouchDB to build a temporary index on first query, which can degrade latency and consume memory. Avoid this by pre-creating indexes for your common selectors and monitoring _explain.
Because Mango has no joins, you must denormalize data or issue multiple requests. The RESTful API makes that straightforward from any language, but application complexity moves from view design to data modeling.
Actionable next step: list your top three query patterns, create explicit JSON indexes for them, and add a check in deployment that runs _explain against a representative selector and fails if a temporary index is selected. That gives you the simplicity of JSON queries with confidence you are not paying a hidden scan cost.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.