Search & Filtering
FacetWP Pro v4.x for faceted filtering and WP Search with Algolia v2.8.2 for instant search provide advanced product discovery.
Overview
StealthCam uses a dual search architecture:
- FacetWP Pro - Faceted filtering on category/catalog pages with AJAX-powered refinement
- Algolia - Instant search with autocomplete and typo tolerance
- BigCommerce Integration - Custom addon bridges FacetWP with BC product data
Both systems work together seamlessly to provide comprehensive product discovery.
FacetWP Integration
Plugin: FacetWP Pro v4.x
Location: wp-content/plugins/facetwp/
License: Commercial (FacetWP, LLC)
Documentation: https://facetwp.com/documentation/
Purpose
Provide faceted filtering for product catalogs with dynamic refinement based on BigCommerce product attributes like brand, category, price range, features, and custom fields.
Core Features
- Multiple Facet Types - Checkboxes, dropdowns, sliders, radio buttons, search, etc.
- AJAX Refresh - No page reload on filter changes
- URL Parameters - SEO-friendly filter URLs (
/products/?_brand=stealth-cam&_price=100-200) - Result Count - Display number of matching products
- Pagination - Integrated with filtered results
- Template Overrides - Custom result displays
- Indexing Engine - Fast query performance
- Mobile Responsive - Touch-optimized interfaces
Add-On Plugins
FacetWP Cache
Location: wp-content/plugins/facetwp-cache/
Caches facet data and query results for improved performance. Automatically purges cache when products are updated.
Configuration:
- Cache method: Object cache (Redis/Memcached) or transients
- Cache duration: 24 hours (default)
- Auto-purge on product save: Enabled
FacetWP Range List
Location: wp-content/plugins/facetwp-range-list/
Custom facet type for displaying numeric ranges as selectable list items instead of sliders. Useful for discrete sizes, quantities, or price tiers.
Example: Display shoe sizes as buttons instead of a slider.
FacetWP BigCommerce Integration
Location: wp-content/plugins/facetwp-bigcommerce/
Version: v0.2.1
Purpose: Custom integration that allows FacetWP to query and filter BigCommerce product data synced to WordPress.
Features:
- BC product data source
- BC custom field support
- Variant filtering
- Brand filtering
- Category filtering
- Price range filtering
- Stock status filtering
Facet Configuration
Admin Location: Settings → FacetWP → Facets
Example Facet Setup
Brand Facet
Facet Settings:
- Name:
brand - Label: Brand
- Type: Checkboxes
- Data Source: BigCommerce Product / Brand
- Sort: Alphabetical
- Show expanded: Yes
- Show search box: Yes (when 10+ brands)
Price Facet
Facet Settings:
- Name:
price - Label: Price Range
- Type: Slider
- Data Source: BigCommerce Product / Price
- Range: Auto-detect min/max
- Prefix:
$ - Format: Number with commas
Category Facet
Facet Settings:
- Name:
category - Label: Categories
- Type: Hierarchy
- Data Source: BigCommerce Category
- Show expanded: No
- Preserve Hierarchy: Yes
Features Facet (Custom Fields)
Facet Settings:
- Name:
features - Label: Features
- Type: Checkboxes
- Data Source: Custom Field / bc_features
- Show count: Yes
Template Configuration
Admin Location: Settings → FacetWP → Templates
Product Grid Template
Template Name: products
Query Type: Post Type
Post Types: bigcommerce_product
Posts Per Page: 24
Order By: Title (A-Z)
Template Code:
<div class="facetwp-template">
<div class="product-grid">
<?php
if (have_posts()) :
while (have_posts()) : the_post();
$product = new \BigCommerce\Post_Types\Product\Product(get_the_ID());
?>
<div class="product-card">
<a href="<?php the_permalink(); ?>" class="product-link">
<div class="product-image">
<?php the_post_thumbnail('product-card'); ?>
</div>
<div class="product-info">
<h3 class="product-title"><?php the_title(); ?></h3>
<div class="product-brand">
<?php echo esc_html($product->brand()); ?>
</div>
<div class="product-price">
<?php echo $product->price_range_html(); ?>
</div>
</div>
</a>
</div>
<?php
endwhile;
else :
?>
<div class="no-results">
<p>No products found matching your filters.</p>
</div>
<?php
endif;
?>
</div>
</div>
Display Facets in Theme
Location: Product archive pages, category pages
<!-- Facet Sidebar -->
<div class="facet-sidebar">
<!-- Brand Filter -->
<div class="facet-group">
<h4 class="facet-title">Brand</h4>
<?php echo facetwp_display('facet', 'brand'); ?>
</div>
<!-- Price Filter -->
<div class="facet-group">
<h4 class="facet-title">Price</h4>
<?php echo facetwp_display('facet', 'price'); ?>
</div>
<!-- Category Filter -->
<div class="facet-group">
<h4 class="facet-title">Categories</h4>
<?php echo facetwp_display('facet', 'category'); ?>
</div>
<!-- Features Filter -->
<div class="facet-group">
<h4 class="facet-title">Features</h4>
<?php echo facetwp_display('facet', 'features'); ?>
</div>
<!-- Active Filters -->
<div class="active-filters">
<h4 class="facet-title">Active Filters</h4>
<?php echo facetwp_display('selections'); ?>
<button class="reset-filters" onclick="FWP.reset()">Clear All</button>
</div>
</div>
<!-- Product Grid -->
<div class="facet-content">
<!-- Result Count & Sort -->
<div class="facet-controls">
<div class="result-count">
<?php echo facetwp_display('counts'); ?>
</div>
<div class="sort-dropdown">
<?php echo facetwp_display('facet', 'sort'); ?>
</div>
</div>
<!-- Product Results -->
<?php echo facetwp_display('template', 'products'); ?>
<!-- Pagination -->
<div class="facet-pagination">
<?php echo facetwp_display('pager'); ?>
</div>
</div>
JavaScript Integration
FacetWP Events
// After facets refresh and results load
document.addEventListener('facetwp-loaded', function() {
console.log('FacetWP loaded');
// Custom code after results load
// Re-initialize any JS components
initProductCards();
// Track analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'facet_interaction', {
facets: FWP.facets
});
}
});
// Before facets refresh
document.addEventListener('facetwp-refresh', function() {
console.log('FacetWP refreshing');
// Show loading state
document.querySelector('.facetwp-template').classList.add('loading');
});
Custom Facet Behavior
// Reset filters button
document.querySelector('.reset-filters').addEventListener('click', function(e) {
e.preventDefault();
FWP.reset();
});
// Programmatically set facet value
FWP.facets['brand'] = ['stealth-cam'];
FWP.refresh();
// Get current facet values
console.log(FWP.facets);
Theme Integration
Location: wp-content/themes/suma-elementor/inc/integrations/class-facetwp.php
<?php
namespace Suma\Integrations;
class FacetWP {
public function __construct() {
add_filter('facetwp_render_output', [$this, 'custom_facet_html'], 10, 2);
add_filter('facetwp_facet_html', [$this, 'facet_html_override'], 10, 2);
add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
}
/**
* Custom facet HTML wrapper
*/
public function custom_facet_html($output, $params) {
// Add custom classes, wrapper elements
return '<div class="suma-facet-wrapper">' . $output . '</div>';
}
/**
* Override specific facet HTML
*/
public function facet_html_override($html, $params) {
if ('brand' === $params['facet']['name']) {
// Custom brand facet HTML
}
return $html;
}
/**
* Enqueue FacetWP custom styles
*/
public function enqueue_assets() {
if (class_exists('FacetWP')) {
wp_enqueue_style(
'suma-facetwp',
get_template_directory_uri() . '/assets/dist/css/facetwp.css'
);
}
}
}
new FacetWP();
Mobile Optimization
Filter Drawer: Mobile devices show filters in a drawer/modal instead of sidebar.
<!-- Mobile Filter Button -->
<button class="filter-toggle mobile-only" id="filter-toggle">
<span class="icon">⚙</span>
<span class="text">Filters</span>
<span class="count"><?php echo facetwp_display('counts'); ?></span>
</button>
<!-- Filter Drawer -->
<div class="filter-drawer" id="filter-drawer">
<div class="filter-drawer-header">
<h3>Filters</h3>
<button class="close-drawer">×</button>
</div>
<div class="filter-drawer-body">
<!-- Facets here -->
</div>
<div class="filter-drawer-footer">
<button class="btn-apply" onclick="document.getElementById('filter-drawer').classList.remove('is-open')">
Apply Filters
</button>
<button class="btn-reset" onclick="FWP.reset()">
Clear All
</button>
</div>
</div>
// Mobile filter drawer toggle
document.getElementById('filter-toggle').addEventListener('click', function() {
document.getElementById('filter-drawer').classList.add('is-open');
});
document.querySelector('.close-drawer').addEventListener('click', function() {
document.getElementById('filter-drawer').classList.remove('is-open');
});
Algolia Instant Search
Plugin: WP Search with Algolia v2.8.2
Location: wp-content/plugins/wp-search-with-algolia/
Service: Algolia SaaS (cloud-hosted search)
Purpose
Provide instant, typo-tolerant search with autocomplete for products, content, FAQs, and dealers.
Features
- Instant Results - Search-as-you-type with sub-100ms response times
- Typo Tolerance - Handles misspellings automatically
- Autocomplete - Suggest products and content while typing
- Faceted Search - Filter results by attributes
- Highlighting - Highlight matching terms in results
- Ranking - Customizable relevance ranking
- Analytics - Search analytics and insights
- Multi-Index - Search across multiple content types
Configuration
Admin Location: Settings → Algolia Search
Application Settings
- Application ID: StealthCam Algolia app ID
- Search API Key: Public search-only key
- Admin API Key: Backend indexing key (keep secure)
Indexed Content Types
Posts: bigcommerce_product, post, page, suma_dealer, suma_faq, suma_download
Taxonomies: bigcommerce_category, bigcommerce_brand, download_category, faq_category
Index Configuration
Products Index: stealthcam_products
Searchable Attributes (ranked):
- Product title
- Product description
- Brand
- SKU
- Categories
- Custom fields
Attributes for Faceting:
- Brand
- Category
- Price (numeric)
- Features
- Stock status
Custom Ranking:
- Featured products (desc)
- Review count (desc)
- Sales count (desc)
- Price (asc)
Search Box Integration
Global Search (Header)
Location: Theme header via Elementor widget
<div class="algolia-search-box" id="algolia-search-global">
<input
type="search"
id="algolia-search-input"
placeholder="Search products, articles, FAQs..."
autocomplete="off"
/>
<button type="submit" class="search-button">
<svg><!-- Search icon --></svg>
</button>
</div>
<!-- Autocomplete Dropdown -->
<div class="algolia-autocomplete" id="algolia-autocomplete">
<!-- Populated by Algolia InstantSearch.js -->
</div>
JavaScript Initialization
Location: assets/src/js/modules/algolia-search.js
import algoliasearch from 'algoliasearch/lite';
import instantsearch from 'instantsearch.js';
import { searchBox, hits, refinementList, rangeSlider } from 'instantsearch.js/es/widgets';
// Initialize Algolia client
const searchClient = algoliasearch(
'YOUR_APP_ID',
'YOUR_SEARCH_API_KEY'
);
// Create search instance
const search = instantsearch({
indexName: 'stealthcam_products',
searchClient,
routing: true // Enable URL routing
});
// Add search box widget
search.addWidgets([
searchBox({
container: '#algolia-search-input',
placeholder: 'Search products...',
showSubmit: true,
showReset: true,
showLoadingIndicator: true,
})
]);
// Add hits (results) widget
search.addWidgets([
hits({
container: '#algolia-hits',
templates: {
item: `
<div class="algolia-hit">
<a href="{{permalink}}">
<img src="{{image_url}}" alt="{{name}}">
<div class="hit-content">
<h3>{{#helpers.highlight}}{ "attribute": "name" }{{/helpers.highlight}}</h3>
<p class="hit-brand">{{brand}}</p>
<p class="hit-price">${{price}}</p>
</div>
</a>
</div>
`,
empty: '<div class="no-results">No results found for <strong>{{query}}</strong></div>'
}
})
]);
// Add refinement widgets (filters)
search.addWidgets([
refinementList({
container: '#brand-filter',
attribute: 'brand',
limit: 10,
showMore: true,
searchable: true,
searchablePlaceholder: 'Search brands...'
}),
rangeSlider({
container: '#price-filter',
attribute: 'price',
pips: false,
tooltips: {
format(value) {
return `$${Math.round(value)}`;
}
}
})
]);
// Start search
search.start();
Autocomplete Integration
Library: Algolia Autocomplete.js
import { autocomplete } from '@algolia/autocomplete-js';
const autocompleteSearch = autocomplete({
container: '#algolia-search-input',
placeholder: 'Search...',
getSources({ query }) {
return [
{
sourceId: 'products',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: 'stealthcam_products',
query,
params: {
hitsPerPage: 5
}
}
]
});
},
templates: {
item({ item, components }) {
return `
<div class="autocomplete-product">
<img src="${item.image_url}" alt="${item.name}">
<div>
<div class="autocomplete-product-name">
${components.Highlight({ hit: item, attribute: 'name' })}
</div>
<div class="autocomplete-product-price">$${item.price}</div>
</div>
</div>
`;
}
}
},
{
sourceId: 'articles',
getItems() {
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: 'stealthcam_posts',
query,
params: {
hitsPerPage: 3
}
}
]
});
}
}
];
}
});
Search Page Template
Location: wp-content/themes/suma-elementor/search.php
Full-featured search results page with filters, sorting, and pagination.
<?php get_header(); ?>
<div class="algolia-search-page">
<aside class="search-sidebar">
<h3>Refine Results</h3>
<!-- Brand Filter -->
<div id="brand-filter"></div>
<!-- Price Filter -->
<div id="price-filter"></div>
<!-- Category Filter -->
<div id="category-filter"></div>
<!-- Clear Filters -->
<button id="clear-filters">Clear All Filters</button>
</aside>
<main class="search-content">
<!-- Search Stats -->
<div id="algolia-stats"></div>
<!-- Sort Dropdown -->
<div id="algolia-sort"></div>
<!-- Search Results -->
<div id="algolia-hits"></div>
<!-- Pagination -->
<div id="algolia-pagination"></div>
</main>
</div>
<?php get_footer(); ?>
Custom Ranking Formula
Algolia Dashboard: Index Settings → Ranking
1. Typo (default)
2. Geo (default)
3. Words (default)
4. Filters (default)
5. Proximity (default)
6. Attribute (default)
7. Exact (default)
8. Custom: featured_product (desc)
9. Custom: review_count (desc)
10. Custom: sales_count (desc)
Indexing Schedule
Cron Job: Hourly product reindex
// Schedule Algolia reindex
add_action('suma_algolia_reindex', function() {
if (class_exists('Algolia_Plugin')) {
$plugin = Algolia_Plugin::get_instance();
$plugin->get_indices_manager()->reindex_post_type('bigcommerce_product');
}
});
// Register cron schedule
if (!wp_next_scheduled('suma_algolia_reindex')) {
wp_schedule_event(time(), 'hourly', 'suma_algolia_reindex');
}
Theme Integration
Location: wp-content/themes/suma-elementor/inc/integrations/class-algolia.php
<?php
namespace Suma\Integrations;
class Algolia {
public function __construct() {
add_filter('algolia_searchable_post_shared_attributes', [$this, 'add_custom_attributes'], 10, 2);
add_filter('algolia_should_index_post', [$this, 'should_index_product'], 10, 2);
}
/**
* Add custom attributes to Algolia index
*/
public function add_custom_attributes($attributes, $post) {
if ('bigcommerce_product' === $post->post_type) {
$product = new \BigCommerce\Post_Types\Product\Product($post->ID);
// Add BigCommerce data
$attributes['bc_id'] = $product->bc_id();
$attributes['brand'] = $product->brand();
$attributes['sku'] = $product->sku();
$attributes['stock_level'] = $product->inventory_level();
$attributes['featured_product'] = get_post_meta($post->ID, 'featured', true);
$attributes['review_count'] = $product->review_count();
$attributes['average_rating'] = $product->average_rating();
// Price data
$prices = $product->price_range();
$attributes['price'] = $prices['calculated']['min'] ?? 0;
}
return $attributes;
}
/**
* Control which products to index
*/
public function should_index_product($should_index, $post) {
if ('bigcommerce_product' === $post->post_type) {
// Don't index draft or hidden products
if ('publish' !== $post->post_status) {
return false;
}
}
return $should_index;
}
}
new Algolia();
Performance Optimization
FacetWP Caching
- Object Cache: Redis-backed cache for facet queries
- Transient Cache: Fallback for shared hosting
- Auto Purge: Clears cache on product updates
Algolia Optimization
- Geo-Distributed: Servers in multiple regions for low latency
- Query Suggestions: Pre-computed suggestions for common queries
- Index Replicas: Separate indices for different sort orders
- Conditional Indexing: Skip indexing for certain content
Combined Strategy
Use FacetWP for category pages with predictable filters, and Algolia for global search and complex queries. This balances server load with search quality.