Skip to main content

Overview

Similar items recommendations find products that look like and relate to a specific anchor product. Use them on product detail pages for “You may also like” carousels.

Basic Usage

import { Refine } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

const recs = await refine.recs.similarItems({
  anchorId: 'sku_dress_001',
  topK: 8
});

recs.results.forEach(product => {
  console.log(`${product.title} - $${product.price}`);
});

Parameters

anchorId
string
required
The product ID to find similar items for. This is typically the currently viewed product.
topK
number
required
Number of recommendations to return. Recommended: 4-12 for carousels.
configId
string
Optional configuration ID from the dashboard. Applies preset filters and business rules.
visualWeight
number
default:"0.5"
Balance between visual similarity and other signals (0.0 to 1.0).
includeMetadata
boolean
default:"true"
Include full product metadata in results.
filters
Filter[]
Additional filters to apply. See Filters.

With Filters

Ensure recommended products are available and appropriately priced:
const recs = await refine.recs.similarItems({
  anchorId: 'sku_dress_001',
  topK: 8,
  visualWeight: 0.7,
  filters: [
    { field: 'stock', operator: 'gt', value: 0 },
    { field: 'price', operator: 'gte', value: 30 },
    { field: 'price', operator: 'lte', value: 150 },
    { field: 'productId', operator: 'ne', value: 'sku_dress_001' }  // Exclude anchor
  ]
});
The anchor product is automatically excluded from results. The explicit ne filter above is optional and shown for clarity.

Visual Weight Tuning

Adjust visualWeight based on product category:
// Fashion: High visual weight (appearance matters)
const fashionRecs = await refine.recs.similarItems({
  anchorId: 'sku_dress_001',
  topK: 8,
  visualWeight: 0.8
});

// Electronics: Lower visual weight (specs matter more)
const techRecs = await refine.recs.similarItems({
  anchorId: 'sku_laptop_001',
  topK: 8,
  visualWeight: 0.3
});

// Furniture: Balanced
const furnitureRecs = await refine.recs.similarItems({
  anchorId: 'sku_chair_001',
  topK: 8,
  visualWeight: 0.5
});

Tracking Similar Items

Track impressions and interactions:
const recs = await refine.recs.similarItems({
  anchorId: 'sku_001',
  topK: 8
});

const recsContext = refine.events.trackRecommendations(
  recs.serveId,
  recs.results,
  'product_page',
  'similar-items',
  { anchorId: 'sku_001' }
);

// User clicks a recommendation
function handleProductClick(productId: string, position: number) {
  recsContext.trackClick(productId, position);
  navigateToProduct(productId);
}

// User adds recommended item to cart
function handleAddToCart(productId: string) {
  recsContext.trackAddToCart(productId);
  addToCart(productId);
}

Complete Implementation

import { Refine, RefineNotFoundError } from '@refine-ai/sdk';

const refine = new Refine({
  apiKey: process.env.REFINE_API_KEY,
  organizationId: 'org_abc123',
  catalogId: 'cat_xyz789'
});

interface SimilarItemsCarouselProps {
  productId: string;
  maxItems?: number;
}

async function loadSimilarItems({ productId, maxItems = 8 }: SimilarItemsCarouselProps) {
  try {
    const recs = await refine.recs.similarItems({
      anchorId: productId,
      topK: maxItems,
      visualWeight: 0.7,
      filters: [
        { field: 'stock', operator: 'gt', value: 0 }
      ]
    });

    // Track recommendations
    const context = refine.events.trackRecommendations(
      recs.serveId,
      recs.results,
      'product_page',
      'similar-items',
      { anchorId: productId }
    );

    return {
      products: recs.results,
      trackClick: (id: string, pos: number) => context.trackClick(id, pos),
      trackAddToCart: (id: string) => context.trackAddToCart(id)
    };
  } catch (error) {
    if (error instanceof RefineNotFoundError) {
      // Product not in catalog, return empty
      console.warn(`Product ${productId} not found for similar items`);
      return { products: [], trackClick: () => {}, trackAddToCart: () => {} };
    }
    throw error;
  }
}

// Usage
const { products, trackClick, trackAddToCart } = await loadSimilarItems({
  productId: 'sku_dress_001'
});

// Render carousel
products.forEach((product, index) => {
  const element = createProductCard(product);
  element.onclick = () => {
    trackClick(product.productId, index);
    window.location.href = `/products/${product.productId}`;
  };
  element.querySelector('.add-to-cart').onclick = (e) => {
    e.stopPropagation();
    trackAddToCart(product.productId);
  };
});

Cross-Sell from Cart

Get similar items for multiple products in the cart:
async function getCartCrossSells(cartItems: string[]) {
  // Get similar items for the first few cart items
  const anchors = cartItems.slice(0, 3);
  
  const allRecs = await Promise.all(
    anchors.map(anchorId => 
      refine.recs.similarItems({
        anchorId,
        topK: 4,
        filters: [
          { field: 'productId', operator: 'nin', value: cartItems },
          { field: 'stock', operator: 'gt', value: 0 }
        ]
      })
    )
  );

  // Deduplicate and combine
  const seen = new Set<string>();
  const combined = allRecs.flatMap(r => r.results).filter(product => {
    if (seen.has(product.productId)) return false;
    seen.add(product.productId);
    return true;
  });

  return combined.slice(0, 8);
}

Error Handling

import { RefineNotFoundError, RefineValidationError } from '@refine-ai/sdk';

async function getSimilarItems(productId: string) {
  try {
    return await refine.recs.similarItems({
      anchorId: productId,
      topK: 8
    });
  } catch (error) {
    if (error instanceof RefineNotFoundError) {
      // Anchor product doesn't exist in catalog
      // Fall back to popular items
      return await refine.recs.forVisitor({
        configId: 'popular_items',
        topK: 8
      });
    }
    
    if (error instanceof RefineValidationError) {
      console.error('Invalid request:', error.message);
      return { results: [] };
    }
    
    throw error;
  }
}

Next Steps