Filtering & Faceting

Introduction

There are multiple ways to filter your results using Algolia. You can filter by date, by numerical value, by tag. You can also use facets, which are a filter with the added benefits of being able to retrieve and display the values to filter by.

Let’s see how all of these work!

Filtering

There are several way to filter a result set, depending on the attribute you want to filter by, please consider one of the following methods:

Filter by Numerical Value

Numerical Values Indexing

Algolia supports indexing of numerical values (integers, doubles and boolean). This can be used for searching for products in a given price range for example.

To enable it, you need to have objects with numerical attributes (ensure your numerical values are not encoded as strings, for boolean we transform false as 0 and true as 1).

Considering the following object with its price attribute:

{
  "title": "Apple MacBook Pro 15.4-Inch Laptop with Retina Display",
  "price": 2594,
  "url": "http://www.amazon.com/Apple-MacBook-ME294LL-15-4-Inch-Display/dp/B0096VD85I"
}, {
  "title": "Apple iPhone 5S - 32GB",
  "price": 969.99,
  "url": "http://www.amazon.com/Apple-iPhone-5s-32GB-Space/dp/B00F3J4KYA"
},
// [...]

You can search with numeric conditions on the price. We support six operators: <, <=, =, >, >= and !=.

// search only with a numeric filter
index.search({
  filters: 'price>1000'
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by query string and numeric filter
index.search('los', {
  filters: 'price>1000'
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search only with a numeric filter
Query* query = [Query new];
query.filters = @"price>1000";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    if (content == nil) {
        NSLog(@"Error: %@", error);
        return;
    }
    NSLog(@"Results: %@", content);
}];
// search by query string and numeric filter
query = [Query new];
query.query = @"los";
query.filters = @"price>1000";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search only with a numeric filter
var query = Query()
query.filters = "price>1000"
index.search(query) { (content, error) in
    guard let content = content else {
        print("Error: \(error)")
        return
    }
    print("Results: \(content)")
}
// search by query string and numeric filter
query = Query()
query.query = "los"
query.filters = "price>1000"
index.search(query) { (content, error) in
    // [...]
}
// search only with a numeric filter
Query query = new Query();
query.setFilters("price>1000");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        if (content == null) {
            System.err.println("Error: " + error);
            return;
        }
        System.out.println("Results: " + content);
    }
});
// search by query string and numeric filter
query = new Query();
query.setFilters("price>1000");
query.setQuery("los");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

You can also mix OR and AND operators. The OR operator is defined with a parenthesis syntax (warning: != cannot be ORed). For example (code=1 AND (price:[0-100] OR price:[1000-2000])) translates in:

