Faceted Navigation & Filtering
Faceted Navigation & Filtering
Faceted navigation lets users refine their search results by clicking on filter options like language, domain, date range, content type, or price. Think of how Amazon lets you filter by brand, rating, and price range — that is faceted navigation.
Your Opensolr Web Crawler index supports faceting out of the box on multiple fields.
How Faceting Works
When you add faceting parameters to your search query, Solr returns the normal search results plus a count of how many results exist for each unique value of the faceted field.
For example, faceting on meta_detected_language might return:
- English: 834 results
- German: 245 results
- French: 123 results
- Spanish: 89 results
The user can then click "German" to filter and see only the 245 German-language results.
Facet Parameters
Add these to your search query:
facet=true facet.field=meta_detected_language facet.field=meta_domain facet.field=content_type facet.mincount=1 facet.sort=count
| Parameter | Description |
|---|---|
facet=true |
Enable faceting |
facet.field |
Which field to facet on (can specify multiple) |
facet.mincount |
Minimum count to include a facet value. Set to 1 to hide empty values. |
facet.sort |
count (most results first) or index (alphabetical) |
facet.limit |
Maximum number of facet values to return (default: 100). Set to -1 for unlimited. |
Best Fields to Facet On
| Field | What It Shows | Use Case |
|---|---|---|
meta_detected_language |
Language of each page (en, de, fr, es...) | "Filter by Language" sidebar |
meta_domain |
Source domain name | "Filter by Site" when crawling multiple domains |
content_type |
MIME type (text/html, application/pdf...) | "Filter by Type" (Web pages, PDFs, Documents) |
category |
Content category | "Filter by Category" |
currency_s |
Currency code (USD, EUR...) | "Filter by Currency" for e-commerce |
Note: You can only facet on string or numeric fields with
docValues=true. Text fields (liketitleordescription) cannot be used for faceting because they are analyzed/tokenized.
Applying a Facet Filter
When the user clicks on a facet value, you add a filter query (fq) to your search:
fq=meta_detected_language:en
This narrows results to only English pages. The important thing about fq is that it does not affect the relevancy score — it just filters. And because Solr caches filter queries, applying and removing filters is very fast.
You can combine multiple filters:
fq=meta_detected_language:en fq=meta_domain:yoursite.com fq=content_type:text/html
Multiple fq parameters are AND-ed together.
Date Range Faceting
For date-based filtering, you can use range faceting to create time buckets:
facet=true facet.range=creation_date facet.range.start=NOW-1YEAR facet.range.end=NOW facet.range.gap=+1MONTH
This returns counts for each month in the past year:
"facet_ranges": { "creation_date": { "counts": [ "2025-03-01T00:00:00Z", 45, "2025-04-01T00:00:00Z", 67, "2025-05-01T00:00:00Z", 89, ... "2026-02-01T00:00:00Z", 123 ] } }
You can then render this as a timeline or date filter.
Price Range Faceting
For e-commerce indexes with price data:
facet=true facet.range=price_f facet.range.start=0 facet.range.end=500 facet.range.gap=50
Returns: 0-50 (23 items), 50-100 (45 items), 100-150 (12 items), etc.
JavaScript Implementation
Here is a complete example of rendering a language facet sidebar and handling clicks:
function renderFacets(data) { var facetDiv = document.getElementById('facets'); var facets = data.facet_counts.facet_fields; var html = ''; // Language facet if (facets.meta_detected_language) { html += '<h4>Language</h4><ul class="facet-list">'; var langs = facets.meta_detected_language; for (var i = 0; i < langs.length; i += 2) { var lang = langs[i]; var count = langs[i + 1]; if (count === 0) continue; var label = getLanguageName(lang); // "en" -> "English" var active = currentFilters.lang === lang ? ' class="active"' : ''; html += '<li' + active + '>' + '<a href="#" onclick="toggleFilter(\'meta_detected_language\', \' ' + lang + ' \')">' + label + ' <span class="count">(' + count + ')</span>' + '</a></li>'; } html += '</ul>'; } // Domain facet if (facets.meta_domain) { html += '<h4>Source</h4><ul class="facet-list">'; var domains = facets.meta_domain; for (var i = 0; i < domains.length; i += 2) { var domain = domains[i]; var count = domains[i + 1]; if (count === 0) continue; html += '<li><a href="#" onclick="toggleFilter(\'meta_domain\', \' ' + domain + ' \')">' + domain + ' <span class="count">(' + count + ')</span>' + '</a></li>'; } html += '</ul>'; } facetDiv.innerHTML = html; } function toggleFilter(field, value) { // Add or remove the fq parameter and re-run the search if (currentFilters[field] === value) { delete currentFilters[field]; } else { currentFilters[field] = value; } doSearch(); } function getLanguageName(code) { var names = { en: 'English', de: 'German', fr: 'French', es: 'Spanish', it: 'Italian', nl: 'Dutch', pt: 'Portuguese', ro: 'Romanian', ja: 'Japanese', zh: 'Chinese', ko: 'Korean', ar: 'Arabic', ru: 'Russian' }; return names[code] || code.toUpperCase(); }
Facet Sidebar CSS
.facet-list { list-style: none; padding: 0; margin: 0 0 20px; } .facet-list li { margin: 0; } .facet-list li a { display: block; padding: 6px 0; color: #333; text-decoration: none; font-size: 14px; } .facet-list li a:hover { color: #e8650a; } .facet-list li.active a { font-weight: bold; color: #e8650a; } .facet-list .count { color: #999; font-size: 12px; }
Tips
- Always include facet counts in your UI — showing "English (834)" is much more useful than just "English". Users can see at a glance how many results each filter will give them.
- Use
facet.mincount=1— Do not show facet values with zero results. It is confusing for users. - Recalculate facets on every search — When the user changes their query or applies a filter, the facet counts change. Always send
facet=truewith every search request. - Active filters should be visually highlighted — Make it obvious which filters are currently applied, and provide a way to remove them (click again or an "x" button).