PHPackages                             hans2103/magento2-optionfilter - 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. hans2103/magento2-optionfilter

ActiveMagento2-module[Search &amp; Filtering](/categories/search)

hans2103/magento2-optionfilter
==============================

Magento 2 module that filters configurable products from catalog views based on stock status of their variants

26.09.2(4mo ago)512↓86.7%2MITPHPPHP ^8.1|^8.2|^8.3

Since Feb 11Pushed 4mo agoCompare

[ Source](https://github.com/hans2103/hans2103_magento2_optionfilter)[ Packagist](https://packagist.org/packages/hans2103/magento2-optionfilter)[ RSS](/packages/hans2103-magento2-optionfilter/feed)WikiDiscussions main Synced today

READMEChangelog (3)Dependencies (5)Versions (4)Used By (0)

Hans2103\_OptionFilter
======================

[](#hans2103_optionfilter)

Overview
--------

[](#overview)

This module filters configurable products from catalog views based on the stock status of their variants:

- **Always hides** configurable products when all variants are out of stock
- **When filters are active:** Only shows configurables with in-stock variants matching the filter criteria

Behavior
--------

[](#behavior)

### No Filters Active

[](#no-filters-active)

When no attribute filters are applied, configurable products are shown only if **at least one** variant is in stock. Products with all variants out of stock are hidden from the catalog.

**Example:**

- Product has variants: Size M (out of stock), Size L (out of stock), Size XL (out of stock)
- No filters applied → Product hidden (all variants out of stock)
- Product has variants: Size M (in stock), Size L (out of stock)
- No filters applied → Product shows (at least one variant in stock)

### Single Attribute Filter

[](#single-attribute-filter)

When filtering by a single attribute (e.g., Size: M), a configurable product is shown only if **at least one** variant matching that filter is in stock.

**Example:**

- Product has variants: Size M (in stock), Size L (out of stock)
- Filter: Size = M → Product shows
- Filter: Size = L → Product hidden

### Multiple Attribute Filters

[](#multiple-attribute-filters)

When filtering by multiple attributes (e.g., Size: M AND Color: Red), a configurable product is shown only if **the exact combination** is in stock.

**Example:**

- Product has variants: Size M Red (in stock), Size M Blue (out of stock)
- Filter: Size = M → Product shows (at least one Size M is in stock)
- Filter: Size = M AND Color = Blue → Product hidden (Size M Blue is out of stock)
- Filter: Size = M AND Color = Red → Product shows (Size M Red is in stock)

### Non-Configurable Products

[](#non-configurable-products)

Simple, grouped, and bundle products are not affected by this filter and always show based on their own stock status.

Technical Implementation
------------------------

[](#technical-implementation)

### Architecture

[](#architecture)

The module uses a `beforeLoad` plugin on `Magento\Catalog\Model\ResourceModel\Product\Collection` to modify the SQL query after all filters have been applied but before the collection executes.

### Key Components

[](#key-components)

1. **Plugin:** `Hans2103\OptionFilter\Plugin\Product\Collection\FilterConfigurableByStock`

    - Intercepts product collection before load
    - Detects layered navigation context
    - Builds and applies SQL WHERE clause
2. **Helper:** `Hans2103\OptionFilter\Helper\AttributeFilter`

    - Parses active attribute filters from Layer state
    - Validates configurable attributes
    - Provides store context

### SQL Strategy

[](#sql-strategy)

The plugin adds a WHERE clause that:

- Always includes non-configurable products
- For configurable products, requires at least one in-stock variant matching ALL active filters

**Query pattern:**

```
WHERE e.type_id != 'configurable' OR EXISTS (
    SELECT 1
    FROM catalog_product_super_link cpsl
    INNER JOIN cataloginventory_stock_item si
        ON cpsl.product_id = si.product_id AND si.is_in_stock = 1
    WHERE cpsl.parent_id = e.entity_id
        AND EXISTS (SELECT 1 FROM catalog_product_entity_int ...)
        AND EXISTS (SELECT 1 FROM catalog_product_entity_int ...)
)
```

**Note:** The module uses `catalog_product_entity_int` (raw EAV table) instead of `catalog_product_index_eav` because Magento does not index configurable product child variants in the EAV index table.

### Performance Considerations

[](#performance-considerations)

- Uses standard Magento tables (`catalog_product_entity_int`, `catalog_product_super_link`, `cataloginventory_stock_item`)
- Applies filter only in layered navigation contexts
- Single query modification (no additional queries)
- Minimal overhead (&lt; 100ms expected)
- No reindexing required - works with real-time EAV data

Compatibility
-------------

[](#compatibility)

- **Magento Version:** 2.4.6-p13
- **Theme:** Compatible with Hyvä theme
- **Dependencies:**
    - Magento\_Catalog
    - Magento\_CatalogInventory
    - Magento\_ConfigurableProduct
    - Magento\_LayeredNavigation

Installation
------------

[](#installation)

1. Place module in `src/app/code/Hans2103/OptionFilter/`
2. Enable module: `bin/magento module:enable Hans2103_OptionFilter`
3. Run deployment: `make deploy`
4. Clear cache: `bin/magento cache:flush`

Uninstallation
--------------

[](#uninstallation)

1. Disable module: `bin/magento module:disable Hans2103_OptionFilter`
2. Run deployment: `make deploy`
3. Remove module directory

Testing
-------

[](#testing)

### Manual Test Scenarios

[](#manual-test-scenarios)

1. **No filters - all variants out of stock**

    - Create configurable with Size M (out of stock), Size L (out of stock), Size XL (out of stock)
    - Navigate to category without filters
    - Expected: Product disappears
2. **No filters - at least one variant in stock**

    - Create configurable with Size M (in stock), Size L (out of stock)
    - Navigate to category without filters
    - Expected: Product shows
3. **Single filter with out-of-stock variant**

    - Product has Size M (in stock), Size L (out of stock)
    - Apply filter: Size = L
    - Expected: Product disappears
4. **Single filter with in-stock variant**

    - Apply filter: Size = M
    - Expected: Product shows
5. **Multiple filters with exact combination out of stock**

    - Create configurable: Size M Red (in stock), Size M Blue (out of stock)
    - Apply filters: Size = M AND Color = Blue
    - Expected: Product disappears
6. **Multiple filters with exact combination in stock**

    - Apply filters: Size = M AND Color = Red
    - Expected: Product shows
7. **Non-configurable products**

    - Apply any attribute filter or no filter
    - Expected: Simple products show based on own stock status

Troubleshooting
---------------

[](#troubleshooting)

### "Catalog Layer has been already created" Exception

[](#catalog-layer-has-been-already-created-exception)

**Symptom:** Exception thrown when viewing category pages with filters applied.

**Cause:** Multiple calls to `Magento\Catalog\Model\Layer\Resolver::get()` causing duplicate layer initialization.

**Solution:** This was fixed in the current version by consolidating layer retrieval to a single `getLayer()` method. If you encounter this error:

1. Ensure you're using the latest version of the module
2. Clear generated code: `rm -rf generated/code`
3. Flush cache: `bin/magento cache:flush`

**Prevention:** Never call `layerResolver->get()` multiple times in the same request flow. Always cache the result if needed in multiple places.

### Products not filtering as expected

[](#products-not-filtering-as-expected)

1. Check if filters are attribute-based (not category or price)
2. Verify attributes are used in configurable products
3. Check stock status in `cataloginventory_stock_item` table
4. Review logs: `var/log/system.log`

### All products disappear when filtering

[](#all-products-disappear-when-filtering)

If all products disappear when applying a filter, verify that configurable product child variants have attribute values in the `catalog_product_entity_int` table:

```
SELECT
    cpsl.product_id,
    child.sku,
    eav_int.value as attribute_option_id
FROM catalog_product_super_link cpsl
INNER JOIN catalog_product_entity child ON cpsl.product_id = child.entity_id
LEFT JOIN catalog_product_entity_int eav_int
    ON child.entity_id = eav_int.entity_id
    AND eav_int.attribute_id = [YOUR_ATTRIBUTE_ID]
    AND eav_int.store_id IN (0, 1)
WHERE cpsl.parent_id = [PARENT_PRODUCT_ID];
```

If this query returns no results, the EAV data may be corrupted or missing. Re-save the configurable product in the admin panel.

### Performance issues

[](#performance-issues)

1. Check MySQL slow query log
2. Verify indexes exist on:
    - `catalog_product_entity_int` (entity\_id, attribute\_id, value, store\_id)
    - `catalog_product_super_link` (parent\_id, product\_id)
    - `cataloginventory_stock_item` (product\_id, is\_in\_stock)
3. Monitor query execution time

### Debug mode

[](#debug-mode)

Enable debug logging by checking `var/log/system.log` for entries containing "Error applying OptionFilter".

Implementation Notes
--------------------

[](#implementation-notes)

### Why `catalog_product_entity_int` instead of `catalog_product_index_eav`?

[](#why-catalog_product_entity_int-instead-of-catalog_product_index_eav)

**Issue:** Magento's EAV indexer (`_prepareSelectIndex`) filters out products with `visibility = 1` (Not Visible Individually). Configurable product children always have `visibility=1`, so they are **never indexed** in `catalog_product_index_eav`. The `_prepareRelationIndex` step that propagates child values to the parent also only works from data already in the index — so configurable parents never get their super attribute values (e.g. `size`) in `catalog_product_index_eav` either.

This affects both `FilterConfigurableByStock` and `AdjustAttributeCount`. OpenSearch indexes configurables with their children's attribute values correctly, but the SQL-based `catalog_product_index_eav` does not.

**Verification:** You can verify this by running:

```
SELECT COUNT(*) as count
FROM catalog_product_super_link cpsl
INNER JOIN catalog_product_index_eav eav ON cpsl.product_id = eav.entity_id
WHERE eav.attribute_id = [YOUR_ATTRIBUTE_ID];
```

This will return `0`, confirming that child products are not in the EAV index.

**Solution:** The module uses `catalog_product_entity_int` (the raw EAV attribute table) instead, which contains all attribute values including those for configurable product child variants. This approach:

- Works with real-time data (no reindexing required)
- Provides accurate filtering based on actual variant attributes
- Has minimal performance impact due to proper join structure

### Filter Type Compatibility

[](#filter-type-compatibility)

The module supports both:

- `Magento\Catalog\Model\Layer\Filter\Attribute` (standard catalog filtering)
- `Magento\CatalogSearch\Model\Layer\Filter\Attribute` (search/layered navigation)

This ensures compatibility with both standard category pages and search results pages, as well as Hyvä theme implementations.

License
-------

[](#license)

Copyright © Hans2103. All rights reserved.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance76

Regular maintenance activity

Popularity12

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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.

###  Release Activity

Cadence

Every ~6 days

Total

3

Last Release

129d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9fb8f6a6130edfc4707d8231d80f09497ceed3d16473554a4e787835aeca5bf5?d=identicon)[hans2103](/maintainers/hans2103)

---

Top Contributors

[![hans2103](https://avatars.githubusercontent.com/u/639822?v=4)](https://github.com/hans2103 "hans2103 (4 commits)")

---

Tags

magentofilterconfigurableinventorymagento2stocklayered-navigation

### Embed Badge

![Health badge](/badges/hans2103-magento2-optionfilter/health.svg)

```
[![Health](https://phpackages.com/badges/hans2103-magento2-optionfilter/health.svg)](https://phpackages.com/packages/hans2103-magento2-optionfilter)
```

###  Alternatives

[smile/elasticsuite

Magento 2 merchandising and search engine built on ElasticSearch

8064.7M49](/packages/smile-elasticsuite)[buckaroo/magento2

Buckaroo Magento 2 extension

32420.3k8](/packages/buckaroo-magento2)[mollie/magento2

Mollie Payment Module for Magento 2

1131.9M16](/packages/mollie-magento2)[smile/module-elasticsuite-cms-search

Smile Elasticsuite - Cms Pages Search Module for Smile Elasticsuite.

25966.8k1](/packages/smile-module-elasticsuite-cms-search)[run-as-root/magento2-prometheus-exporter

Magento2 Prometheus Exporter

68357.9k](/packages/run-as-root-magento2-prometheus-exporter)[opengento/module-category-import-export

This module add the capability to import and export the categories from the back-office.

1310.9k2](/packages/opengento-module-category-import-export)

PHPackages © 2026

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