Products
List Products
const products = await client.products.list({
page: 1,
limit: 25,
expand: ['variants', 'media', 'categories'],
});
Filtering Products
Use flat param keys — the SDK wraps them in q[...] automatically:
const products = await client.products.list({
name_cont: 'shirt', // Name contains
price_gte: 20, // Min price
price_lte: 100, // Max price
with_option_value_ids: ['optval_abc', 'optval_def'], // By option values
in_stock: true, // In stock only
search: 'blue shirt', // Full-text search
});
Sorting Products
Pass sort with one of the supported values. Prefix with - for descending order:
const products = await client.products.list({
sort: 'price', // price, -price, best_selling,
// name, -name, -available_on, available_on
});
Field Selection
Request only specific fields to reduce payload size:
const products = await client.products.list({
fields: ['name', 'slug', 'price', 'display_price'],
});
const product = await client.products.get('spree-tote', {
fields: ['name', 'slug', 'price'],
expand: ['media'], // expanded associations are always included
});
id is always included. Omit fields to return all fields.
Nested Expand
Use dot notation to expand nested associations (up to 4 levels):
// Expand variants with their images in one request
const product = await client.products.get('spree-tote', {
expand: ['variants.media', 'option_types'],
});
// Access nested data
product.variants.forEach(variant => {
console.log(variant.media); // Media are included
});
Get a Product
Fetch a single product by slug or prefix ID:
const product = await client.products.get('spree-tote', {
expand: ['variants', 'media'],
});
Prior Price (EU Omnibus Directive)
When a product is on sale, EU regulations require displaying the lowest price in the last 30 days. Use the prior_price expand to fetch this:
const product = await client.products.get('spree-tote', {
expand: ['prior_price'],
});
if (product.prior_price) {
console.log(product.prior_price.amount); // "9.99"
console.log(product.prior_price.display_amount); // "$9.99"
console.log(product.prior_price.currency); // "USD"
console.log(product.prior_price.recorded_at); // "2026-03-10T..."
}
Prior price is only relevant on product detail pages (PDP), not on listings. It returns null if no price history exists within the 30-day window. Price history tracking can be disabled globally via Spree::Config[:track_price_history] = false.
Product Filters
Get available filters (price range, availability, options, categories) and sort options for building filter UIs:
const filters = await client.products.filters({
category_id: 'ctg_abc123', // Optional: scope filters to a category
});
// Response:
// {
// filters: [
// { id: 'price', type: 'price_range', min: 9.99, max: 199.99, currency: 'USD' },
// { id: 'availability', type: 'availability', options: [{ id: 'in_stock', count: 42 }, ...] },
// { id: 'opttype_abc', type: 'option', name: 'color', presentation: 'Color', options: [...] },
// { id: 'categories', type: 'category', options: [{ id: 'ctg_abc', name: 'Shirts', permalink: '...', count: 12 }] },
// ],
// sort_options: [{ id: 'manual' }, { id: 'price' }, { id: 'best_selling' }, ...],
// default_sort: 'manual',
// total_count: 85,
// }
Categories
List Categories
const categories = await client.categories.list({
depth_eq: 1, // Top-level categories only
});
Get a Category
Fetch by ID or permalink:
const category = await client.categories.get('clothing/shirts', {
expand: ['ancestors', 'children'], // For breadcrumbs and subcategories
});
List Products in a Category
const categoryProducts = await client.categories.products.list('clothing/shirts', {
page: 1,
limit: 12,
expand: ['media', 'default_variant'],
});
Geography
// List countries available for checkout
const { data: countries } = await client.countries.list();
// Get country by ISO code (includes states)
const usa = await client.countries.get('US');
console.log(usa.states); // Array of states