Customizations
1. Custom Response Processing
Override the fetcher method to customize response processing:
// In a code.js block
if (searchClient && searchClient.fetcher) {
const originalFetcher = searchClient.fetcher.bind(searchClient)
searchClient.fetcher = async function (params) {
const result = await originalFetcher(params)
// Custom processing
result.products = result.products.map((product) => ({
...product,
customField: "customValue",
}))
return result
}
}2. Custom Filter Processing
Override filter methods to add custom logic:
// Custom filter application
if (searchClient) {
const originalSetFilter = searchClient.setFilter.bind(searchClient)
searchClient.setFilter = async function (filter) {
// Add custom pre-processing
if (filter.key === "price") {
filter.value = formatPrice(filter.value)
}
return originalSetFilter(filter)
}
}3. Custom Sort Options
Modify sort option resolution:
if (searchClient) {
const originalResolveSortOptions =
searchClient.resolveSortOptions?.bind(searchClient)
searchClient.resolveSortOptions = function (label) {
// Custom sort mapping
const customSortMap = {
popularity: "custom_popularity_score",
newest: "created_at_desc",
}
return customSortMap[label] || originalResolveSortOptions?.(label) || label
}
}4. Using a Specific Search Integration in a code.js Block
When building custom blocks, you receive useSearchContext as a prop. Pass a provider name to activate that integration for your block and the entire layout.
Selecting a Provider
// In templates/BlockTemplates/MyCustomBlock/code.js
export default function MyCustomBlock({
useSearchContext = () => ({ searchClient: null, isSearchClient: false }),
}) {
// Select Algolia as the active provider
const { searchClient, isSearchClient } = useSearchContext("algolia")
...
}Supported provider names:
"algolia""search-spring""nosto-search""instant-search-plus"
Important: Calling
useSearchContext("algolia")sets Algolia as the active provider for the entire layout, not just this block. If multiple blocks calluseSearchContextwith different provider names, the last one to render will win. Best practice is to select the provider once in your main product grid or search block, and let other blocks inherit it by callinguseSearchContext()without arguments.
Example: ProductGrid with Conditional Provider Selection
export default function ProductGrid({
useSearchContext = () => ({ searchClient: null, isSearchClient: false }),
useSearchParams,
pageState,
}) {
const searchParams = useSearchParams()
const searchQuery = searchParams?.get("searchQuery") || pageState?.searchParams?.searchQuery
// Use Algolia when searching, otherwise don't set a provider (pass undefined)
const providerType = searchQuery ? "algolia" : undefined
const { searchClient, isSearchClient } = useSearchContext(providerType)
...
}Example: Carousel Using a Different Provider Instance
export default function RecommendedProducts({ blockConfig }) {
// Create a separate Search Spring instance for this carousel
const carouselClient = useSearchInstance({
searchParams: { collectionId: blockConfig.collectionId },
productsPerPage: 8,
type: "search-spring",
})
...
}Key Points
- Pass the provider name to
useSearchContext()once in your main block to activate it - Pass
undefinedtouseSearchContext()to not set a provider and inherit the current active provider or fall back to default behavior. - Use
useSearchInstance()when you need a separate client instance with different parameters. This will not affect the entire layout and is typically used in carousels. - Remember that provider selection with
useSearchContext()affects the entire layout, not just the current block.
Integration-Specific Customizations
Algolia
Search Parameters Customization
// Override search parameters to add custom filters
searchClient.setSearchParametersOnHelper = function (searchParameters) {
const existingFilters = searchParameters.filters
const customFilter = "(inStock:true) AND isOnline:true AND (NOT isNfs:true)"
searchParameters.filters = existingFilters
? `${existingFilters} AND ${customFilter}`
: customFilter
return searchParameters
}Query and Index Management
// Set search query
searchClient.setQueryOnHelper("search term")
// Set search index for sorting
searchClient.setIndexOnHelper("products_price_asc")
// Override rule contexts for custom merchandising behavior
searchClient.setRuleContextsOnHelper = function () {
// Access the helper via this.helper
if (this.collectionHandle) {
const customRuleContext = `custom-${this.collectionHandle}`
this.helper.setState(
this.helper.state.setQueryParameter("ruleContexts", [customRuleContext])
)
}
}Note:
setRuleContextsOnHelperis a method to override, not just invoke. It will replace the default rule context behavior.
Access Algolia Helper Directly
// Access Algolia-specific helper for advanced customizations
// Modify search parameters directly
searchClient.helper.setState(
searchClient.helper.state.setQueryParameter("analyticsTags", ["custom-tag"])
)
// Add custom facet configuration
searchClient.helper.setState(
searchClient.helper.state.addFacet("custom_attribute")
)Important: Direct helper access is best used within overridden search client methods (like
setRuleContextsOnHelper, orapplyBaseParamsToIntegration). Accessing the helper outside of these methods can lead to unintended results, as the search client may reset or override your changes during its internal operations. Always prefer method overrides when possible for more predictable behavior.
Response Formatting
// Custom response formatting for Algolia results
searchClient.formatResponse = function ({ results, isFacetsOnly }) {
// Custom logic here
return {
products: results.hits,
page: results.page,
totalPages: results.nbPages,
totalProducts: results.nbHits,
hasMore: results.page + 1 < results.nbPages,
nextPage: results.page + 1 < results.nbPages ? results.page + 1 : null,
// Add custom fields
customField: "custom value",
facets: results.facets,
}
}SearchSpring Client
Request Parameters
// Customize request parameters
if (searchClient.buildRequestParams) {
const original = searchClient.buildRequestParams.bind(searchClient)
searchClient.buildRequestParams = function (params) {
const result = original(params)
// Add custom parameters
result.append("customParam", "customValue")
result.append("boost", "featured:1.5")
return result
}
}Response Formatting
// Custom response formatting for SearchSpring
if (searchClient.formatSearchSpringResponse) {
searchClient.formatSearchSpringResponse = function (response) {
// Return null to use default formatting, or return custom response
return {
products: response.results,
page: response.pagination.currentPage - 1,
totalPages: response.pagination.totalPages,
totalProducts: response.pagination.totalResults,
hasMore: response.pagination.currentPage < response.pagination.totalPages,
nextPage:
response.pagination.currentPage < response.pagination.totalPages
? response.pagination.currentPage
: null,
// Add custom metadata
searchMetadata: response.merchandising || {},
}
}
}URL Building
// Custom URL building for SearchSpring requests
if (searchClient.buildSearchSpringUrl) {
searchClient.buildSearchSpringUrl = function (baseUrl, params) {
// Add custom query parameters or modify URL structure
return `${baseUrl}?${params.toString()}&customParam=value&tracking=enabled`
}
}Merchandising Rules
// Apply custom merchandising rules
if (searchClient.applyMerchandisingRules) {
searchClient.applyMerchandisingRules = function (params) {
params.append("boost", "featured:true")
params.append("merchandising", "seasonal_promotion")
params.append("pin", "productId:12345:1") // Pin product to position 1
}
}Custom Headers
// Set custom headers for SearchSpring requests
if (searchClient.setCustomHeaders) {
searchClient.setCustomHeaders({
"X-Custom-Header": "value",
"X-Store-Context": "premium",
Authorization: "Bearer token",
})
}Nosto Search Client
Custom Rules and Segments
// Set custom rules for Nosto search
if (searchClient.setCustomRules) {
searchClient.setCustomRules([
{
field: "category",
operation: "equals",
value: "featured",
},
{
field: "price",
operation: "range",
value: { min: 10, max: 100 },
},
])
}
// Set user segments for personalization
if (searchClient.setSegments) {
searchClient.setSegments([
"premium_customers",
"mobile_users",
"returning_visitors",
])
}
// Set session parameters for tracking
if (searchClient.setSessionParams) {
searchClient.setSessionParams({
userId: "user123",
sessionId: "session456",
visitorId: "visitor789",
})
}Request Headers
// Set custom headers for Nosto requests
if (searchClient.setRequestHeaders) {
searchClient.setRequestHeaders({
"X-Custom-Header": "value",
"X-User-Segment": "premium",
"X-Device-Type": "mobile",
})
}GraphQL Operation Name
// Set GraphQL operation name for Nosto
if (searchClient.setOperationName) {
searchClient.setOperationName("CustomProductSearch")
}Request Parameters
// Customize request parameters for Nosto
if (searchClient.buildRequestParams) {
const original = searchClient.buildRequestParams.bind(searchClient)
searchClient.buildRequestParams = function (baseParams) {
const params = original(baseParams)
// Add custom logic
params.customField = "customValue"
params.personalization = true
return params
}
}Response Formatting
// Custom response formatting for Nosto
if (searchClient.formatNostoResponse) {
searchClient.formatNostoResponse = function (result) {
// Return null to use default formatting, or return custom response
const hits = result.search.products.hits || []
const totalProducts = result.search.products.total || 0
return {
products: hits, // Will be processed through fetchProductsByNostoHits
page: 0,
totalPages: Math.ceil(totalProducts / this.productsPerPage),
totalProducts,
hasMore: hits.length === this.productsPerPage,
nextPage: hits.length === this.productsPerPage ? 1 : null,
// Add Nosto-specific metadata
recommendations: result.recommendations || [],
segments: result.segments || [],
}
}
}Search Client Hooks: Selecting Providers and Usage
This provider exposes hooks that let blocks/components select which search provider to use at runtime. Supported providers:
- algolia
- nosto-search
- search-spring
- instant-search-plus
You can set a global provider for the entire layout or use a specific provider for an instance as needed.
useSearchContext: Select a Provider for the Layout
The useSearchContext hook optionally accepts a SearchClientType. Passing a type sets the active client for the whole layout. Calling it without a type returns the current active client.
import { useSearchContext } from "@/lib/context-providers/search-client-provider"
export function MyBlock() {
// Select Algolia for the whole layout
const { searchClient, isSearchClient, activeClientType } =
useSearchContext("algolia")
if (!isSearchClient || !searchClient) return null
// Use the client as needed...
return <div>Active client: {activeClientType}</div>
}Example: ProductGrid selecting a provider
If your ProductGrid block uses the search client, you can target a specific provider:
import { useSearchContext } from "@/lib/context-providers/search-client-provider"
export function ProductGrid() {
// Force Search Spring for the entire layout
const { searchClient, isSearchClient } = useSearchContext("search-spring")
if (!isSearchClient || !searchClient) return null
// Render grid using the active search client
return <div>{/* grid implementation */}</div>
}Multiple Blocks Using the Search Client on the Same Layout
When multiple blocks call useSearchContext, prefer setting the provider once so all blocks align. There are two common patterns:
- Controller block pattern (recommended): Have a single early-rendered block set the provider. Other blocks call
useSearchContext()without a type and inherit the active client. - Direct selection (acceptable): Each block can call
useSearchContext("<provider>"). Ensure they all pass the same provider to avoid thrashing.
Setting it once example:
import { useSearchContext } from "@/lib/context-providers/search-client-provider"
export function ProductGrid() {
const { searchClient } = useSearchContext("nosto-search")
if (!searchClient) return null
return <div>{/* grid implementation */}</div>
}You can also manually change the active provider later using the setActiveClientType function from the context if needed.
import { useSearchContext } from "@/lib/context-providers/search-client-provider"
export function Switcher() {
const { setActiveClientType, activeClientType } = useSearchContext()
return (
<button onClick={() => setActiveClientType("algolia")}>
Switch to Algolia (current: {activeClientType})
</button>
)
}useSearchInstance: Create a Client Instance for Carousels or Scoped Queries
Use useSearchInstance for components that need their own client instance (e.g., carousels). By default it uses the active provider, but you can target a specific provider for that instance with the type option.
import { useSearchInstance } from "@/lib/context-providers/search-client-provider"
export function ProductCarousel({ collectionId }: { collectionId: string }) {
// Use the active layout provider
const carouselClient = useSearchInstance({
searchParams: { collectionId },
productsPerPage: 8,
})
if (!carouselClient) return null
// Use carouselClient.get(...) etc.
return <div>{/* carousel implementation */}</div>
}Target a specific provider for the instance:
const carouselClient = useSearchInstance({
searchParams: { collectionId },
productsPerPage: 8,
type: "search-spring", // Only this instance uses Search Spring
})Tips
- Prefer setting the provider once to keep the layout consistent.
- Passing a stable literal to
useSearchContext("<provider>")is safe; avoid toggling providers dynamically across blocks.
Updated 10 days ago
