filtering-system
Product Filtering System
Source: Saleor API - ProductFilterInput - Available server-side filter options
When to Use
Use this skill when:
- Modifying product list filtering or sorting
- Adding new filter types
- Working on category, collection, or products pages
- Understanding server-side vs client-side filtering
Instructions
Filter Architecture
| Filter | Processing | Why |
|---|---|---|
| Categories | ✅ Server-side | Uses Saleor's ProductFilterInput.categories |
| Price | ✅ Server-side | Uses Saleor's ProductFilterInput.price |
| Sort | ✅ Server-side | Uses Saleor's ProductOrder |
| Colors | ❌ Client-side | Saleor needs attribute IDs |
| Sizes | ❌ Client-side | Same as colors |
Key Files
| File | Purpose |
|---|---|
src/ui/components/plp/filter-utils.ts |
All filter utilities (server + client) |
src/ui/components/plp/FilterBar.tsx |
Filter UI (dropdowns, mobile sheet) |
src/ui/components/plp/useProductFilters.ts |
Hook consolidating filter logic |
Server-Side Filtering
Category slugs in URL are resolved to IDs:
// In page.tsx (server component)
import { resolveCategorySlugsToIds, buildFilterVariables } from "@/ui/components/plp/filter-utils";
const categorySlugs = searchParams.categories?.split(",") || [];
const categoryMap = await resolveCategorySlugsToIds(categorySlugs);
const categoryIds = Array.from(categoryMap.values()).map((c) => c.id);
const filter = buildFilterVariables({
priceRange: searchParams.price,
categoryIds,
});
// Pass to GraphQL query
const { products } = await executePublicGraphQL(ProductListDocument, {
variables: { channel, filter },
});
Client-Side Filtering
Colors and sizes are filtered after fetch:
import { filterProducts, extractColorOptions } from "@/ui/components/plp/filter-utils";
// Extract available options
const colorOptions = extractColorOptions(products, selectedColors);
// Apply filters
const filtered = filterProducts(products, {
colors: selectedColors,
sizes: selectedSizes,
});
Using the Hook
The useProductFilters hook consolidates all filter logic:
"use client";
import { useProductFilters } from "@/ui/components/plp/useProductFilters";
function ProductsClient({ products, resolvedCategories }) {
const {
filteredProducts,
colorOptions,
sizeOptions,
selectedColors,
handleColorToggle,
handleSortChange,
activeFilters,
} = useProductFilters({
products,
resolvedCategories,
enableCategoryFilter: true,
});
return (
<FilterBar
colorOptions={colorOptions}
selectedColors={selectedColors}
onColorToggle={handleColorToggle}
// ...
/>
);
}
Static Price Ranges
Price ranges are static to avoid UI flicker when filtering:
import { STATIC_PRICE_RANGES_WITH_COUNT } from "@/ui/components/plp/filter-utils";
// Returns: [
// { label: "Under $50", value: "0-50", count: 0 },
// { label: "$50 - $100", value: "50-100", count: 0 },
// ...
// ]
Examples
Adding a New Server-Side Filter
- Update
buildFilterVariablesinfilter-utils.ts:
export function buildFilterVariables(params: {
priceRange?: string | null;
categoryIds?: string[];
inStock?: boolean; // New filter
}): ProductFilterInput | undefined {
// ... existing code ...
if (params.inStock) {
filter.stockAvailability = "IN_STOCK";
hasFilter = true;
}
}
- Parse from URL in page.tsx and pass to the function.
Anti-patterns
❌ Don't filter categories client-side - Use server-side with IDs
❌ Don't generate dynamic price ranges - Use static ranges
❌ Don't hide selected filters - Always show so users can deselect
More from saleor/storefront
react-patterns
Write idiomatic React with proper hooks and render purity. Use when writing components, fixing hook-related lint errors, or deciding where to put logic (render vs effect vs handler).
4ui-components
Create and style UI components with design tokens. Use when creating components, styling with Tailwind, deciding between Server/Client Components, or using shadcn/ui primitives.
4caching-strategy
Understand the caching architecture, Cache Components (PPR), and revalidation mechanisms. Use when debugging stale content, modifying caching behavior, or understanding data freshness guarantees.
4pdp
Product Detail Page architecture, image gallery/carousel, caching, and add-to-cart flow. Use when modifying PDP layout, debugging gallery swipe/thumbnails, understanding LCP optimization, fixing ErrorBoundary issues, or working with variant-specific images.
3checkout-architecture
Reference for reusable checkout UI components. Use when working with SignInForm, AddressSelector, PaymentMethodSelector, or composing checkout step views.
3variant-selection
Variant and attribute selection on product detail pages. Use when modifying variant selectors, debugging "Add to Cart" button state, understanding option availability, or adding discount badges to options.
3