// search by query string and complex numerical conditions
index.search('los', {
  filters: 'code=1 AND (price:1000 TO 3000 OR price:10 TO 100)'
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by query string and complex numerical conditions
Query* query = [Query new];
query.query = @"los";
query.filters = @"code=1 AND (price:1000 TO 3000 OR price:10 TO 100)";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string and complex numerical conditions
let query = Query()
query.query = "los"
query.filters = "code=1 AND (price:1000 TO 3000 OR price:10 TO 100)"
index.search(query) { (content, error) in
    // [...]
}
// search by query string and complex numerical conditions
Query query = new Query();
query.setQuery("los");
query.setFilters("code=1 AND (price:1000 TO 3000 OR price:10 TO 100)");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

Filter by date

Algolia supports indexing of dates. To enable search by date, you must convert your dates in numeric values. We recommend to use a unix timestamp as illustrated by the following record:

{
  "objectID": "myID1",
  "name": "Jimmy",
  "company": "Paint Inc.",
  "date": 1362873600 // UNIX timestamp as an integer
}

You can then use standard numeric operators in your search. You can even search by date range by combining two operators as illustrated by the following example:

// search by date between 2013-03-10 & 2013-04-20
index.search({
  filters: 'date>=1362873600 AND date<=1366416000'
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by date between 2013-03-10 & 2013-04-20
Query* query = [Query new];
query.filters = @"date>=1362873600 AND date<=1366416000";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by date between 2013-03-10 & 2013-04-20
let query = Query()
query.filters = "date>=1362873600 AND date<=1366416000"
index.search(query) { (content, error) in
    // [...]
}
// search by date between 2013-03-10 & 2013-04-20
Query query = new Query();
query.setFilters("date>=1362873600 AND date<=1366416000");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

In the case you need to index and filter on dates previous to the 1st January 1970, you can convert the date following a rule like: year * 10000 + month * 100 + day. The 30th March 1964 will then become 19640330.

Filter by tag

Algolia supports indexing of categories (tags) that you can use when searching for a specific kind of objects.

To enable it, you need to index objects with a _tags attribute that contains the list of their categories (You can also use faceting, tags is just a simplified version of faceting).

Here is an example indexing products with tags:

{
  "title": "Apple MacBook Pro 15.4-Inch Laptop with Retina Display"
  "url": "http://www.amazon.com/Apple-MacBook-ME294LL-15-4-Inch-Display/dp/B0096VD85I",
  "_tags": ["laptop", "computer", "retina"],
},
{
  "title": "Apple iPhone 5S - 32GB",
  "url": "http://www.amazon.com/Apple-iPhone-5s-32GB-Space/dp/B00F3J4KYA",
  "_tags": ["phone", "smartphone", "retina"]
},
// [...]

You can then easily search for one or multiple tags, using our filters feature:

// search only by tags
index.search({
  filters: 'smartphone AND retina' // smartphone AND retina
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by query string and tags
index.search('appl', {
  filters: 'smartphone AND retina' // smartphone AND retina
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search only by tags
Query* query = [Query new];
query.filters = @"smartphone AND retina";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string and tags
query = [Query new];
query.query = @"appl";
query.filters = @"smartphone AND retina";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search only by tags
var query = Query()
query.filters = "smartphone AND retina"
index.search(query) { (content, error) in
    // [...]
}
// search by query string and tags
query = Query()
query.query = "appl"
query.filters = "smartphone AND retina"
index.search(query) { (content, error) in
    // [...]
}
// search only by tags
Query query = new Query();
query.setFilters("smartphone AND retina");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});
// search by query string and tags
query = new Query();
query.setQuery("appl");
query.setFilters("smartphone AND retina");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

You can also mix OR and AND operators. The OR operator is defined with a parenthesis syntax. For example (retina AND (laptop OR smartphone)) translates in:

// search by query string and tags
index.search('appl', {
  filters: 'retina AND (smartphone OR laptop)' // retina AND (smartphone OR laptop)
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by query string and tags
Query* query = [Query new];
query.query = @"appl";
query.filters = @"retina AND (smartphone OR laptop)";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string and tags
let query = Query()
query.query = "appl"
query.filters = "retina AND (smartphone OR laptop)"
index.search(query) { (content, error) in
    // [...]
}
// search by query string and tags
Query query = new Query();
query.setQuery("appl");
query.setFilters("retina AND (smartphone OR laptop)");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

Negations are also supported via the NOT operator, prefixing the value. For example (retina AND NOT(smartphone)) translates in:

// search by query string and tags
index.search('appl', {
  filters: 'retina AND NOT smartphone', // retina AND NOT(smartphone)
}, function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  console.log(content);
});
// search by query string and tags
Query* query = [Query new];
query.query = @"appl";
query.filters = @"retina AND NOT smartphone";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string and tags
let query = Query()
query.query = "appl"
query.filters = "retina AND NOT smartphone"
index.search(query) { (content, error) in
    // [...]
}
// search by query string and tags
Query query = new Query();
query.setQuery("appl");
query.setFilters("retina AND NOT smartphone");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

Filter by facet

You can also filter your result set using facets. We’ll have a look at facets in the next section.

Faceting

Algolia supports faceting and faceted search (or filtering, navigation). To enable it, you need to set the list of attributes on which you want to enable faceting in the index settings. The attributes can be either numerical or string values.

Here is an example of a book listing:

{
  "title": "The Hitchhiker's Guide to the Galaxy",
  "authors": "Adams Douglas",
  "type": "Literature & Fiction",
  "url": "http://www.amazon.com/Hitchhikers-Guide-Galaxy-Douglas-Adams/dp/0345391802"
},
{
  "title": "Remote: Office Not Required",
  "authors": ["Jason Fried", "David Heinemeier Hansson"],
  "type": "Business & Investing",
  "url": "http://www.amazon.com/Remote-Office-Required-Jason-Fried/dp/0804137501"
},
{
  "title": "Rework",
  "authors": ["Jason Fried", "David Heinemeier Hansson"],
  "type": "Business & Investing",
  "url": "http://www.amazon.com/Rework-Jason-Fried/dp/0307463745"
},
// [...]

You can enable faceting on the authors and type attributes with the following code:

index.set_settings({"attributesForFaceting" => ["authors", "type"]})
class MyModel < ActiveRecord::Base
  include AlgoliaSearch
  algoliasearch per_environment: true do
    attributesForFaceting ['author', 'type']
  end
end
index.set_settings({"attributesForFaceting": ["authors", "type"]})
index.setSettings({attributesForFaceting: ['authors', 'type']});
<?php
$index->setSettings(array("attributesForFaceting" => array("authors", "type")));
<?php
/**
 *
 * @ORMEntity
 *
 * @AlgoliaIndex(
 *     attributesForFaceting = {"authors", "type"}
 * )
 *
 */
class Contact
{
}
// please consider using a backend client to change the index settings
index.setSettings(new JSONObject()
      .append("attributesForFaceting", "authors")
      .append("attributesForFaceting", "type"));
settings := make(map[string]interface{})
settings["attributesForFaceting"] = []string{"authors", "type"}
index.SetSettings(settings)
curl --header 'X-Algolia-API-Key: YourAPIKey' \
     --header 'X-Algolia-Application-Id: YourApplicationID' \
     --data-binary '{"attributesForFaceting": ["authors", "type"]}' \
     --request PUT https://APP_ID.algolia.net/1/indexes/YourIndexName/settings
index.SetSettings(JObject.Parse(@"{""attributesForFaceting"":[""authors"", ""type""]}"));

In the query you need to specify the list of attributes on which you want to enable faceting (* is a shortcut to enable faceting on all attributes specified in index settings).

// search by query string with faceting on all attributes
index.search('appl', {facets: '*'}, searchCallback);
// search by query string with faceting on authors attribute
index.search('appl', {facets: 'authors'}, searchCallback);
// search by query string with faceting on authors & type attributes
index.search('appl', {facets: 'authors,type'}, searchCallback);
// search by query string with faceting on all attributes
Query* query = [Query new];
query.query = @"appl";
query.facets = @[ @"*" ];
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string with faceting on authors attribute
query = [Query new];
query.query = @"appl";
query.facets = @[ @"authors" ];
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string with faceting on authors & type attributes
query = [Query new];
query.query = @"appl";
query.facets = @[ @"authors", @"type" ];
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// search by query string with faceting on all attributes
var query = Query()
query.query = "appl"
query.facets = ["*"]
index.search(query) { (content, error) in
    // [...]
}
// search by query string with faceting on authors attribute
query = Query()
query.query = "appl"
query.facets = ["authors"]
index.search(query) { (content, error) in
    // [...]
}
// search by query string with faceting on authors & type attributes
query = Query()
query.query = "appl"
query.facets = ["authors", "type"]
index.search(query) { (content, error) in
    // [...]
}
// search by query string with faceting on all attributes
Query query = new Query();
query.setQuery("appl");
query.setFacets("*");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        
    }
});
// search by query string with faceting on authors attribute
query = new Query();
query.setQuery("appl");
query.setFacets("authors");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
    }
});
// search by query string with faceting on authors & type attributes
query = new Query();
query.setQuery("appl");
query.setFacets("authors", "type");
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
    }
});

