Search & Filtering
ScentLok uses a dual search architecture combining FacetWP for faceted filtering with Algolia for instant search capabilities.
Overview
Faceted Search: FacetWP v4.3.5
Instant Search: WP Search with Algolia v2.8.2
FacetWP Implementation
Overview
FacetWP provides advanced faceted filtering for product searches with custom BigCommerce data source integration.
Configuration
// Enable FacetWP for BigCommerce products
add_filter( 'facetwp_index_row', function( $params, $class ) {
if ( 'bigcommerce_product' === $params['post_id'] ) {
$post_id = $params['post_id'];
// Index custom BigCommerce fields
if ( 'bc_price' === $params['facet_name'] ) {
$params['facet_value'] = get_post_meta( $post_id, 'bc_price', true );
}
if ( 'bc_brand' === $params['facet_name'] ) {
$terms = get_the_terms( $post_id, 'bc_brand' );
if ( $terms ) {
$params['facet_value'] = $terms[0]->name;
$params['facet_display_value'] = $terms[0]->name;
}
}
if ( 'bc_availability' === $params['facet_name'] ) {
$inventory = get_post_meta( $post_id, 'bc_inventory_level', true );
$params['facet_value'] = $inventory > 0 ? 'in_stock' : 'out_of_stock';
$params['facet_display_value'] = $inventory > 0 ? 'In Stock' : 'Out of Stock';
}
}
return $params;
}, 10, 2 );
Available Facets
Category Facet
// Category hierarchy facet
$facets[] = [
'name' => 'categories',
'label' => 'Categories',
'type' => 'hierarchy',
'source' => 'bc_category',
'parent_term' => 'category',
'orderby' => 'count',
'count' => 10,
];
Price Range Facet
// Price slider facet
$facets[] = [
'name' => 'price_range',
'label' => 'Price',
'type' => 'slider',
'source' => 'bc_price',
'prefix' => '$',
'min' => 0,
'max' => 500,
'step' => 10,
];
Brand Facet
// Brand checkbox facet
$facets[] = [
'name' => 'brand',
'label' => 'Brand',
'type' => 'checkboxes',
'source' => 'bc_brand',
'orderby' => 'display_value',
'show_expanded' => 'yes',
];
Size Facet
// Size dropdown facet
$facets[] = [
'name' => 'size',
'label' => 'Size',
'type' => 'dropdown',
'source' => 'pa_size', // Product attribute
'placeholder' => 'Select Size',
];
Color Facet
// Color swatch facet
$facets[] = [
'name' => 'color',
'label' => 'Color',
'type' => 'color',
'source' => 'pa_color',
];
// Custom color values
add_filter( 'facetwp_facet_render_args', function( $args, $params ) {
if ( 'color' === $params['facet']['name'] ) {
$args['values'] = [
'camo' => [ 'label' => 'Camo', 'color' => '#5C4033' ],
'black' => [ 'label' => 'Black', 'color' => '#000000' ],
'green' => [ 'label' => 'Green', 'color' => '#2D5016' ],
];
}
return $args;
}, 10, 2 );
Availability Facet
// In stock / out of stock facet
$facets[] = [
'name' => 'availability',
'label' => 'Availability',
'type' => 'radio',
'source' => 'bc_availability',
];
Custom Sort Options
// Add custom sort options
add_filter( 'facetwp_sort_options', function( $options, $params ) {
$options['price_asc'] = [
'label' => 'Price: Low to High',
'query_args' => [
'orderby' => 'meta_value_num',
'meta_key' => 'bc_price',
'order' => 'ASC',
],
];
$options['price_desc'] = [
'label' => 'Price: High to Low',
'query_args' => [
'orderby' => 'meta_value_num',
'meta_key' => 'bc_price',
'order' => 'DESC',
],
];
$options['newest'] = [
'label' => 'Newest First',
'query_args' => [
'orderby' => 'date',
'order' => 'DESC',
],
];
$options['popular'] = [
'label' => 'Most Popular',
'query_args' => [
'orderby' => 'meta_value_num',
'meta_key' => 'bc_total_sold',
'order' => 'DESC',
],
];
return $options;
}, 10, 2 );
Template Integration
// Product archive template with FacetWP
?>
<div class="shop-page">
<aside class="filters-sidebar">
<h3>Filter Products</h3>
<?php echo facetwp_display( 'facet', 'categories' ); ?>
<?php echo facetwp_display( 'facet', 'price_range' ); ?>
<?php echo facetwp_display( 'facet', 'brand' ); ?>
<?php echo facetwp_display( 'facet', 'size' ); ?>
<?php echo facetwp_display( 'facet', 'color' ); ?>
<?php echo facetwp_display( 'facet', 'availability' ); ?>
<button onclick="FWP.reset()">Clear Filters</button>
</aside>
<main class="products-main">
<div class="results-header">
<div class="results-count">
<?php echo facetwp_display( 'counts' ); ?>
</div>
<div class="results-sort">
<?php echo facetwp_display( 'sort' ); ?>
</div>
</div>
<div class="facetwp-template">
<?php
if ( have_posts() ) :
echo '<div class="product-grid">';
while ( have_posts() ) :
the_post();
get_template_part( 'template-parts/product-card' );
endwhile;
echo '</div>';
else :
echo '<p>No products found.</p>';
endif;
?>
</div>
<?php echo facetwp_display( 'pager' ); ?>
</main>
</div>
<?php
JavaScript Customization
// Listen for FacetWP refresh
document.addEventListener('facetwp-loaded', function() {
// Update URL with selected filters
if (FWP.loaded) {
let params = new URLSearchParams();
for (let [key, value] of Object.entries(FWP.facets)) {
if (value.length > 0) {
params.set(key, JSON.stringify(value));
}
}
let newUrl = window.location.pathname + '?' + params.toString();
window.history.replaceState({}, '', newUrl);
}
// Scroll to results
document.querySelector('.facetwp-template').scrollIntoView({
behavior: 'smooth',
block: 'start'
});
});
// Custom range slider formatting
document.addEventListener('facetwp-loaded', function() {
const slider = document.querySelector('.facetwp-slider');
if (slider) {
const min = FWP.facets['price_range'][0] || 0;
const max = FWP.facets['price_range'][1] || 500;
slider.setAttribute('data-label', `$${min} - $${max}`);
}
});
Performance Optimization
// Cache facet counts
add_filter( 'facetwp_cache_lifetime', function( $seconds ) {
return 3600; // 1 hour
});
// Pre-load facet data
add_action( 'facetwp_scripts', function() {
?>
<script>
// Pre-load selected facets from URL
FWP.auto_refresh = false;
FWP.facets = <?php echo json_encode( facetwp_get_url_vars() ); ?>;
FWP.soft_refresh = true;
</script>
<?php
});
Algolia Integration
Overview
Algolia provides instant search with typo tolerance and autocomplete.
Configuration
// wp-config.php
define( 'ALGOLIA_APPLICATION_ID', 'your-app-id' );
define( 'ALGOLIA_SEARCH_API_KEY', 'your-search-key' );
define( 'ALGOLIA_API_KEY', 'your-admin-key' );
Index Configuration
// Configure product index
add_filter( 'algolia_products_index_settings', function( $settings ) {
$settings['searchableAttributes'] = [
'post_title',
'content',
'taxonomies.bc_category',
'taxonomies.bc_brand',
'meta.bc_sku',
];
$settings['attributesForFaceting'] = [
'searchable(taxonomies.bc_category)',
'searchable(taxonomies.bc_brand)',
'filterOnly(meta.bc_price)',
'filterOnly(meta.bc_availability)',
];
$settings['customRanking'] = [
'desc(meta.bc_total_sold)',
'desc(post_date)',
];
$settings['replicas'] = [
'products_price_asc',
'products_price_desc',
'products_date_desc',
];
return $settings;
});
Custom Autocomplete
// Algolia autocomplete implementation
import algoliasearch from 'algoliasearch/lite';
import { autocomplete } from '@algolia/autocomplete-js';
const searchClient = algoliasearch(
'YOUR_APP_ID',
'YOUR_SEARCH_API_KEY'
);
autocomplete({
container: '#autocomplete',
placeholder: 'Search products...',
getSources({ query }) {
return [
{
sourceId: 'products',
getItems() {
return searchClient
.search([
{
indexName: 'wp_posts_bigcommerce_product',
query,
params: {
hitsPerPage: 8,
attributesToRetrieve: [
'post_title',
'permalink',
'images.thumbnail',
'meta.bc_price',
],
},
},
])
.then(({ results }) => results[0].hits);
},
templates: {
item({ item, components, html }) {
return html`
<a href="${item.permalink}" class="aa-ItemLink">
<div class="aa-ItemContent">
<div class="aa-ItemIcon">
<img
src="${item.images.thumbnail.url}"
alt="${item.post_title}"
/>
</div>
<div class="aa-ItemContentBody">
<div class="aa-ItemContentTitle">
${components.Highlight({
hit: item,
attribute: 'post_title',
})}
</div>
<div class="aa-ItemContentPrice">
$${item.meta.bc_price}
</div>
</div>
</div>
</a>
`;
},
},
},
];
},
});
InstantSearch UI
// Full search page with InstantSearch.js
import instantsearch from 'instantsearch.js';
import { searchBox, hits, refinementList, rangeSlider, pagination } from 'instantsearch.js/es/widgets';
const search = instantsearch({
indexName: 'wp_posts_bigcommerce_product',
searchClient,
routing: true,
});
search.addWidgets([
// Search box
searchBox({
container: '#searchbox',
placeholder: 'Search for products',
}),
// Category refinement
refinementList({
container: '#category-list',
attribute: 'taxonomies.bc_category',
limit: 10,
showMore: true,
}),
// Brand refinement
refinementList({
container: '#brand-list',
attribute: 'taxonomies.bc_brand',
searchable: true,
searchablePlaceholder: 'Search brands',
}),
// Price range slider
rangeSlider({
container: '#price-slider',
attribute: 'meta.bc_price',
tooltips: {
format(value) {
return `$${Math.round(value)}`;
},
},
}),
// Hits (results)
hits({
container: '#hits',
templates: {
item(hit, { html, components }) {
return html`
<div class="product-card">
<a href="${hit.permalink}">
<img src="${hit.images.thumbnail.url}" alt="${hit.post_title}" />
<h3>${components.Highlight({ hit, attribute: 'post_title' })}</h3>
<p class="price">$${hit.meta.bc_price}</p>
</a>
</div>
`;
},
},
}),
// Pagination
pagination({
container: '#pagination',
}),
]);
search.start();
BigCommerce Data Sync
// Sync BigCommerce data to Algolia
add_action( 'bigcommerce/product/imported', function( $post_id, $bc_product ) {
// Trigger Algolia re-index
do_action( 'algolia_reindex_post', $post_id );
}, 10, 2 );
// Custom product data for Algolia
add_filter( 'algolia_post_bigcommerce_product_shared_attributes', function( $attributes, $post ) {
$attributes['bc_id'] = get_post_meta( $post->ID, 'bc_id', true );
$attributes['bc_sku'] = get_post_meta( $post->ID, 'bc_sku', true );
$attributes['bc_price'] = (float) get_post_meta( $post->ID, 'bc_price', true );
$attributes['bc_sale_price'] = (float) get_post_meta( $post->ID, 'bc_sale_price', true );
$attributes['bc_inventory'] = (int) get_post_meta( $post->ID, 'bc_inventory_level', true );
$attributes['bc_availability'] = $attributes['bc_inventory'] > 0 ? 'in_stock' : 'out_of_stock';
$attributes['bc_total_sold'] = (int) get_post_meta( $post->ID, 'bc_total_sold', true );
return $attributes;
}, 10, 2 );
Search Analytics
Track Search Queries
// Log search queries
add_action( 'facetwp_scripts', function() {
?>
<script>
document.addEventListener('facetwp-loaded', function() {
if (FWP.facets.search && FWP.facets.search.length > 0) {
// Send to Google Analytics
gtag('event', 'search', {
search_term: FWP.facets.search[0]
});
// Log to database
fetch('/wp-json/suma/v1/log-search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: FWP.facets.search[0],
results: FWP.settings.pager.total_rows
})
});
}
});
</script>
<?php
});
Popular Searches
// Get most popular searches
function get_popular_searches( $limit = 10 ) {
global $wpdb;
return $wpdb->get_results(
$wpdb->prepare(
"SELECT query, COUNT(*) as count
FROM {$wpdb->prefix}search_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY query
ORDER BY count DESC
LIMIT %d",
$limit
)
);
}
Troubleshooting
FacetWP Issues
# Re-index all facets
wp facetwp index
# Clear facet cache
wp cache flush
Algolia Issues
# Re-index Algolia
wp algolia reindex
# Check index status
wp algolia indices