Configuring JS layout
Fast Simon PLP uses advanced view rendering to bring the most optimized user experience. To adjust elements or logic we provide unique hooks you can use to edit the elements we generate.
Getting Started
PLP hook Flow
- Register a hook using a script tag located in the
<head> - PLP will pickup the hook and will execute any code you write there
- Hook will run again on every life cycle update
Rules
- Registering a hook with the same event, ensure they do not modify the same code scope to avoid conflicts.
- You can only manipulate the dom element provided by the hook.
- We only update the dom nodes that changed in each life cycle change, so make sure your code doesn't add the same elements twice (see examples)
- hook runs per item, data will contain the specific identifier
Register a new hook & execute it
<script>
function hooks() {
window.SerpOptions.registerHook('{{ customization_hook_name_1 }}', ({data, element}) => {
// logic here
});
window.SerpOptions.registerHook('{{ customization_hook_name_2 }}', ({data, element}) => {
// logic here
});
}
// execution here
if (window.SerpOptions) {
hooks();
} else {
window.addEventListener('fast-serp-ready', function () {
hooks();
});
}
</script>
Available Hooks
| Event Name | Description | Hook Params | Return Params |
|---|---|---|---|
| "serp-product-grid" | Life cycle hook: every plp product grid update | object: {products: Products, element: HTMLElement} | void |
| "serp-filters" | Life cycle hook: every plp filters update | object: {facets: Facet[], element: HTMLElement} | void |
| "serp-quick-view" | Life cycle hook: every plp quick view pop up created | object: {product: Product, index: number, onClose: (reason: CloseQuickViewReason, event: [Event]) => void, mode: [Device], element: HTMLElement} | void |
| "format-serp-size-variant-selector-options" | Format size selector options (display text only) | string | string |
| "serp-fixed-narrow" | Life cycle hook: add a fixed narrow | void | HookNarrow |
| "serp-product-swatches" | Life cycle hook: every plp product swatches update | object: {colors: [colors[]], swatchClick: [Event], showMoreClick: boolean, element: HTMLElement} | void |
| "serp-removable-tags" | Life cycle hook: every plp removable tag filter update | object: {tags: [RemovableTags], element: HTMLElement} | void |
| "serp-top-page" | Life cycle hook: every plp top page update | void | void |
| "serp-main-block" | Life cycle hook: every plp main block update | void | void |
| "serp-paging" | Life cycle hook: every pagination update (SSR version only) | void | void |
type references: Typescript Types
interface HookNarrow{
[FilterName: string]: FilterValues[]
}
type FilterValues = string
SerpOptions Methods
In addition to registerHook, window.SerpOptions exposes the following helper methods you can call from your customization code:
| Method | Description | Returns |
|---|---|---|
getNarrow() | Returns the current narrow (selected filters) as Record<string, Set<string>> | Narrow |
getQuery() | Returns the current search query (or empty string if not in search mode) | string |
getSort() | Returns the current sort key | string |
getPage() | Returns the current page number | number |
getCategory() | Returns the current category data (async) | Promise<Category \| null> |
navigateToCategory(args) | Programmatically swaps the current collection for a different one. See below. | { ok: true } \| { ok: false, error: string } |
navigateToCategory(args)
Tells the storefront to navigate to a different collection — using the same categories_navigation request the shopper would trigger by clicking a link to that collection. Use this from a custom event listener to redirect on specific filter combinations or other shopper signals.
interface NavigateToCategoryArgs {
categoryID: string; // required, Fast Simon category id
categoryURL?: string; // strongly recommended; falls back to `/collections/${categoryID}` (Shopify path style) if omitted
clearNarrow?: boolean; // default true; if false, residualNarrow replaces the cleared narrow
residualNarrow?: Record<string, Set<string> | string[]>;
reason?: string; // free-form analytics tag, echoed back on the navigate event
}
type NavigateToCategoryResult =
| { ok: true }
| { ok: false, error: string };
Cross-platform note: the
categoryURLfallback uses Shopify's/collections/...path convention. On BigCommerce, Magento, Wix, or custom storefronts, always passcategoryURLexplicitly with the path your platform produces (e.g."/blue-large-shirts/"on BigCommerce,"/blue-large-shirts.html"on Magento). ThecategoryIDitself comes from the Fast Simon dashboard and is identical regardless of platform.
Built-in safety guards
The function is safe to call from any listener — it has cooldowns and limits that prevent runaway calls. Each guard rejects with { ok: false, error: string } and dispatches a fs-custom-events-navigate-to-category-skipped event with the guard label:
| Guard | When it rejects |
|---|---|
validation | categoryID missing or not a non-empty string |
first-load | Storefront not yet hydrated (the canonical fast-serp-ready event hasn't fired) |
search-mode | A search query is active (feature is collection-only) |
merch-rules-preview / merch-strategies-preview / preview-mode | Merchandising or A/B preview is active |
merchant-disabled | __fast_options.disable_navigate_to_category is true |
popstate-cooldown | Browser back/forward fired in the last 500 ms |
cooldown | Another navigateToCategory call succeeded in the last 750 ms |
in-flight | A previous call is still navigating |
max-per-page | Exceeded __fast_options.max_navigate_to_category_per_page (default 5) |
no-op | args.categoryID equals the current category id |
The callCount resets when the shopper hits browser back/forward, so legitimate exploration isn't blocked.
Companion events
fs-custom-events-navigate-to-category— fires on a successful call. Detail:{from, to, reason}.fs-custom-events-navigate-to-category-skipped— fires when a guard rejects. Detail:{error, guard, reason}.
See Custom Events for full payload references.
Example
function start() {
window.SerpOptions.navigateToCategory({
categoryID: "287654321098",
categoryURL: "/collections/blue-large-shirts",
clearNarrow: true,
reason: "facet-combo:color:blue|size:large"
});
}
if (window.SerpOptions) {
start();
} else {
window.addEventListener('fast-serp-ready', start, { once: true });
}
A complete working example that listens to filter clicks and redirects on specific combos: Facet Combo → Category Redirect.