Filtering / Navigation

Filtering faceting 1

Conjunctive Faceting

You can implement faceted navigation (or facet filtering) by specifying the list of facet values you want to use as refinements using the filters query parameter:

// filter on author=Adams Douglas AND type=Literature & Fiction
index.search('appl', {
  facets: '*',
  filters: "authors:\"Adams Douglas\" AND type:\"Literature & Fiction\""
}, searchCallback);
// filter on author=Adams Douglas AND type=Literature & Fiction
Query* query = [Query new];
query.query = @"appl";
query.facets = @[ @"*" ];
query.filters = @"authors:\"Adams Douglas\" AND type:\"Literature & Fiction\"";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
// filter on author=Adams Douglas AND type=Literature & Fiction
var query = Query()
query.query = "appl"
query.facets = ["*"]
query.filters = "authors:\"Adams Douglas\" AND type:\"Literature & Fiction\""
index.search(query) { (content, error) in
    // [...]
}
// filter on author=Adams Douglas AND type=Literature & Fiction
Query query = new Query();
query.setQuery("appl");
query.setFacets("*");
query.setFilters("authors:\"Adams Douglas\" AND type:\"Literature & Fiction\"")
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        
    }
});

Do not forget to configure your attributesForFaceting index setting with the list of attributes of want to facet on, otherwise you’ll not be able to use it at query-time.

