Facets
Facets let a reader narrow the results themselves, inside the boundary you set.
| Set by | Job | |
|---|---|---|
fq |
You | Remove content the reader must never see, across every request. |
| Facets | The reader | Narrow what is left. |
The two never fight. fq decides the pool, facets pick from that pool, and a
reader cannot widen past your filter. See
Filter results (fq).
The SDK owns the facet state. It reads the counts, renders the groups, tracks what is selected, sends the filter, and refreshes the results. Your part is to name the fields and style the block. There is no filter logic to write.
Facets belong to Hybrid Search and Search. An answer has no result list to narrow.
Turn them on
Two steps: name the fields, and put the element on the page.
const workflow = client.ui.hybridSearch;
workflow.useApi({
facets: ['tags', 'categories'],
});
Miso counts only the fields you name. A custom attribute uses its dotted path,
such as custom_attributes.section.
await client.ui.ready;
document.querySelector('#my-root').innerHTML =
MisoClient.ui.defaults.hybridSearch.templates.root();
The default markup already holds <miso-facets></miso-facets>. To place the
block yourself, put that element where you want it.
What a click sends
A reader selecting a value does not change q or fq. The SDK adds a separate
field, facet_filters, and runs the search again:
{
"q": "linux",
"facets": ["tags"],
"facet_filters": { "tags": { "terms": ["Computing"] } }
}
Two consequences worth knowing.
Your fq still applies. A reader narrows inside the rules you set, and
cannot widen past them. See Filter results (fq).
One value per field. Picking a second value in the same group replaces the
first: a click on Computing sends ["Computing"], and a click on FLOSS
next sends ["FLOSS"], not both. Selecting values in different fields does
combine.
Rename the labels
Field names read like a schema. Replace them with words your readers know:
const LABELS = {
tags: 'Topic',
categories: 'Section',
'custom_attributes.section': 'Desk',
};
workflow.useLayouts({
facets: {
templates: {
title(layout, { field }) {
const { escapeHtml } = layout.templates.helpers;
return escapeHtml(LABELS[field] || field);
},
value(layout, { value }) {
return layout.templates.helpers.escapeHtml(value);
},
},
},
});
The heading now reads "Topic" rather than "tags". Use value the same way to
tidy the values themselves, such as turning science-and-tech into
"Science and technology".
Miso strips a custom_attributes. prefix before it draws the default heading,
so custom_attributes.section already shows as section. Your LABELS lookup
sees the full path, so key it either way:
title(layout, { field }) {
const name = field.replace('custom_attributes.', '');
return layout.templates.helpers.escapeHtml(LABELS[name] || name);
}
The templates
Each one replaces a smaller part of the block. Change the least you can.
| Template | Draws | Arguments |
|---|---|---|
title |
One group heading. | (layout, { field }) |
value |
One option label. | (layout, { field, value, count }) |
count |
The number beside a value. | (layout, { count }) |
option |
One whole row. | (layout, entry, state) |
options |
The list of rows in a group. | (layout, facet, state) |
header |
The heading row of a group. | (layout, facet, state) |
facet |
One whole group. | (layout, facet, state) |
facets |
Every group. | (layout, state) |
Keep
data-role="option"if you rewriteoption. The SDK listens for clicks on that attribute. A row without it renders correctly and stops filtering, with no error.
Hide the counts, for example, by returning nothing from count:
workflow.useLayouts({
facets: { templates: { count: () => '' } },
});
Hide some values
To drop values a reader has no use for, override options, filter the entries,
and hand the rest to the original template:
workflow.useLayouts({
facets: {
templates: {
options(layout, data, state) {
const skip = ['Uncategorised'];
const entries = data.entries.filter(([value]) => !skip.includes(value));
const { options: defaultOptions } = layout.constructor.defaultTemplates;
return defaultOptions(layout, { ...data, entries }, state);
},
},
},
});
data.entries is an array of [value, count] pairs, so entries[0] is the
value and entries[1] is the count. Filter, sort or slice it, then let Miso
draw the rows.
Reach the original through
layout.constructor.defaultTemplates. Inside an override,layout.templates.optionsis your own function, so calling it repeats forever.defaultTemplatesholds the version you replaced. This is the way to add to a template rather than rewrite it.
The counts stay as Miso calculated them. Hiding a row removes the control, not
the results behind it. To change what the query matches, filter with fq.
Order and empty groups
Values arrive most common first, and Miso returns the top 10 values per field. A field with no matching values is left out of the response, so the group does not render.
To show a different number of values, or to filter which ones appear, set the
facet as an object on the endpoint. See
Hybrid Search API for size,
alias, include, exclude and ranges.
Can I use checkboxes, or a dropdown?
Not today. Facets render as clickable rows, and each field holds one value.
You can make the rows look like checkboxes with the option template, but
do not. A checkbox tells a reader to tick several values in a group, and the
second tick clears the first. The control then promises what the behaviour
does not do.
The SDK has a checkbox layout and a select layout, and neither one fits
here. The checkbox layout is a single on and off switch, used for the
Answer Updates subscribe button. The select layout is the single-choice
dropdown used for sorting.
If you need several values at once, filter with fq while you wait, and
ask Miso for multi-select facets. See
Filter results (fq).
Do not drive facets from your own code
Facet state belongs to the SDK, and there is no public call to select a value for the reader. That is the design, not a gap: one owner of the selection means the chips, the counts, the request and the results cannot disagree.
When your code needs to control what is eligible, that is a different job, and
fq does it:
workflow.useApi({ fq: 'custom_attributes.premium:"false"' });
Set fq and the facet counts follow it automatically.
Next
- Filter results (fq) — the filters your product sets.
-
Hybrid Search API —
facets,order_byand the response shape. - Customize the SDK — change what each result looks like.
