# ProductSearch API The ProductSearch API gives extensions access to the native product search and fetching functionality of Shopify POS. The interface provides numerous functions to search for products by query, or to fetch the details of one or more products or product variants. ## ProductSearchApi ### ProductSearchApiContent ### fetchPaginatedProductVariantsWithProductId value: `(productId: number, paginationParams: PaginationParams) => Promise>` - PaginationParams: export interface PaginationParams { /** * Specifies the number of results to be returned in this page. The maximum number of items that will be returned is 50. */ first?: number; /** * Specifies the page cursor. Items after this cursor will be returned. */ afterCursor?: string; } - PaginatedResult: export interface PaginatedResult { /** * The items returned from the fetch. */ items: T[]; /** * The cursor of the last item. This can be used to fetch more results. * The format of this cursor may look different depending on if POS is fetching results from the remote API, or its local database. However, that should not affect its usage with the search functions. */ lastCursor?: string; /** * Whether or not there is another page of results that can be fetched. */ hasNextPage: boolean; } - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } Fetches a page of product variants associated with a product. ### fetchProductsWithIds value: `(productIds: number[]) => Promise>` - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } - MultipleResourceResult: export interface MultipleResourceResult { /** * The resources that were fetched using the IDs provided. */ fetchedResources: T[]; /** * The IDs for which a resource was not found. */ idsForResourcesNotFound: number[]; } Fetches multiple products' details. ### fetchProductVariantsWithIds value: `(productVariantIds: number[]) => Promise>` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } - MultipleResourceResult: export interface MultipleResourceResult { /** * The resources that were fetched using the IDs provided. */ fetchedResources: T[]; /** * The IDs for which a resource was not found. */ idsForResourcesNotFound: number[]; } Fetches multiple product variants' details. ### fetchProductVariantsWithProductId value: `(productId: number) => Promise` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } Fetches all product variants associated with a product. ### fetchProductVariantWithId value: `(productVariantId: number) => Promise` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } Fetches a single product variant's details. ### fetchProductWithId value: `(productId: number) => Promise` - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } Fetches a single product's details. ### searchProducts value: `(searchParams: ProductSearchParams) => Promise>` - PaginatedResult: export interface PaginatedResult { /** * The items returned from the fetch. */ items: T[]; /** * The cursor of the last item. This can be used to fetch more results. * The format of this cursor may look different depending on if POS is fetching results from the remote API, or its local database. However, that should not affect its usage with the search functions. */ lastCursor?: string; /** * Whether or not there is another page of results that can be fetched. */ hasNextPage: boolean; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } - ProductSearchParams: export interface ProductSearchParams extends PaginationParams { /** * The search term to be used to search for POS products. */ queryString?: string; /** * Specifies the order in which products should be sorted. When a `queryString` is provided, sortType will not have any effect, as the results will be returned in order by relevance to the `queryString`. */ sortType?: ProductSortType; } Search for products on the POS device. ### PaginationParams Base interface for pagination. ### afterCursor value: `string` Specifies the page cursor. Items after this cursor will be returned. ### first value: `number` Specifies the number of results to be returned in this page. The maximum number of items that will be returned is 50. ### PaginatedResult Contains a page of fetched results. ### hasNextPage value: `boolean` Whether or not there is another page of results that can be fetched. ### items value: `T[]` The items returned from the fetch. ### lastCursor value: `string` The cursor of the last item. This can be used to fetch more results. The format of this cursor may look different depending on if POS is fetching results from the remote API, or its local database. However, that should not affect its usage with the search functions. ### ProductVariant ### barcode value: `string` ### compareAtPrice value: `string` ### createdAt value: `string` ### displayName value: `string` ### hasInStockVariants value: `boolean` ### id value: `number` ### image value: `string` ### inventoryAtAllLocations value: `number` ### inventoryAtLocation value: `number` ### inventoryIsTracked value: `boolean` ### inventoryPolicy value: `ProductVariantInventoryPolicy` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - ProductVariantInventoryPolicy: 'DENY' | 'CONTINUE' - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } ### options value: `ProductVariantOption[]` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - ProductVariantOption: export interface ProductVariantOption { name: string; value: string; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } ### position value: `number` ### price value: `string` ### product value: `Product` - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } ### productId value: `number` ### sku value: `string` ### taxable value: `boolean` ### title value: `string` ### updatedAt value: `string` ### ProductVariantOption ### name value: `string` ### value value: `string` ### Product ### createdAt value: `string` ### description value: `string` ### descriptionHtml value: `string` ### featuredImage value: `string` ### hasInStockVariants value: `boolean` ### hasOnlyDefaultVariant value: `boolean` ### id value: `number` ### isGiftCard value: `boolean` ### maxVariantPrice value: `string` ### minVariantPrice value: `string` ### numVariants value: `number` ### onlineStoreUrl value: `string` ### options value: `ProductOption[]` - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } - ProductOption: export interface ProductOption { id: number; name: string; optionValues: string[]; productId: number; } ### productCategory value: `string` ### productType value: `string` ### tags value: `string[]` ### title value: `string` ### totalAvailableInventory value: `number` ### totalInventory value: `number` ### tracksInventory value: `boolean` ### updatedAt value: `string` ### variants value: `ProductVariant[]` - ProductVariant: export interface ProductVariant { id: number; createdAt: string; updatedAt: string; title: string; price: string; compareAtPrice?: string; taxable: boolean; sku?: string; barcode?: string; displayName: string; image?: string; inventoryIsTracked: boolean; inventoryAtLocation?: number; inventoryAtAllLocations?: number; inventoryPolicy: ProductVariantInventoryPolicy; hasInStockVariants?: boolean; options?: ProductVariantOption[]; product?: Product; productId: number; position: number; } - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } ### vendor value: `string` ### ProductOption ### id value: `number` ### name value: `string` ### optionValues value: `string[]` ### productId value: `number` ### MultipleResourceResult The result of a fetch function where the input is multiple IDs. This object contains the resources that were found, as well as an array of IDs specifying which IDs, if any, did not correspond to a resource. ### fetchedResources value: `T[]` The resources that were fetched using the IDs provided. ### idsForResourcesNotFound value: `number[]` The IDs for which a resource was not found. ### ProductSearchParams Interface for product search ### afterCursor value: `string` Specifies the page cursor. Items after this cursor will be returned. ### first value: `number` Specifies the number of results to be returned in this page. The maximum number of items that will be returned is 50. ### queryString value: `string` The search term to be used to search for POS products. ### sortType value: `ProductSortType` - Product: export interface Product { id: number; createdAt: string; updatedAt: string; title: string; description: string; descriptionHtml: string; featuredImage?: string; isGiftCard: boolean; tracksInventory: boolean; vendor: string; minVariantPrice: string; maxVariantPrice: string; productType: string; productCategory: string; tags: string[]; numVariants: number; totalAvailableInventory?: number; totalInventory: number; variants: ProductVariant[]; options: ProductOption[]; hasOnlyDefaultVariant: boolean; hasInStockVariants?: boolean; onlineStoreUrl?: string; } - ProductSortType: 'RECENTLY_ADDED' | 'RECENTLY_ADDED_ASCENDING' | 'ALPHABETICAL_A_TO_Z' | 'ALPHABETICAL_Z_TO_A' Specifies the order in which products should be sorted. When a `queryString` is provided, sortType will not have any effect, as the results will be returned in order by relevance to the `queryString`. ## Examples The ProductSearch API gives extensions access to the native product search and fetching functionality of Shopify POS. The interface provides numerous functions to search for products by query, or to fetch the details of one or more products or product variants. ```tsx import React, { useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); const search = async (queryString?: string) => { const results = await api.productSearch.searchProducts({queryString}) const data = results.items.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage } } } }) setData(data) } return ( } imageDisplayStrategy='always' data={data} /> ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const search = async (queryString?: string) => { const results = await api.productSearch.searchProducts({queryString}); const data = results.items.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage, }, }, }; }); list.updateProps({data}); }; const searchBar = root.createFragment(); searchBar.append( root.createComponent(SearchBar, { placeholder: 'Search products', onTextChange: search, onSearch: search, }), ); list.updateProps({listHeaderComponent: searchBar}); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProduct = async () => { const resultProduct = await api.productSearch.fetchProductWithId(1) if (resultProduct) { const listItem = { id: String(resultProduct.id), leftSide: { label: resultProduct.title, image: { source: resultProduct.featuredImage } } } setData([listItem]) } } fetchProduct(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProduct = async () => { const resultProduct = await api.productSearch.fetchProductWithId(1); if (resultProduct) { const listItem = { id: String(resultProduct.id), leftSide: { label: resultProduct.title, image: { source: resultProduct.featuredImage, }, }, }; list.updateProps({data: [listItem]}); } }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProduct(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProducts = async () => { const results = await api.productSearch.fetchProductsWithIds([1, 2, 3]); const data = results.fetchedResources.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage } } } }) console.log('IDs not found: ', results.idsForResourcesNotFound); setData(data) } fetchProducts(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProducts = async () => { const results = await api.productSearch.fetchProductsWithIds([1, 2, 3, 4]); const data = results.fetchedResources.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage, }, }, }; }); console.log('IDs not found: ', results.idsForResourcesNotFound); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProducts(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariant = async () => { const resultProductVariant = await api.productSearch.fetchProductVariantWithId(1); if (resultProductVariant) { const listItem = { id: String(resultProductVariant.id), leftSide: { label: resultProductVariant.title, image: { source: resultProductVariant.image } } } setData([listItem]) } } fetchProductVariant(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariant = async () => { const resultProductVariant = await api.productSearch.fetchProductVariantWithId(1); if (resultProductVariant) { const listItem = { id: String(resultProductVariant.id), leftSide: { label: resultProductVariant.title, image: { source: resultProductVariant.image, }, }, }; list.updateProps({data: [listItem]}); } }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariant(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariants = async () => { const results = await api.productSearch.fetchProductVariantsWithIds([1, 2, 3]); const data = results.fetchedResources.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image } } } }) console.log('IDs not found: ', results.idsForResourcesNotFound); setData(data) } fetchProductVariants(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariants = async () => { const results = await api.productSearch.fetchProductVariantsWithIds([ 1, 2, 3, 4, ]); const data = results.fetchedResources.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image, }, }, }; }); console.log('IDs not found: ', results.idsForResourcesNotFound); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariants(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariants = async () => { const results = await api.productSearch.fetchPaginatedProductVariantsWithProductId(1, {first: 10}); const data = results.items.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image } } } }) console.log('Cursor for next page: ', results.lastCursor); setData(data) } fetchProductVariants(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariants = async () => { const results = await api.productSearch.fetchPaginatedProductVariantsWithProductId(1, { first: 10, }); const data = results.items.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image, }, }, }; }); console.log('Cursor for next page: ', results.lastCursor); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariants(); }); ``` ## Examples The ProductSearch API gives extensions access to the native product search and fetching functionality of Shopify POS. The interface provides numerous functions to search for products by query, or to fetch the details of one or more products or product variants. ```tsx import React, { useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); const search = async (queryString?: string) => { const results = await api.productSearch.searchProducts({queryString}) const data = results.items.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage } } } }) setData(data) } return ( } imageDisplayStrategy='always' data={data} /> ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const search = async (queryString?: string) => { const results = await api.productSearch.searchProducts({queryString}); const data = results.items.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage, }, }, }; }); list.updateProps({data}); }; const searchBar = root.createFragment(); searchBar.append( root.createComponent(SearchBar, { placeholder: 'Search products', onTextChange: search, onSearch: search, }), ); list.updateProps({listHeaderComponent: searchBar}); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProduct = async () => { const resultProduct = await api.productSearch.fetchProductWithId(1) if (resultProduct) { const listItem = { id: String(resultProduct.id), leftSide: { label: resultProduct.title, image: { source: resultProduct.featuredImage } } } setData([listItem]) } } fetchProduct(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProduct = async () => { const resultProduct = await api.productSearch.fetchProductWithId(1); if (resultProduct) { const listItem = { id: String(resultProduct.id), leftSide: { label: resultProduct.title, image: { source: resultProduct.featuredImage, }, }, }; list.updateProps({data: [listItem]}); } }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProduct(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProducts = async () => { const results = await api.productSearch.fetchProductsWithIds([1, 2, 3]); const data = results.fetchedResources.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage } } } }) console.log('IDs not found: ', results.idsForResourcesNotFound); setData(data) } fetchProducts(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProducts = async () => { const results = await api.productSearch.fetchProductsWithIds([1, 2, 3, 4]); const data = results.fetchedResources.map((product): ListRow => { return { id: String(product.id), leftSide: { label: product.title, image: { source: product.featuredImage, }, }, }; }); console.log('IDs not found: ', results.idsForResourcesNotFound); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProducts(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariant = async () => { const resultProductVariant = await api.productSearch.fetchProductVariantWithId(1); if (resultProductVariant) { const listItem = { id: String(resultProductVariant.id), leftSide: { label: resultProductVariant.title, image: { source: resultProductVariant.image } } } setData([listItem]) } } fetchProductVariant(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariant = async () => { const resultProductVariant = await api.productSearch.fetchProductVariantWithId(1); if (resultProductVariant) { const listItem = { id: String(resultProductVariant.id), leftSide: { label: resultProductVariant.title, image: { source: resultProductVariant.image, }, }, }; list.updateProps({data: [listItem]}); } }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariant(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariants = async () => { const results = await api.productSearch.fetchProductVariantsWithIds([1, 2, 3]); const data = results.fetchedResources.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image } } } }) console.log('IDs not found: ', results.idsForResourcesNotFound); setData(data) } fetchProductVariants(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariants = async () => { const results = await api.productSearch.fetchProductVariantsWithIds([ 1, 2, 3, 4, ]); const data = results.fetchedResources.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image, }, }, }; }); console.log('IDs not found: ', results.idsForResourcesNotFound); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariants(); }); ``` ```tsx import React, { useEffect, useState } from 'react' import { Screen, List, Navigator, reactExtension, SearchBar, useApi, ListRow } from '@shopify/ui-extensions-react/point-of-sale'; const Modal = () => { const api = useApi<'pos.home.modal.render'>(); const [data, setData] = useState([]); useEffect(() => { const fetchProductVariants = async () => { const results = await api.productSearch.fetchPaginatedProductVariantsWithProductId(1, {first: 10}); const data = results.items.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image } } } }) console.log('Cursor for next page: ', results.lastCursor); setData(data) } fetchProductVariants(); }, []); return ( ) } export default reactExtension('pos.home.modal.render', () => ); ``` ```ts import { SearchBar, Screen, Navigator, extension, ListRow, List, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const list = root.createComponent(List, { imageDisplayStrategy: 'always', data: [], }); const fetchProductVariants = async () => { const results = await api.productSearch.fetchPaginatedProductVariantsWithProductId(1, { first: 10, }); const data = results.items.map((variant): ListRow => { return { id: String(variant.id), leftSide: { label: variant.title, image: { source: variant.image, }, }, }; }); console.log('Cursor for next page: ', results.lastCursor); list.updateProps({data}); }; const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); screen.append(list); const navigator = root.createComponent(Navigator); navigator.append(screen); root.append(navigator); fetchProductVariants(); }); ```