Documentation Index Fetch the complete documentation index at: https://docs.joinrefine.io/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Product Listing Pages (PLPs) are curated collections of products, typically used for category pages, collections, or promotional landing pages. PLPs support pinned products, intelligent ranking, and merchandising rules.
Basic Usage
import { Refine } from '@refine-ai/sdk' ;
const refine = new Refine ({
apiKey: process . env . REFINE_API_KEY ,
organizationId: 'org_abc123' ,
catalogId: 'cat_xyz789'
});
// Fetch a PLP by its configuration ID
const plp = await refine . plp . get ( 'plp_summer_collection' );
// Get all products
console . log ( plp . results );
// Get pinned products specifically
const pinnedProducts = plp . getPinnedProducts ();
Creating PLPs
PLPs are created and managed in the Refine dashboard:
Dashboard → Product Listing Pages → New PLP
Configuration options include:
Option Description Name Internal name for the PLP Slug URL-friendly identifier Products Selected products for the collection Pinned Products Products pinned to specific positions Ranking Sort order (manual, popularity, newest, etc.) Filters Default filters applied to the collection
Response Structure
interface PLPResponse {
results : PLPProduct [];
pinnedPositions : Map < string , number >;
totalResults : number ;
configId : string ;
}
interface PLPProduct {
productId : string ;
title : string ;
price : number ;
imageUrl : string ;
metadata : Record < string , any >;
isPinned : boolean ;
pinnedPosition ?: number ;
}
Pinned Products
Pinned products appear at specific positions regardless of the ranking algorithm:
const plp = await refine . plp . get ( 'plp_summer_collection' );
// Get only pinned products
const pinned = plp . getPinnedProducts ();
// Returns products sorted by their pinned position
// Check if a specific product is pinned
const product = plp . results [ 0 ];
if ( product . isPinned ) {
console . log ( `Pinned at position ${ product . pinnedPosition } ` );
}
Tracking PLP Views
Track when users view and interact with PLPs:
const plp = await refine . plp . get ( 'plp_summer_collection' );
const plpContext = refine . events . trackItemsServed ({
surface: 'category_page' ,
source: 'plp' ,
itemIds: plp . results . map ( p => p . productId ),
totalResults: plp . totalResults ,
metadata: {
plpId: 'plp_summer_collection' ,
plpName: 'Summer Collection'
}
});
// Track interactions
plpContext . trackClick ( productId , position );
plpContext . trackView ( productId , position );
plpContext . trackAddToCart ( productId );
With Filters
Apply additional filters at runtime:
const plp = await refine . plp . get ( 'plp_summer_collection' , {
filters: [
{ field: 'price' , operator: 'lte' , value: 100 },
{ field: 'metadata.size' , operator: 'in' , value: [ 'S' , 'M' , 'L' ] }
]
});
Complete Implementation
import { Refine } from '@refine-ai/sdk' ;
const refine = new Refine ({
apiKey: process . env . REFINE_API_KEY ,
organizationId: 'org_abc123' ,
catalogId: 'cat_xyz789'
});
class CategoryPage {
private plpContext : any ;
async load ( categorySlug : string ) {
const plp = await refine . plp . get ( categorySlug );
// Track the PLP serve
this . plpContext = refine . events . trackItemsServed ({
surface: 'category_page' ,
source: 'plp' ,
itemIds: plp . results . map ( p => p . productId ),
totalResults: plp . totalResults ,
metadata: { categorySlug }
});
this . render ( plp );
return plp ;
}
private render ( plp : PLPResponse ) {
const container = document . getElementById ( 'products' ) ! ;
container . innerHTML = plp . results . map (( product , index ) => `
<div class="product ${ product . isPinned ? 'pinned' : '' } "
data-id=" ${ product . productId } "
data-position=" ${ index } ">
${ product . isPinned ? '<span class="badge">Featured</span>' : '' }
<img src=" ${ product . imageUrl } " alt=" ${ product . title } " />
<h3> ${ product . title } </h3>
<p>$ ${ product . price . toFixed ( 2 ) } </p>
</div>
` ). join ( '' );
this . attachEventHandlers ();
}
private attachEventHandlers () {
document . querySelectorAll ( '.product' ). forEach ( el => {
const productId = ( el as HTMLElement ). dataset . id ! ;
const position = parseInt (( el as HTMLElement ). dataset . position ! );
el . addEventListener ( 'click' , () => {
this . plpContext ?. trackClick ( productId , position );
});
});
}
}
// Usage
const categoryPage = new CategoryPage ();
await categoryPage . load ( 'summer-collection' );
React Integration
// hooks/usePLP.ts
import { useState , useEffect , useRef , useCallback } from 'react' ;
import { refine } from '@/lib/refine' ;
export function usePLP ( plpId : string ) {
const [ products , setProducts ] = useState < any []>([]);
const [ isLoading , setIsLoading ] = useState ( true );
const [ error , setError ] = useState < Error | null >( null );
const contextRef = useRef < any >( null );
useEffect (() => {
setIsLoading ( true );
refine . plp . get ( plpId )
. then ( plp => {
setProducts ( plp . results );
contextRef . current = refine . events . trackItemsServed ({
surface: 'category_page' ,
source: 'plp' ,
itemIds: plp . results . map ( p => p . productId ),
totalResults: plp . totalResults ,
metadata: { plpId }
});
})
. catch ( setError )
. finally (() => setIsLoading ( false ));
}, [ plpId ]);
const trackClick = useCallback (( productId : string , position : number ) => {
contextRef . current ?. trackClick ( productId , position );
}, []);
return { products , isLoading , error , trackClick };
}
// components/CategoryPage.tsx
import { usePLP } from '@/hooks/usePLP' ;
export function CategoryPage ({ slug } : { slug : string }) {
const { products , isLoading , error , trackClick } = usePLP ( slug );
if ( isLoading ) return < div > Loading... </ div > ;
if ( error ) return < div > Failed to load category </ div > ;
return (
< div className = "product-grid" >
{ products . map (( product , index ) => (
< div
key = { product . productId }
className = { `product ${ product . isPinned ? 'featured' : '' } ` }
onClick = { () => {
trackClick ( product . productId , index );
window . location . href = `/products/ ${ product . productId } ` ;
} }
>
{ product . isPinned && < span className = "badge" > Featured </ span > }
< img src = { product . imageUrl } alt = { product . title } />
< h3 > { product . title } </ h3 >
< p > $ { product . price . toFixed ( 2 ) } </ p >
</ div >
)) }
</ div >
);
}
Merchandising Best Practices
Pin strategically:
Pin high-margin products at positions 1-3
Pin new arrivals to increase visibility
Pin products with excess inventory to drive sales
Don’t over-pin:
Keep pinned products under 20% of total
Let the algorithm work for most positions
Rotate pinned products regularly
Next Steps
Event Tracking Track PLP interactions
Filters Apply filters to PLPs