pollora/meilifacets
MeiliFacets adds faceted search to Laravel with Meilisearch. Define facets, apply filters, and return facet counts for intuitive, fast filtering in your applications.
pollora/meilifacets sits on top of a Pollora/WordPress app: it renders faceted Meilisearch listings through Blade components and keeps the sort/filter state in sync with a small browser script driven by data-meili attributes.
Three steps get you to a working listing:
composer require pollora/meilifacets
php artisan module:publish MeiliFacets # copies views + browser assets into your theme
wp meiliscout index # build/refresh the index
Look first at:
docs/upgrading.md — the 0.2.3 upgrade has breaking renames and template changes; read it before touching views.config/meilifacets.php — drawer.row.overflow, drawer.row.limit, sort/facet defaults.results, drawer, facets in your theme — these are yours now; upstream changes do not reach them.First use case: wrap a WooCommerce product archive in a listing root, add a sort control and one facet, and confirm the browser script hydrates it:
<x-meilifacets::listing :index="'products'">
<x-meilifacets::listing.facets />
<x-meilifacets::listing.sort label="Sort by" />
<x-meilifacets::listing.results />
</x-meilifacets::listing>
If results render server-side but stop reacting to filter clicks, the browser client did not start — check the console before touching PHP.
Listing roots and naming. A component inside a named root now belongs to that listing without repeating name. Use named roots when you render two listings (e.g. a carousel plus the archive) on one page, and reserve the implicit form for the single-listing case.
Default sort. Order follows Appearance › Customize › WooCommerce › Product Catalog › Default product sorting while no sort is chosen and no term is searched — enforced on both server and browser, including under a variation filter. Override per listing by implementing Contracts\DefaultSortedListing:
class ProductListing implements DefaultSortedListing
{
public function defaultSort(): string { return 'price:asc'; }
}
Popularity and average rating are not valid defaults yet (see Gotchas); keep custom defaults to price/date/title-style attributes you have marked sortable at index time.
Row overflow: side sheet vs. « Show more ». side-sheet remains the default; opt into the newer behaviour per listing or globally:
<x-meilifacets::listing row-overflow="show-more" />
// config/meilifacets.php
'drawer' => ['row' => ['overflow' => 'show-more', 'limit' => 4]],
« Show more » ends the desktop row and opens the same side-sheet drawer with the overflow filters — one implementation, two entry points. Add listing.facets unwrapped when each filter should wrap independently rather than as a single unit.
Card components per listing. Implement Contracts\CardComponentListing to have the listing render results with a project Blade component — used for both the server-rendered markup and the <template> the browser clones, so the two can never drift:
<x-meilifacets::listing.card :binding="CardBinding::of($card)" />
For cards rendered outside a listing (carousels, cross-sell blocks), use CardBinding::standalone($card). Without it the browser console reports the card as misplaced, because the binding tries to attach listing hooks that do not exist in that context.
Reindex as part of deployment. Anything touching sortable/filterable attributes, index settings, or product order (including drag-and-drop reordering, which now reindexes) needs wp meiliscout index. Treat it as a deploy step alongside module:publish and cache clearing, not an afterthought.
Theming hooks. data-apply now lives on the listing root, so write theme rules as .meilifacetsFacets[data-apply] scoped from the root rather than from the facet element. Keep custom CSS downstream of published views so a re-publish does not silently orphan your selectors.
0.2.3 is a breaking release despite the patch number. Before deploying:
meilifacets.drawer.row_limit → meilifacets.drawer.row.limit; the old key is read by nothing and fails silently (row limit falls back to the default).results, drawer, facets if your theme copied them — and re-apply your customisations afterwards.:card with :binding="CardBinding::of($card)" on <x-meilifacets::listing.card>; listing.card-template is removed with no shim..meilifacetsFacets[data-apply] theme rules to the listing root.Then run wp meiliscout index, php artisan module:publish MeiliFacets, and clear the page cache — skipping any one of these produces stale assets against fresh markup.
Beta surface. The module's PHP classes/interfaces and the data-meili attributes are explicitly unstable until 1.0. Pin the version, and avoid scattering data-meili selectors through application JS; centralise them so a minor bump does not become a hunt.
Reindex storms. Saving a product re-indexes once per WooCommerce meta write, and each pass resends index settings. Bulk edits and imports can leave the engine's queue far behind — schedule imports off-peak, or expect stale results for a while. The real fix belongs in MeiliScout.
Silent browser failures. A failed search shows no message and the URL keeps the filter that was not applied, so the UI and the query string disagree. Watch the console and the URL during QA rather than trusting the visible result count. Related: past the row limit on a wide screen, « Filters »/« Show more » need the browser client — if it does not start, they open nothing at all.
Known functional gaps to design around:
/pa_volume/400ml/) do not show the browsed term's variant; leave « Enable archives? » unchecked.Theme breakpoints. If you move the drawer's media past 48em, you must also redeclare the side-sheet block — they share the breakpoint contract, and only moving one leaves the drawer unstyled above the new threshold.
Debugging order of operations: verify the index (wp meiliscout index output), then the rendered server HTML, then whether the browser client started, then the data-meili attributes in the DOM. Most "facets don't work" reports are the third step, and most "wrong results" reports are a stale index after an attribute change that was never reindexed.
How can I help you explore Laravel packages today?