--- title: Scanner API description: >- The Scanner API provides access to barcode and QR code scanning functionality on POS devices, allowing you to subscribe to scan events, monitor available scanner sources, and process scanned data through subscription callbacks. The API enables integration with device cameras, external scanners, and embedded scanning hardware. api_version: 2024-04 api_name: pos-ui-extensions source_url: html: >- https://shopify.dev/docs/api/pos-ui-extensions/latest/target-apis/platform-apis/scanner-api md: >- https://shopify.dev/docs/api/pos-ui-extensions/latest/target-apis/platform-apis/scanner-api.md --- # Scanner API **Requires pos.home.modal.render:** The Scanner API provides access to barcode and QR code scanning functionality on POS devices, allowing you to subscribe to scan events, monitor available scanner sources, and process scanned data through subscription callbacks. The API enables integration with device cameras, external scanners, and embedded scanning hardware. #### Use cases * **Barcode scanning:** Implement barcode scanning for product lookup or inventory management. * **QR scanning:** Build QR code scanning features for customer engagement or loyalty programs. * **Custom workflows:** Create custom scanning workflows that process scan data. * **Real-time feedback:** Implement real-time scanning feedback with immediate processing. ## Support Targets (1) ### Supported targets * [pos.​home.​modal.​render](https://shopify.dev/docs/api/pos-ui-extensions/2024-04/targets/home-screen#home-screen-action-modal-) ## ScannerApi The `ScannerApi` object provides access to scanning functionality and scanner source information. Access these properties through `api.scanner` to monitor scan events and available scanner sources. * **scannerDataSubscribable** **RemoteSubscribable\** **required** Subscribe to scan events to receive barcode and QR code data when scanned. Supports one subscription at a time. Use for receiving real-time scan results. * **scannerSourcesSubscribable** **RemoteSubscribable\** **required** Subscribe to changes in available scanner sources on the device. Supports one subscription at a time. Use to monitor which scanners are available (camera, external, or embedded). ### ScannerSubscriptionResult Represents the data from a scanner event. Contains the scanned string data and the hardware source that captured the scan. * data The string data from the last scanner event received. Contains the scanned barcode, QR code, or other scannable data. Returns \`undefined\` when no scan data is available. Use to process scanned content and implement scan-based business logic. ```ts string ``` * source The scanning source from which the scan event came. Returns one of the following scanner types: ```ts ScannerSource ``` ```ts export interface ScannerSubscriptionResult { data?: string; source?: ScannerSource; } ``` ### ScannerSource The scanner source the POS device supports. ```ts 'camera' | 'external' | 'embedded' ``` Examples ### Examples * #### Listen for barcode scan events ##### Description Subscribe to scanner events to capture barcode scans in real time. This example shows how to listen for scan events and display the scanned data, enabling barcode-based workflows like product lookup, inventory management, or custom scanning functionality. ##### 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 || ''}`); }); }); ``` * #### Monitor available scanner sources ##### Description Track which scanner sources are available on the device. This example demonstrates subscribing to scanner source changes to detect when hardware scanners or camera scanners become available or unavailable, allowing you to adapt your scanning UI accordingly. ##### 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}`); }); }); ``` * #### Render different UIs based on scanner type ##### Description Conditionally display different scanning interfaces based on available scanner sources. This example shows how to detect the scanner type and render appropriate UI components, showing camera scanner UI when only camera is available and hardware scanner UI when physical scanners are connected. ##### 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); } }); }); ``` * #### Use a hardware scanner ##### Description Implement hardware scanner integration to capture barcode scans from connected devices. This example shows how to set up hardware scanner support and process scan events, providing a seamless scanning experience with physical barcode scanners. ##### 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 || ''}`); }); }); ``` ## Best practices * **Implement proper post-scan workflow management:** After successful scanning, dismiss the full-screen camera view and display a secondary screen showcasing the intended outcome. Use the Toast API (`toast.show()`) with concise messages like "Item scanned" to confirm successful operations and consider altering screen content to signal completion. * **Optimize camera view layout for multitasking:** Adjust the camera scanner UI to display the camera view on part of the screen while dedicating remaining space to other components. This approach is particularly useful for tasks like inventory management where users need to see both camera input and related information simultaneously. * **Write effective banner content:** Keep banner messages concise with one to two short sentences maximum. Make banners dismissible unless they contain critical information or important steps merchants need to take. Use clear, actionable language that guides users toward successful scanning. * **Coordinate with Toast API for success feedback:** Use the Toast API (`toast.show()`) for short confirmation messages after successful scans. Keep toast messages to three to four words maximum, avoid using them for error messages, and write them in noun + verb pattern. For example, "Item scanned" instead of "Your item has been scanned and added to your inventory count!". ![](https://cdn.shopify.com/shopifycloud/shopify-dev/development/assets/assets/images/templated-apis-screenshots/pos-ui-extensions/2024-04/camera-scanner-best-practice-DFgOqBYf.png) ## Limitations * Scanner API provides access to scan events from all supported scanner types (camera, external, and embedded scanners) but doesn't control scanner hardware directly or modify scanning behavior. * `RemoteSubscribable` supports only one subscription at a time. Use `makeStatefulSubscribable` if you need multiple components to subscribe to scanner events simultaneously. * The API provides scan data as strings—parsing, validation, or processing of specific barcode formats requires additional implementation within your extension. * Scanner hardware availability depends on device capabilities and permissions—not all POS devices support all scanner types, and camera scanning requires explicit user permissions.