I've done this a couple of times in Twig and a couple of times with the Faceted Nav plugin and would recommend doing it yourself in Twig, as it gives you full control. More specifically, Faceted Nav plugin cannot achieve what most people want which is to separate the filter groups with 'AND' logic while separating the individual filters in each group with 'OR' logic.
The secret to achieving this in Twig is leveraging Craft's relatedTo parameter, which is incredibly powerful and can accept multiple sets of criteria. Here's a simplified example:
{% set facet1 = craft.entries.slug(['slug-1','slug-2']).ids() %}
{% set facet2 = craft.entries.slug(['slug-3','slug-4']).ids() %}
{% set facet3 = craft.categories.slug(['slug-5','slug-6']).ids() %}
{% set params = ['and',
{ targetElement: facet1, field: 'facetFieldHandle_1' },
{ targetElement: facet2, field: 'facetFieldHandle_2' },
{ targetElement: facet3, field: 'facetFieldHandle_3' }
] %}
{% set entries = craft.entries.section('products').relatedTo(params).all() %}
<ul>
{% for entry in entries %}
<li>{{ entry.title }}</li>
{% endfor %}
</ul>
...So you can see there that my facet1
, facet2
, facet3
arrays are basically acting like 'OR' filters ("fetch me the entries with this slug OR this slug OR this slug"), then we feed those arrays into the relatedTo parameter prefixed with the 'AND' keyword. (Naturally, those hardcoded slug-1
, slug-2
values will need to come from your form data - usually best to take them via query string params so that the result pages are URL-addressable e.g. craft.app.request.getParam('facet1')
).
Once you start adding logic to pre-check the right checkboxes and adding eager loading etc, it gets more complicated, but that's the basics. The last time I did this I added some simple JS to enhance/prettify the URL query strings (by default a form submission will append multiple values repeatedly like this ?facet1[]=abc&facet1[]=def&facet2[]=123
-- which gets quite ugly and long). With a little JS you can transform into something more like ?facet1=abc|def&facet2=123
, and use Twig's is iterable
test to check whether you're dealing with the enhanced version or the default/no-js/slow-js/googlebot version and split('|')
the data accordingly.