Refinements are ANDed by default (Conjunctive selection).

You can get facets’ counts in the facets attribute of the JSON answer. These might be approximated (linear approximation) based on the size of the index. You can check if they are by using the exhaustiveFacetsCount of the JSON answer.

{
  "hits": [ ... ],
  "page": 0,
  "nbHits": 2,
  "nbPages": 1,
  "hitsPerPage": 20,
  "processingTimeMS": 1,
  "query": "appl",
  "params": "query=appl&facets=*",
  "facets": {
    "authors": {
      "Jason Fried": 2,
      "David Heinemeier Hansson": 1,
      "Adams Douglas": 1
    },
    "type": {
      "Literature & Fiction": 1,
      "Business & Investing": 2
    }
  },
  "exhaustiveFacetsCount": true
}

To OR refinements, you must use nested arrays. For example, to refine on “Business & Investing” books written by Jason Fried or David Heinemeier Hansson:

index.search('appl', {
  facets: '*',
  filters: "authors:\"Jason Fried\" OR authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\""
}, searchCallback);
Query* query = [Query new];
query.query = @"appl";
query.facets = @[ @"*" ];
query.filters = @"authors:\"Jason Fried\" OR authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\"";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
let query = Query()
query.query = "appl"
query.facets = ["*"]
query.filters = "authors:\"Jason Fried\" OR authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\""
index.search(query) { (content, error) in
    // [...]
}
Query query = new Query();
query.setQuery("appl");
query.setFacets("*");
query.setFilters("authors:\"Jason Fried\" OR authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\"")
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        
    }
});

Negations are also supported via the NOT operator, prefixing the facet value. For example to refine on “Business & Investing” book written by Jason Fried and not David Heinemeir Hanssan:

index.search('appl', {
  facets: '*',
  filters: "authors:\"Jason Fried\" AND NOT authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\"
}, searchCallback);
Query* query = [Query new];
query.query = @"appl";
query.facets = @[ @"*" ];
query.filters = @"authors:\"Jason Fried\" AND NOT authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\"";
[index search:query completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
var query = Query()
query.query = "appl"
query.facets = ["*"]
query.filters = "authors:\"Jason Fried\" AND NOT authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\""
index.search(query) { (content, error) in
    // [...]
}
Query query = new Query();
query.setQuery("appl");
query.setFacets("*");
query.setFilters("authors:\"Jason Fried\" AND NOT authors:\"David Heinemeier Hansson\" AND type:\"Business & Investing\"")
index.searchAsync(query, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        
    }
});

Disjunctive Faceting

Filtering faceting 2

Disjunctive Faceting

The most common use case for faceted search or navigation is to select at most one value per facet, but there are at least two ways from which a user might select multiple values from the same facet:

  • Conjunctive “AND” selection (standard Navigation, described above)

  • Disjunctive “OR” selection. Selecting hotel ratings (e.g., hotels with 4 OR 5 stars) may be a kind of disjunctive selection. Checkboxes are usually used to represent such navigation capabilities.

You may also check our instant-search tutorial through this link: Instant-Search Tutorial

