ProductService is found in Poq.Shopify.Services namespace. ProductService service uses only Shopify Storefront API to obtain data and can be used for feedless implementation of BFC. It requires two dependencies to be used in your backend. All required dependencies should be registered using IoC:

services.AddScoped<IShopifyProductClient, ShopifyProductClient>();
services.AddScoped<IProductMapper, ProductMapper>();
services.AddScoped<IProductService, ProductService>();

Explanation of services

  • IShopifyProductClient is available as part of Poq Shopify nuget package. No further action is required than properly registering the service.

  • IProductService with implementation ProductService

    Product Service searches for products by their ids using Shopify Storefront Api. Service functionality and products mapping can be modified by providing custom implementation of IProductMapper. ProducService has generic overloads that allow customization of Graphql query and contract.

  • IProductMapper with implementation ProductMapper is responsible of mapping product listings, filters and pagination. It is used to map filters to shopify contract.

Here in an example of overriding default handling in IProductMapper.

services.AddScoped<IProductMapper, MyProductMapper>();
public class MyProductMapper : IProductMapper
private readonly string _onlineStoreBaseUrl;
public ProductMapper(string onlineStoreBaseUrl)
_onlineStoreBaseUrl = onlineStoreBaseUrl;
public Product MapProduct(string appIdentifier, ProductListing productListing)
return new Product(
productListing.Variants.Variants.Select(v => MapVariant(productListing, v.ListingVariant)),
Meta = new ProductMeta()
ClientId = productListing.Id,
DefaultVariantId = productListing.Variants.Variants.FirstOrDefault()?.ListingVariant.Id,
WebUrl = $"{_onlineStoreBaseUrl}/products/{productListing.Handle}",
Promotion = new Promotion(),
Review = new Review(),
protected virtual IEnumerable<Form>? MapForms(ProductListing productListing)
var result = new List<Form>();
foreach (var option in productListing.Options)
result.Add(new Form()
Name = MapFormName(option.Name),
Variations = option.Values.Select(optionValue => new FormVariant() { Id = $"{MapFormName(option.Name)}{optionValue}", Value = optionValue }).ToArray(),
return result;
protected virtual ProductVariant MapVariant(ProductListing productListing, ProductListingVariant variant)
var images = new List<string>() { variant.Image.Src };
images.AddRange(productListing.ImageEdges?.ImageEdges?.Select(i => i.Image.Url) ?? new List<string>());
images = images.Distinct().ToList();
return new ProductVariant()
Id = variant.Id,
ListingId = productListing.Id,
Images = new Images(images),
Meta = new VariantMeta()
Barcode = string.IsNullOrWhiteSpace(variant.Barcode) ? null : variant.Barcode,
DefaultCurrency = variant.Price.CurrencyCode
Name = productListing.Title,
Prices = new Dictionary<string, Price>() { { variant.Price.CurrencyCode, new Price(variant.Price.Amount ?? 0) { Was = variant.CompareAtPrice?.Amount } } },
Forms = MapVariantForms(variant),
Stock = new Stock()
Available = variant.AvailableForSale,
MaxOrderableQuantity = variant.QuantityAvailable,
LowOnStock = variant.QuantityAvailable < 5,
Quantity = variant.QuantityAvailable,


  • GetProductsAsync obtains products from Shopify storefront API by provided productIds. This method is used to obtain products by listingId since Shopify SDK considers productId and listingId to be the same.

  • GetProductsByVariantIdsAsync obtains products from Shopify storefront API by provided variantIds.