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
- 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