Product Search for B2B catalogs

Scope search results to a buyer's commercial context with the GraphQL productsSearch query, returning Products with resolved, negotiated Prices.

Ask about this Page
Copy for LLM
View as Markdown

After completing this page, you should be able to:

  • Use the GraphQL productsSearch query to fetch search results and product data in one step.

  • Separate scoping which variants match from resolving the price to display.

  • Account for variant-level matches when deciding which Product Variant data to display.

  • Scope a buyer's catalog using Channel, Customer Group, Store, and Product Selection.

In Design B2B catalogs you used Product Selections and Stores to control which Products a buyer sees. Each buyer also trades through a distribution Channel that carries their negotiated Price, which you configure in Configure B2B pricing; for search, what matters is that this Channel is the key you scope prices by. Search is where those two decisions become visible to the buyer: the same query, run for Horizon Hotels and for Pacific Property Group, must return each buyer's assortment at each buyer's price. This page shows how to express that scoping with Product Search. For the storefront mechanics of Product Search, such as faceting, filtering, and pagination, see the Implement product discovery and presentation path.

Use the GraphQL productsSearch query

Product Search is ID-first, so a raw search returns Product IDs and you fetch the data to render separately. The recommended way to do both in one request is the GraphQL productsSearch query: you search and select only the fields you need for the catalog page in a single call, which keeps payloads small.
The older REST data-integration path, the productProjectionParameters field on ProductSearchRequest (the ProductSearchProjectionParams type, with priceCurrency, priceChannel, and priceCustomerGroup), was deprecated in December 2025. Use the GraphQL productsSearch query for new B2B implementations. The productsSearch query is currently in public beta.

Two concerns: scope the match, then resolve the price

B2B search scoping is two separate mechanisms, and keeping them apart is the key to getting it right:

  1. Scope which variants match: constrain the search result to the buyer's context using exact expressions in the query on searchable price fields: variants.prices.channel, variants.prices.customerGroup, variants.prices.currencyCode, and variants.prices.country. Assortment scope uses stores, variants.stores, productSelections, and variants.productSelections.
  2. Resolve the price to display: select the variant's price(...) field in the GraphQL response, passing the same context as price-selection arguments (currency, country, channelId, customerGroupId). This returns the single Price the buyer should see, applying the same price selection the platform uses at Cart time.
Filtering decides which Products appear; the price field decides what number is shown for each. A query that filters by Channel but forgets to resolve the price would return the right Products with no price; one that resolves the price but forgets to filter would show every Product, priced for one buyer.

Worked example: Horizon Hotels' scoped catalog

Horizon Hotels trades through the horizon-hotels-pricing distribution Channel (id chan-hh-001) in AUD. The following productsSearch query returns laptops in their assortment, each with the negotiated Channel-scoped Price resolved for display.
{
  productsSearch(
    query: {
      and: [
        { fullText: { field: "name", value: "laptop", language: "en-AU" } }
        { exact: { field: "variants.prices.channel", value: "chan-hh-001" } }
        { exact: { field: "variants.prices.currencyCode", value: "AUD" } }
      ]
    }
    limit: 20
  ) {
    total
    results {
      product {
        masterData {
          current {
            masterVariant {
              sku
              price(currency: "AUD", channelId: "chan-hh-001") {
                value {
                  centAmount
                  currencyCode
                }
              }
            }
          }
        }
      }
    }
  }
}

Reading the query:

  • The and block combines a full-text match on name with two exact filters that scope the match to Products having an AUD Price on Horizon Hotels' Channel.
  • The price(currency: "AUD", channelId: "chan-hh-001") selection resolves the price to the buyer's negotiated Channel-scoped Price. The argument names are channelId and customerGroupId (the resource IDs), not the resource objects.
  • Selecting only sku and price keeps the response small because the ID-first design rewards requesting only what the catalog page renders.
The example selects the masterVariant to keep the query compact. In a production catalog, confirm that the variant you display is the one the buyer's search matched. Product Search can match only some Product Variants when you filter on variants.* fields, so use matching-variant handling when the catalog page must show the exact variant that carried the buyer-scoped Price.
To scope a different buyer, change the Channel id (and add customerGroupId if the buyer's pricing is Customer-Group-scoped) in both the filter and the price selection. Run the identical shape for Pacific Property Group's Channel and the same catalog returns that buyer's prices instead.

Connecting search to assortment

Filtering by price scopes pricing; it does not by itself limit the catalog to a buyer's permitted assortment. That limit comes from the Store and Product Selection scope you built in Module 3, exposed in the search index as stores, variants.stores, productSelections, and variants.productSelections. To return only Products available in a buyer's Store, add an exact filter on variants.stores (or stores) for that Store's id.
{ exact: { field: "variants.stores", value: "store-hh-au" } }
The mental model: Product Selections and Stores decide which Products are in scope; Channel and Customer Group decide the price; Product Search combines both into one buyer-specific catalog view.
Product Search must be activated for the Project before it returns results, and it deactivates automatically after 30 days with no search calls. If search requests fail with an ObjectNotFound error, the API is not enabled. Activate it in the Merchant Center under Settings > Project settings > Storefront Search, or with the Change Product Search Indexing Enabled update action.

Key takeaways

  • Use the GraphQL productsSearch query to search and fetch only the product fields you need in one call; the REST productProjectionParameters path is deprecated.
  • B2B scoping is two mechanisms: exact filters on variants.prices.* and assortment fields scope which variants match, and the variant price(...) field resolves what price to display.
  • Pass price context as currency, country, channelId, and customerGroupId arguments to the price field; they take resource IDs.
  • When filters target variants.* fields, make sure the catalog page displays the matched Product Variant, not an unrelated variant from the same Product.
  • Scope a buyer's assortment with variants.stores / stores and variants.productSelections / productSelections, the index fields backed by Module 3's Store and Product Selection design.
  • Product Search must be activated and stays active only while it is queried; an unconfigured API returns ObjectNotFound.

Test your knowledge