PHPackages                             mgussekloo/laravel-facet-filter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. mgussekloo/laravel-facet-filter

ActiveLibrary[Search &amp; Filtering](/categories/search)

mgussekloo/laravel-facet-filter
===============================

Simple facet filtering in Laravel projects, hassle free.

58749↓50%6[1 issues](https://github.com/mgussekloo/laravel-facet-filter/issues)PHP

Since Feb 23Pushed 5mo ago1 watchersCompare

[ Source](https://github.com/mgussekloo/laravel-facet-filter)[ Packagist](https://packagist.org/packages/mgussekloo/laravel-facet-filter)[ RSS](/packages/mgussekloo-laravel-facet-filter/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (6)Used By (0)

[![Stars](https://camo.githubusercontent.com/b5601ed584f720a2c492081e022c3f12276f3bca48da1068341dcbab7a3a4000/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f6d67757373656b6c6f6f2f6c61726176656c2d66616365742d66696c746572)](https://camo.githubusercontent.com/b5601ed584f720a2c492081e022c3f12276f3bca48da1068341dcbab7a3a4000/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f6d67757373656b6c6f6f2f6c61726176656c2d66616365742d66696c746572)

Laravel Facet Filter
====================

[](#laravel-facet-filter)

This package provides simple facet filtering (sometimes called Faceted Search or Faceted Navigation) in Laravel projects. It helps narrow down query results based on the attributes of your models.

- Free, no dependencies
- Easy to use in any project
- Easy to customize
- There's a [demo project](https://github.com/mgussekloo/Facet-Demo) to get you started
- Ongoing support (last update: july 2025)

[![Demo](https://raw.githubusercontent.com/mgussekloo/laravel-facet-filter/master/demo.gif)](https://raw.githubusercontent.com/mgussekloo/laravel-facet-filter/master/demo.gif)

### Contributing

[](#contributing)

Please contribute to this package either by creating a pull request or reporting an issue.

### Installation

[](#installation)

This package can be installed through [Composer](https://packagist.org/packages/mgussekloo/laravel-facet-filter).

```
composer require mgussekloo/laravel-facet-filter
```

Get started
-----------

[](#get-started)

### Update your models

[](#update-your-models)

Add a Facettable trait and a facetDefinitions() method to models you'd like to filter:

```
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

use Mgussekloo\FacetFilter\Traits\Facettable;

class Product extends Model
{
	use HasFactory;
	use Facettable;

	public static function facetDefinitions()
	{
		// Return an array of definitions
		return [
			[
				'title' => 'Main color', // The title will be used for the parameter.
				'fieldname' => 'color' // Model property from which to get the values.
			],
			[
				'title' => 'Sizes',
				'fieldname' => 'sizes.name' // Use dot notation to get the value from related models.
			]
		];
	}
}
```

### Build an index

[](#build-an-index)

In most cases you'll want to create an index to filter large datasets efficiently. (Don't want to build an index? Skip ahead to [filtering collections](#filtering-collections).)

First, run the migrations:

```
php artisan vendor:publish --tag="facet-filter-migrations"
php artisan migrate
```

To build the index, create an Artisan command that queries your Facettable models. Run it periodically or whenever your data changes.

```
$products = Product::with(['sizes'])->get(); // get some products
$products->buildIndex(); // build the index
```

Get results
-----------

[](#get-results)

Within a controller, apply the facet filter to a query

```
$filter = request()->all(); // the filter looks like ['main-color' => ['green']]
$products = Product::facetFilter($filter)->get();
```

### Basic frontend example

[](#basic-frontend-example)

Here's a simple [demo project](https://github.com/mgussekloo/Facet-Demo) that demonstrates a basic frontend.

```

		@foreach ($products->getFacets() as $facet)

				{{ $facet->title }}

				@foreach ($facet->getOptions() as $option)
					{{ $option->value }} ({{ $option->total }})
				@endforeach

		@endforeach

		@foreach ($products as $product)

				{{ $product->name }} ({{ $product->sizes->pluck('name')->join(', ') }})
				{{ $product->color }}

		@endforeach

```

To see an example of a Livewire implementation, [see this gist](https://gist.github.com/mgussekloo/85f1901baceb8e0e244c4860c37dae1f).

Facet details
-------------

[](#facet-details)

```
$facets = $products->getFacets();

/* You can filter and sort like any regular Laravel collection. */
$singleFacet = $facets->firstWhere('fieldname', 'color');

/* Find out stuff about the facet. */
$paramName = $singleFacet->getParamName(); // "main-color"
$options = $singleFacet->getOptions();

/*
Options look like this:
(object)[
	'value' => 'Red',
	'selected' => false,
	'total' => 3,
	'slug' => 'color_red',
	'http_query' => 'main-color%5B1%5D=red&sizes%5B0%5D=small'
]
*/
```

Customization
-------------

[](#customization)

### Advanced indexing

[](#advanced-indexing)

Extend the [Indexer](src/Indexer.php) to customize behavior, e.g. to save a "range bracket" value instead of a "individual price" value to the index.

```
class MyCustomIndexer extends \Mgussekloo\FacetFilter\Indexer {
	public function buildValues($facet, $model) {
		$values = parent::buildValues($facet, $model);

		if ($facet->fieldname == 'price') {

			if ($model->price > 1000) {
				return 'Expensive';
			}
			if ($model->price > 500) {
				return '500 - 1000';
			}
			if ($model->price > 250) {
				return '250 - 500';
			}
			return '0 - 250';
		}

		return $values;
	}
}
```

You may overwrite the indexer() method on your Facettable model to return an instance of your custom indexer:

```
use App\MyCustomIndexer;

class Product extends Model
{
	use HasFactory;
	use Facettable;

	public static function indexer()
	{
		return new MyCustomIndexer();
	}
}
```

```
$products = Products::get();
$products->buildIndex(); // uses MyCustomIndexer
```

### Updating a larger index over time

[](#updating-a-larger-index-over-time)

```
$indexer = new Indexer();

$perPage = 1000; $currentPage = Cache::get('facetIndexingPage', 1);

$products = Product::with(['sizes'])->paginate($perPage, ['*'], 'page', $currentPage);

if ($currentPage == 1) {
	$indexer->resetIndex(); // clear entire index
}

$indexer->buildIndex($products);

if ($products->hasMorePages()) {}
	// next iteration, increase currentPage with one
}
```

### Custom facets

[](#custom-facets)

Provide custom attributes and an optional custom [Facet class](src/Models/Facet.php) in the facet definitions.

```
public static function facetDefinitions()
{
	return [
		[
			'title' => 'Main color',
			'description' => 'The main color.', // optional custom attribute, you could use $facet->description when creating the frontend...
			'related_id' => 23, // ... or use $facet->related_id with your custom indexer
			'fieldname' => 'color',
			'facet_class' => CustomFacet::class // optional Facet class with custom logic
		]
	];
}
```

### Filtering collections

[](#filtering-collections)

It's possible to apply facet filtering to a collection without building an index. Facettable models return a [FacettableCollection](src/Collections/FacettableCollection.php), which provides an indexlessFacetFilter() method.

```
$products = Product::all(); // returns a "FacettableCollection"
$products = $products->indexlessFacetFilter($filter);
```

### Pagination

[](#pagination)

Example:

```
$products = Product::facetFilter($filter)->paginate(10);
$pagination = $products->appends(request()->input())->links();
```

Notes on caching
----------------

[](#notes-on-caching)

By default Facet Filter caches some heavy operations through the non-persistent 'array' cache driver. Caches are based on the model and filter, not the query bindings or specifics. Use a cacheTag to distinguish between queries.

```
// if you have two facet filter queries on the same model...
Product::where('published', false)->cacheTag('unpublished')->facetFilter($filter)->get();
// ...make sure to distinguish between queries
Product::where('published', true)->cacheTag('published')->facetFilter($filter)->get();
```

The default Indexer clears the cache for all models when rebuilding the index. You can do it manually:

```
Product::forgetCache(); // clears cache for all cache tags
Product::forgetCache('unpublished'); // clears cache for only this cachetag

use Mgussekloo\FacetFilter\Facades\FacetCache;
FacetCache::forgetCache(); // clears cache for all models and cache tags
```

Config
------

[](#config)

You can configure a peristent cache driver through `config/facet-filter.php`. Be aware of any caching related issues, e.g. if you have any user specific results this may be problematic.

```
php artisan vendor:publish --tag=facet-filter-config
```

```
'classes' => [
	'facet' => Mgussekloo\FacetFilter\Models\Facet::class,
	'facetrow' => Mgussekloo\FacetFilter\Models\FacetRow::class,
],

'table_names' => [
	'facetrows' => 'facetrows',
],

'cache' => [
	'expiration_time' => \DateInterval::createFromDateString('24 hours'),
	'key' => 'mgussekloo.facetfilter.cache',
	'store' => 'array',
],
```

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance48

Moderate activity, may be stable

Popularity29

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity29

Early-stage or recently created project

 Bus Factor1

Top contributor holds 99.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/1426964?v=4)[Martijn Gussekloo](/maintainers/mgussekloo)[@mgussekloo](https://github.com/mgussekloo)

---

Top Contributors

[![mgussekloo](https://avatars.githubusercontent.com/u/1426964?v=4)](https://github.com/mgussekloo "mgussekloo (247 commits)")[![marcobax](https://avatars.githubusercontent.com/u/2600040?v=4)](https://github.com/marcobax "marcobax (2 commits)")

### Embed Badge

![Health badge](/badges/mgussekloo-laravel-facet-filter/health.svg)

```
[![Health](https://phpackages.com/badges/mgussekloo-laravel-facet-filter/health.svg)](https://phpackages.com/packages/mgussekloo-laravel-facet-filter)
```

###  Alternatives

[ruflin/elastica

Elasticsearch Client

2.3k50.4M203](/packages/ruflin-elastica)[opensearch-project/opensearch-php

PHP Client for OpenSearch

15024.3M65](/packages/opensearch-project-opensearch-php)[mailerlite/laravel-elasticsearch

An easy way to use the official PHP ElasticSearch client in your Laravel applications.

934529.3k2](/packages/mailerlite-laravel-elasticsearch)[massive/search-bundle

Massive Search Bundle

721.4M13](/packages/massive-search-bundle)[outl1ne/nova-multiselect-filter

Multiselect filter for Laravel Nova.

45802.7k3](/packages/outl1ne-nova-multiselect-filter)[handcraftedinthealps/zendsearch

a general purpose text search engine written entirely in PHP 5

39921.0k35](/packages/handcraftedinthealps-zendsearch)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
