--- title: Scanner API description: > The Scanner API enables an extension to access scanner data and available scanning sources supported by the device. #### Supporting targets - [pos.home.modal.render](/docs/api/pos-ui-extensions/targets/smart-grid/pos-home-modal-render) - [pos.purchase.post.action.render](/docs/api/pos-ui-extensions/targets/post-purchase/pos-purchase-post-action-render) - [pos.product-details.action.render](/docs/api/pos-ui-extensions/targets/product-details/pos-product-details-action-render) - [pos.order-details.action.render](/docs/api/pos-ui-extensions/targets/order-details/pos-order-details-action-render) - [pos.draft-order-details.action.render](/docs/api/pos-ui-extensions/targets/draft-order-details/pos-draft-order-details-action-render) - [pos.customer-details.action.menu-item.render](/docs/api/pos-ui-extensions/targets/customer-details/pos-customer-details-action-menu-item-render) api_version: 2025-04 api_name: pos-ui-extensions source_url: html: 'https://shopify.dev/docs/api/pos-ui-extensions/2025-04/apis/scanner-api' md: 'https://shopify.dev/docs/api/pos-ui-extensions/2025-04/apis/scanner-api.md' --- # Scanner APIAPIs The Scanner API enables an extension to access scanner data and available scanning sources supported by the device. #### Supporting targets * [pos.home.modal.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/smart-grid/pos-home-modal-render) * [pos.purchase.post.action.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/post-purchase/pos-purchase-post-action-render) * [pos.product-details.action.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/product-details/pos-product-details-action-render) * [pos.order-details.action.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/order-details/pos-order-details-action-render) * [pos.draft-order-details.action.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/draft-order-details/pos-draft-order-details-action-render) * [pos.customer-details.action.menu-item.render](https://shopify.dev/docs/api/pos-ui-extensions/targets/customer-details/pos-customer-details-action-menu-item-render) ## ScannerApi * scannerDataSubscribable RemoteSubscribable\ required Creates a subscription to scan events Provides an initial value and a callback to subscribe to value changes. Currently supports only one subscription. You can utilize `makeStatefulSubscribable` on a `RemoteSubscribable` to implement multiple subscriptions. Using `makeStatefulSubscribable` or the corresponding hooks counts as a subscription. * scannerSourcesSubscribable RemoteSubscribable\ required Creates a subscription to the scanning sources available on the POS device. Provides an initial value and a callback to subscribe to value changes. Currently supports only one subscription. You can utilize `makeStatefulSubscribable` on a `RemoteSubscribable` to implement multiple subscriptions. Using `makeStatefulSubscribable` or the corresponding hooks counts as a subscription. ### ScannerSubscriptionResult * data The string data from the last scanner event received. ```ts string ``` * source The scanning source from which the scan event came. ```ts ScannerSource ``` ```ts export interface ScannerSubscriptionResult { /** The string data from the last scanner event received. */ data?: string; /** The scanning source from which the scan event came. */ source?: ScannerSource; } ``` ### ScannerSource The scanner source the POS device supports. ```ts 'camera' | 'external' | 'embedded' ``` ## Examples Examples of receiving updates from the Scanner API Subscribe to scan event updates Receiving updates on available scanner sources ### Examples * #### Subscribe to scan event updates ##### React ```tsx import React from 'react'; import { Navigator, Screen, Stack, Text, useScannerDataSubscription, reactExtension, } from '@shopify/ui-extensions-react/point-of-sale'; const SmartGridModal = () => { const {data, source} = useScannerDataSubscription(); return ( {`Scanned data: ${data || ''} with ${source || ''}`} ); }; export default reactExtension('pos.home.modal.render', () => ( )); ``` ##### TS ```ts import { Navigator, Screen, Stack, Text, extension, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const dataText = root.createComponent('Text', null, 'Scanned data: '); const sourceText = root.createComponent( 'Text', null, 'Scanned data source: ', ); const stack1 = root.createComponent(Stack, { direction: 'horizontal', }); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); const navigator = root.createComponent(Navigator); stack1.append(dataText); stack1.append(sourceText); screen.append(stack1); navigator.append(screen); root.append(navigator); api.scanner.scannerDataSubscribable.subscribe((data, source) => { dataText.replaceChildren(`Scanned data: ${data || ''}`); sourceText.updateProps(`Scanned data source: ${source || ''}`); }); }); ``` * #### Receiving updates on available scanner sources ##### React ```tsx import React from 'react'; import { Navigator, Screen, Stack, Text, useScannerSourcesSubscription, reactExtension, } from '@shopify/ui-extensions-react/point-of-sale'; const SmartGridModal = () => { const scannerSources = useScannerSourcesSubscription(); return ( {`Available scanner sources: ${scannerSources}`} ); }; export default reactExtension('pos.home.modal.render', () => ( )); ``` ##### TS ```ts import { Navigator, Screen, Stack, Text, extension, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const scannerSourcesText = root.createComponent( 'Text', null, 'Available scanner sources: ', ); const stack1 = root.createComponent(Stack, { direction: 'horizontal', }); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); const navigator = root.createComponent(Navigator); stack1.append(scannerSourcesText); screen.append(stack1); navigator.append(screen); root.append(navigator); api.scanner.scannerSourcesSubscribable.subscribe((sources) => { scannerSourcesText.replaceChildren(`Available scanner sources: ${sources}`); }); }); ``` ### Use cases Hardware scanner example In this example, assuming a physical scanner is connected to the POS, any scans performed when ui extensions are in use will automatically add the product to the cart if the data exists on the shop. Conditional scanner source rendering example There might be situations where a developer needs to conditionally render UI elements based on the available scanning sources of the device on which the extension is installed. For example, an extension could be designed for full-screen camera scanning, but a device like POS GO does not have a camera. In such cases, it would be necessary to avoid rendering the camera scanner component and instead create a UI that supports embedded scanning. ### Examples * #### Hardware scanner example ##### Description In this example, assuming a physical scanner is connected to the POS, any scans performed when ui extensions are in use will automatically add the product to the cart if the data exists on the shop. ##### React ```tsx import React from 'react'; import { Navigator, Screen, Stack, Text, useScannerDataSubscription, useApi, reactExtension, } from '@shopify/ui-extensions-react/point-of-sale'; const SmartGridModal = () => { const api = useApi<'pos.home.modal.render'>(); const {data} = useScannerDataSubscription(); return ( {`Scanned data: ${data || ''}`} ); }; export default reactExtension('pos.home.modal.render', () => ( )); ``` ##### TS ```ts import React from 'react'; import { Navigator, Screen, Stack, Text, extension, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const text = root.createComponent('Text', null, 'Scanned data: '); const stack = root.createComponent(Stack, { direction: 'horizontal', }); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); const navigator = root.createComponent(Navigator); stack.append(text); screen.append(stack); navigator.append(screen); root.append(navigator); api.scanner.scannerDataSubscribable.subscribe((data) => { text.replaceChildren(`Scanned data: ${data || ''}`); }); }); ``` * #### Conditional scanner source rendering example ##### Description There might be situations where a developer needs to conditionally render UI elements based on the available scanning sources of the device on which the extension is installed. For example, an extension could be designed for full-screen camera scanning, but a device like POS GO does not have a camera. In such cases, it would be necessary to avoid rendering the camera scanner component and instead create a UI that supports embedded scanning. ##### React ```tsx import React from 'react'; import { CameraScanner, Navigator, Screen, Stack, Text, useScannerDataSubscription, useScannerSourcesSubscription, reactExtension, } from '@shopify/ui-extensions-react/point-of-sale'; const SmartGridModal = () => { const {data, source} = useScannerDataSubscription(); const availableScanners = useScannerSourcesSubscription(); const hasCameraScanner = availableScanners.includes('camera'); return ( {hasCameraScanner ? ( ) : ( {`Scanned data: ${data || ''}`} {`Scanned data source: ${source || ''}`} )} ); }; export default reactExtension('pos.home.modal.render', () => ( )); ``` ##### TS ```ts import { CameraScanner, Navigator, Screen, Stack, Text, extension, } from '@shopify/ui-extensions/point-of-sale'; export default extension('pos.home.modal.render', (root, api) => { const dataText = root.createComponent('Text', null, 'Scanned data: '); const sourceText = root.createComponent( 'Text', null, 'Scanned data source: ', ); const cameraScanner = root.createComponent(CameraScanner); const stack1 = root.createComponent(Stack, { direction: 'horizontal', }); const stack2 = root.createComponent(Stack, { direction: 'vertical', alignment: 'space-evenly', }); const screen = root.createComponent(Screen, { title: 'Home', name: 'Home', }); const navigator = root.createComponent(Navigator); screen.append(stack1); navigator.append(screen); root.append(navigator); api.scanner.scannerDataSubscribable.subscribe((data, source) => { dataText.replaceChildren(`Scanned data: ${data || ''}`); sourceText.replaceChildren(`Scanned data source: ${source || ''}`); }); api.scanner.scannerSourcesSubscribable.subscribe((sources) => { // Clear previous children to avoid duplicate appending stack1.children = []; stack2.children = []; if (sources.include('camera')) { stack1.append(cameraScanner); } else { stack2.append(dataText); stack2.append(sourceText); stack1.append(stack2); } }); }); ```