Vue Storefront 2: Extending Product Filters Without Breaking Module Upgrades
A practical look at adding custom product filters in Vue Storefront 2 using module overrides and slot patterns, with verification steps for Nuxt 3 compatibility.
10 Nov 2026, 07:11 UTC

The Problem: Custom Filters That Break on Upgrade
Vue Storefront 2 ships with a module system that lets you swap components through slots and overrides. The friction appears when you need a product filter that the core modules do not expose. If you patch core files, the next module upgrade overwrites your work. If you fork a module, you inherit its bugs and miss security patches. The useful question is not how to add a filter, but how to add one that survives the next npm update.
The Thesis: Extend Through Registration, Not Replacement
Vue Storefront 2 is built on Nuxt 3 and the Vue 3 Composition API. Its commerce modules—product listing, search, checkout—expose extension points rather than closed implementations. The stable pattern is to register a custom component or composable through the module API and render it inside an existing slot. That keeps your code in your theme or a custom module, leaving the vendor packages untouched.
Understanding the Filter Pipeline
In a typical storefront, the product listing page delegates to a container component that reads filter state from a composable such as useProductList and passes it to the commerce API. The sidebar renders filter widgets dynamically. Your custom filter needs to do three things: read the current filter state, write new selections back into that state, and trigger a data refetch. The exact composable names and state keys vary by backend integration, so treat the following as a pattern to verify against your installed version rather than a guaranteed API.
A Worked Example: Adding a Color Filter
Assume you have a Magento or Shopify backend that already exposes a color attribute. The goal is a checkbox group in the sidebar that updates the URL and the product grid.
First, create a module directory in your theme. A minimal module might look like this:
// modules/custom-color-filter/index.js
export default function registerCustomFilter(context) {
// Register a component so the module system can resolve it
context.registerComponent('CustomColorFilter', () => import('../components/CustomColorFilter.vue'))
}
Then create the component. It reads and writes filter state through the same composable the core sidebar uses:
<template>
<div class="filter-group">
<h4>Color</h4>
<label v-for="color in colors" :key="color">
<input type="checkbox" :value="color" v-model="selected" @change="apply">
{{ color }}
</label>
</div>
</template>
<script setup>
import { ref } from 'vue'
// Verify the exact import path against your installed version
import { useProductList } from '@vue-storefront/core'
const { filters, updateFilters } = useProductList()
const colors = ['Red', 'Blue', 'Green']
const selected = ref(filters.value.color || [])
function apply() {
updateFilters({ color: selected.value })
}
</script>
Finally, mount the component inside the existing filter sidebar slot in your theme layout:
<FilterSidebar>
<CustomColorFilter />
</FilterSidebar>
Because the component is registered through the module system and rendered in a slot, the core sidebar remains a vendor file. Upgrading the commerce module replaces the sidebar internals but preserves your slot content.
Trade-offs and Limitations
Backend dependency: The filter is only as useful as the API behind it. If your commerce backend does not expose color as a filterable attribute in its GraphQL or REST schema, the frontend control will return empty results. Confirm the attribute exists and is indexed before writing the component.
Version drift: Vue Storefront 2 modules and Nuxt 3 evolve independently. The composable signature or slot name in your installed version may differ from the pattern above. Pin your dependency versions and check the module compatibility matrix in the repository before upgrading.
URL synchronization: Shareable filtered links depend on the core URL sync logic. If your custom filter writes state directly to a local ref without going through the framework's router integration, the address bar will not update and refresh will lose the selection.
How to Check the Result
- Load the product listing page and confirm the sidebar renders your component without console errors.
- Select a color and verify the product grid refetches.
- Check that the URL includes the filter parameter and that copying the URL into a new tab restores the selection.
- Run
npm ls @vue-storefront/corein your project root to confirm the installed version matches your module's peer dependency range.
Actionable Next Steps
Start with a single-select attribute before attempting multi-select facets. Keep the custom filter in its own module directory so it can be removed cleanly if the backend changes. Most importantly, verify the exact composable and slot names against the source of the module version you have installed—documentation for advanced customization scenarios is still catching up to the code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.