Understanding the Solr JSON Response
Understanding the Solr JSON Response
When you query your Opensolr Web Crawler index via the /select API, you get back a JSON object. This page explains every section of that response so you know exactly how to parse it in your application.
The Basic Response Structure
Here is a simplified example of what comes back when you search:
{ "responseHeader": { "status": 0, "QTime": 12 }, "response": { "numFound": 1542, "start": 0, "numFoundExact": true, "docs": [ { ... }, { ... } ] }, "highlighting": { "doc_id_1": { ... }, "doc_id_2": { ... } }, "facet_counts": { "facet_fields": { ... } }, "spellcheck": { "suggestions": [ ... ], "collations": [ ... ] } }
responseHeader
"responseHeader": { "status": 0, "QTime": 12 }
| Field | Description |
|---|---|
status |
0 means success. Any other value means an error occurred. |
QTime |
Query execution time in milliseconds. This is how long Solr took to process your query (not including network time). A good search returns in under 50ms. |
response
This is the main section containing your search results.
"response": { "numFound": 1542, "start": 0, "numFoundExact": true, "docs": [ ... ] }
| Field | Description |
|---|---|
numFound |
Total number of documents matching your query. This is the number you display as "Showing results 1-10 of 1,542". |
start |
The offset you requested (from the start parameter). |
numFoundExact |
Whether numFound is exact (true) or an approximation. Almost always true. |
docs |
Array of document objects — your actual search results. |
A Document Object
Each item in the docs array looks like this:
{ "id": "bc434cee9e50b48f80095c9f49bdeae3", "uri": "https://yoursite.com/blog/my-article", "title": "My Article Title", "description": "A short description of the article...", "text": "The full body text of the page...", "og_image": "https://yoursite.com/images/article-thumb.jpg", "meta_icon": "https://yoursite.com/favicon.ico", "meta_domain": "yoursite.com", "meta_detected_language": "en", "content_type": "text/html", "creation_date": "2026-02-20T14:30:00Z", "timestamp": 1771684200, "sent_pos": 0.153, "sent_neu": 0.752, "sent_neg": 0.095, "sent_com": 0.9856, "score": 1.8234, "price_f": 29.99, "currency_s": "USD" }
The fields you get back depend on what you specified in the fl (field list) parameter. The score field is special — it is computed at query time and represents the relevancy score for that document.
Tip: Only request the fields you actually need. Requesting
text(full body text) for every result increases response size significantly. If you only display titles and snippets, usefl=id,uri,title,description,og_image,score.
highlighting
If you set hl=true in your query, you get a highlighting section keyed by document ID:
"highlighting": { "bc434cee9e50b48f80095c9f49bdeae3": { "title": ["My <em>Article</em> Title"], "description": ["A short description about <em>article</em> writing..."], "text": ["...the main body discusses <em>article</em> formatting and..."] }, "another_doc_id": { "title": ["Another <em>Article</em>"] } }
How to use it in your code:
- Loop through your
docsarray - For each doc, look up
highlighting[doc.id] - If a highlighted version exists for a field, use it instead of the raw field value
- The
<em>tags wrap the matching search terms — style them with CSS (bold, background color, etc.)
// Example: get the best title to display var docId = doc.id; var hl = data.highlighting[docId] || {}; var displayTitle = (hl.title && hl.title[0]) ? hl.title[0] : doc.title;
Important: Highlighted text contains HTML (
<em>tags), so you should insert it usinginnerHTML, nottextContent. The rest of the text is already HTML-escaped by Solr.
facet_counts
If you set facet=true, you get a facet_counts section:
"facet_counts": { "facet_fields": { "meta_detected_language": [ "en", 834, "de", 245, "fr", 123, "es", 89, "nl", 42 ], "currency_s": [ "USD", 156, "EUR", 89 ] } }
The facet arrays alternate between value and count: ["en", 834, "de", 245, ...] means 834 results in English, 245 in German, etc.
Parsing facets in JavaScript:
var langFacets = data.facet_counts.facet_fields.meta_detected_language; var facets = []; for (var i = 0; i < langFacets.length; i += 2) { facets.push({ value: langFacets[i], count: langFacets[i+1] }); } // facets = [{value: "en", count: 834}, {value: "de", count: 245}, ...]
You can then render these as clickable filter buttons or a sidebar. When the user clicks "English (834)", add fq=meta_detected_language:en to your query to filter.
spellcheck
If you enabled spellcheck, you get suggestions for misspelled words:
"spellcheck": { "suggestions": [ "opnesolr", { "numFound": 1, "suggestion": ["opensolr"] } ], "collations": [ "collation", "opensolr search" ] }
The collations section is the most useful — it gives you a corrected version of the entire query that you can show as "Did you mean: opensolr search?".
Parsing collations:
var collations = data.spellcheck ? data.spellcheck.collations : []; if (collations.length >= 2) { var suggestion = collations[1]; // The corrected query string // Display: "Did you mean: <a>suggestion</a>?" }
Error Responses
If something goes wrong, the response looks like this:
{ "responseHeader": { "status": 400 }, "error": { "msg": "undefined field: nonexistent_field", "code": 400 } }
Always check responseHeader.status — if it is not 0, read the error.msg for details. Common errors:
- 400 — Bad query syntax, undefined field, or invalid parameter value
- 500 — Server error (rare, usually a misconfigured query)
stats
If you requested stats on a numeric field (e.g., stats=true&stats.field=price_f):
"stats": { "stats_fields": { "price_f": { "min": 4.99, "max": 299.99, "count": 156, "missing": 1386, "sum": 12450.50, "mean": 79.81 } } }
This is useful for displaying price ranges or other numeric summaries in your UI.