From b2c
Provides best practices for querying products, orders, customers, and system objects in B2C Commerce using index-backed APIs like ProductSearchModel, OrderMgr to boost search performance and avoid database pitfalls.
npx claudepluginhub salesforcecommercecloud/b2c-developer-tooling --plugin b2cThis skill uses the workspace's default tool permissions.
Efficient data querying is critical for storefront performance and job stability. B2C Commerce provides index-backed search APIs and database query APIs—choosing the right one for each use case avoids performance problems.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Efficient data querying is critical for storefront performance and job stability. B2C Commerce provides index-backed search APIs and database query APIs—choosing the right one for each use case avoids performance problems.
Use ProductSearchModel for all storefront product searches. It is index-backed and designed for high-traffic pages.
var ProductSearchModel = require('dw/catalog/ProductSearchModel');
var psm = new ProductSearchModel();
psm.setCategoryID('electronics');
psm.setOrderableProductsOnly(true); // Only in-stock products
psm.setSearchPhrase('laptop');
psm.search();
var hits = psm.getProductSearchHits();
while (hits.hasNext()) {
var hit = hits.next();
var productID = hit.productID;
var minPrice = hit.minPrice;
var maxPrice = hit.maxPrice;
}
hits.close();
Always page results—never load the full result set:
var ProductSearchModel = require('dw/catalog/ProductSearchModel');
var PagingModel = require('dw/web/PagingModel');
var psm = new ProductSearchModel();
psm.setCategoryID('mens-clothing');
psm.setOrderableProductsOnly(true);
psm.search();
var pagingModel = new PagingModel(psm.getProductSearchHits(), psm.count);
pagingModel.setPageSize(12);
pagingModel.setStart(0); // page offset
var pageElements = pagingModel.pageElements;
while (pageElements.hasNext()) {
var hit = pageElements.next();
// Render product tile
}
Use ProductSearchHit methods instead of loading full product objects:
// GOOD: Get variation info from the search hit (index-backed)
var representedColors = hit.getRepresentedVariationValues('color');
var representedIDs = hit.getRepresentedProductIDs();
var minPrice = hit.getMinPrice();
var maxPrice = hit.getMaxPrice();
// BAD: Loading the full product and iterating variants (database-intensive)
var product = hit.product;
var variants = product.getVariants(); // Expensive!
var priceModel = product.getPriceModel(); // Expensive!
var psm = new ProductSearchModel();
psm.setCategoryID('shoes');
psm.addRefinementValues('color', 'blue');
psm.addRefinementValues('size', '10');
psm.setPriceMin(50);
psm.setPriceMax(200);
psm.search();
// Get available refinement values for the current result set
var refinements = psm.getRefinements();
var colorValues = refinements.getNextLevelRefinementValues(
refinements.getRefinementDefinitionByName('color')
);
| Method | Description |
|---|---|
search() | Execute the search |
setCategoryID(id) | Filter by category |
setSearchPhrase(phrase) | Set search keywords |
setOrderableProductsOnly(flag) | Exclude out-of-stock |
addRefinementValues(name, value) | Add refinement filter |
setPriceMin(price) / setPriceMax(price) | Price range filter |
setSortingRule(rule) | Set sorting rule |
getProductSearchHits() | Get result iterator |
getRefinements() | Get available refinements |
count | Total result count |
Use searchOrders for index-backed order lookups and queryOrders for database queries:
var OrderMgr = require('dw/order/OrderMgr');
var Order = require('dw/order/Order');
// Index-backed search (preferred for common lookups)
var orders = OrderMgr.searchOrders(
'customerEmail = {0} AND status != {1}',
'creationDate desc',
'customer@example.com',
Order.ORDER_STATUS_FAILED
);
while (orders.hasNext()) {
var order = orders.next();
// Process order
}
orders.close(); // Always close iterators
var OrderMgr = require('dw/order/OrderMgr');
var Calendar = require('dw/util/Calendar');
var Order = require('dw/order/Order');
var startDate = new Calendar();
startDate.add(Calendar.DAY_OF_YEAR, -7);
var orders = OrderMgr.searchOrders(
'creationDate >= {0} AND status = {1}',
'creationDate desc',
startDate.time,
Order.ORDER_STATUS_NEW
);
while (orders.hasNext()) {
var order = orders.next();
// Process
}
orders.close();
| Aspect | searchOrders | queryOrders |
|---|---|---|
| Backing | Search index | Database |
| Performance | Fast for indexed fields | Slower, full table scan possible |
| Use when | Querying indexed attributes (status, email, dates) | Querying non-indexed or custom attributes |
| Result limit | Up to 1000 hits | No hard limit (but use paging) |
Prefer searchOrders for storefront and high-traffic code paths. Use queryOrders only when you need to query attributes not available in the search index.
Use searchProfiles for index-backed searches and processProfiles for batch processing in jobs:
var CustomerMgr = require('dw/customer/CustomerMgr');
// Index-backed search (storefront use)
var profiles = CustomerMgr.searchProfiles(
'email = {0}',
'lastLoginTime desc',
'customer@example.com'
);
while (profiles.hasNext()) {
var profile = profiles.next();
// Process profile
}
profiles.close();
Use processProfiles for jobs that need to iterate over many profiles—it has optimized memory management:
var CustomerMgr = require('dw/customer/CustomerMgr');
function processProfile(profile) {
// Process each profile individually
// Memory is managed automatically
}
// Process all profiles matching the query
CustomerMgr.processProfiles('gender = {0}', processProfile, 1);
Important: processProfiles replaces the older queryProfiles and SystemObjectMgr.querySystemObjects for customer data. It uses the full-text search service with better performance and memory characteristics.
*, %, +) are filtered from queries and replaced by spacesLIKE and ILIKE execute as full-text queries (match whole words, not substrings)LIKE is case-insensitiveAND and OR in the same query degrades performancea > b) impact performanceFor querying system objects other than customers (e.g., SitePreferences, catalogs):
var SystemObjectMgr = require('dw/object/SystemObjectMgr');
// Query system objects
var results = SystemObjectMgr.querySystemObjects(
'Profile',
'custom.loyaltyTier = {0}',
'lastLoginTime desc',
'Gold'
);
while (results.hasNext()) {
var obj = results.next();
// Process
}
results.close();
Note: For customer profiles specifically, prefer CustomerMgr.searchProfiles or CustomerMgr.processProfiles over SystemObjectMgr.querySystemObjects—they use the search index and perform significantly better.
These APIs hit the database directly and are expensive on high-traffic pages. Replace them with index-friendly alternatives. See Performance-Critical APIs for the complete list with impact details.
| Avoid (Database-Intensive) | Use Instead (Index-Friendly) |
|---|---|
Category.getProducts() / getOnlineProducts() | ProductSearchModel.setCategoryID() |
ProductMgr.queryAllSiteProducts() | ProductSearchModel.search() |
Product.getVariants() / getVariationModel() | ProductSearchHit methods |
Product.getPriceModel() (in loops) | ProductSearchHit.getMinPrice() / getMaxPrice() |
CustomerMgr.queryProfiles() | CustomerMgr.searchProfiles() or processProfiles() |
results.close())PagingModel or limit result counts; never load unbounded result setsProductSearchModel, searchOrders, searchProfiles for storefront pagesprocessProfiles for batch customer operations in jobs (optimized memory)setOrderableProductsOnly(true) — to filter unavailable products at the search levelProductSearchHit methods insteadqueryAllSiteProducts() on storefront pages — it bypasses the search indexprocessProfiles over queryProfiles for large customer data sets