We’ve implemented a JavaScript helper to help you generate such pages:

<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
<script src="https://cdn.jsdelivr.net/algoliasearch.helper/1/algoliasearch.helper.min.js"></script>
<script>
var client = algoliasearch('YourApplicationID', 'YourAPIKey');
var helper = algoliasearchHelper(
  client,
  // index name
  'hotels', {
    facets: ['facilities'],       // list of conjunctive facets
    disjunctiveFacets: ['stars'], // list of disjunctive facets
    hitsPerPage: 10
  }
);
helper.search('luxury', function searchDone(err, content) {
  if (err) {
    console.error(err);
    return;
  }
  for (var i = 0; i < content.hits.length; ++i) {
    // display the result set
    // [...]
  }
  for (var facet in content.facets) {
    // display link-based refinements
    // [...]
  }
  for (var facet in content.disjunctiveFacets) {
    // display checkbox-based refinements
    // [...]
  }
});
</script>
Query* query = [[Query alloc] initWithQuery:@"luxury"];
query.facets = @[@"facilities", @"stars"];
NSDictionary* refinements = @{
    @"facilities": @[@"wifi"],
    @"stars": @[@"4", @"5"]
};
[index searchDisjunctiveFaceting:query disjunctiveFacets:@[@"stars"] refinements:refinements completionHandler:^(NSDictionary* content, NSError* error) {
    // [...]
}];
let query = Query(query: "luxury")
query.facets = ["facilities", "stars"]
let refinements = [
    "facilities": ["wifi"],
    "stars": ["4", "5"]
]
index.searchDisjunctiveFaceting(query, disjunctiveFacets: ["stars"], refinements: refinements) { (content, error) in
    // [...]
}
Query query = new Query("luxury");
query.setFacets("facilities", "stars");
Map<String, List<String>> refinements = new HashMap<>();
refinements.put("facilities", Arrays.asList("wifi"));
refinements.put("stars", Arrays.asList("4", "5"));
index.searchDisjunctiveFacetingAsync(query, Arrays.asList("stars"), refinements, new CompletionHandler() {
    @Override
    public void requestCompleted(JSONObject content, AlgoliaException error) {
        // [...]
    }
});

The results of a disjunctive faceting search are the same as those of a regular search, except that they contain an additional disjunctiveFacets top-level attribute listing counts for disjunctive facets:

{
  "hits": [ /* ... */ ],
  "facets": {
    "facilities": {
      "wifi": 13,
      "swimming pool": 5
    }
  },
  "disjunctiveFacets": {
    "stars": {
       "1": 6,
       "2": 17,
       "3": 24,
       "4": 8,
       "5": 5
    }
  }
}

Disjunctive faceting results in querying several times the index:

  • a query is performed to display the result set ANDing refined conjunctive facets and ORing refined disjunctive facets.

  • a query used to display each disjunctive facet (with the associated number of hits that would be added to the result set if selected) ANDing the refined conjunctive facets.

For example, if a user is looking for an hotel matching the full-text query luxury with 4 OR 5 stars AND with a wifi facility, the following queries must be performed:

  • To display the result set and the conjunctive facet facilities: index.search("luxury", { facets: "facilities", filters: "facilities:wifi AND (stars:4 OR stars:5)" }).
  • To display the disjunctive facet stars: index.search("luxury", { facets: "stars", filters: "facilities:wifi" }).

Aggregations & Stats

All numerical-based facets returns the associated min, max & avg values. The values are available in the facets_stats attribute of the JSON answer. They are computed on all facets before the application of the maxValuesPerFacet parameter.

{
  "hits": [ ... ],
  "page": 0,
  "nbHits": 2,
  "nbPages": 1,
  "hitsPerPage": 20,
  "processingTimeMS": 1,
  "query": "appl",
  "params": "query=appl&facets=*",
  "facets": {
    "price": {
      "42": 4,
      "12": 3,
      "1": 1
    }
  },
  "exhaustiveFacetsCount": true,
  "facets_stats": {
    "price": {
      "min": 1,
      "max": 42,
      "avg": 25.625
    }
  }
}