Skip to content

Commit f904df3

Browse files
authored
Merge pull request #3645 from Shopify/20646-camera-api-docs
20646: Add camera API docs
2 parents f5b0101 + 78ee43a commit f904df3

File tree

4 files changed

+185
-0
lines changed

4 files changed

+185
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
2+
import {generateJsxCodeBlock} from '../helpers/generateCodeBlock';
3+
4+
const generateJsxCodeBlockForCameraApi = (title: string, fileName: string) =>
5+
generateJsxCodeBlock(title, 'camera-api', fileName);
6+
7+
const data: ReferenceEntityTemplateSchema = {
8+
name: 'Camera API',
9+
description:
10+
"The Camera API provides access to the device's camera, enabling photo capture directly within POS UI extensions. The API requests camera permissions if not already enabled, opens the native camera interface, and returns the image data including dimensions, file size, and base64 string for immediate display or server upload.",
11+
isVisualComponent: false,
12+
type: 'APIs',
13+
definitions: [
14+
{
15+
title: 'CameraApi',
16+
description:
17+
'The `CameraApi` object provides methods for capturing photos using the device camera. Access these methods through `shopify.camera` to take photos and retrieve image data with metadata.',
18+
type: 'CameraApiContent',
19+
},
20+
],
21+
category: 'Target APIs',
22+
subCategory: 'Standard APIs',
23+
subSections: [
24+
{
25+
type: 'Generic',
26+
anchorLink: 'best-practices',
27+
title: 'Best practices',
28+
sectionContent: `
29+
- **Optimize image quality:** Use appropriate quality and size settings to balance image quality with file size and upload performance.
30+
- **Provide feedback:** Show loading states while processing images and clear success/error messages after capture.
31+
- **Handle errors gracefully:** Account for scenarios where users cancel, camera is unavailable, or permissions are denied.
32+
`,
33+
},
34+
{
35+
type: 'Generic',
36+
anchorLink: 'limitations',
37+
title: 'Limitations',
38+
sectionContent: `
39+
- Camera functionality requires the device to have a camera and appropriate permissions granted by the user.
40+
- Only one camera operation can be in progress at a time. Attempting to call \`takePhoto()\` while a capture is already in progress will result in a rejected promise.
41+
- Base64 strings can be memory-intensive for large images. Use appropriate \`maxWidth\`, \`maxHeight\`, and \`quality\` settings to optimize performance.
42+
- The \`facingMode\` parameter may not behave consistently on all Android devices, because camera-facing behavior varies across manufacturers. If a requested mode isn't supported, the rear-facing camera is used by default, and users can still manually switch cameras from the camera interface.
43+
`,
44+
},
45+
],
46+
related: [],
47+
examples: {
48+
description:
49+
'Learn how to capture photos using the device camera and handle the resulting image data.',
50+
examples: [
51+
{
52+
codeblock: generateJsxCodeBlockForCameraApi(
53+
'Capture and upload photo to server',
54+
'take-photo-upload',
55+
),
56+
description:
57+
'This example demonstrates capturing a photo using `shopify.camera.takePhoto()` and uploading it to a backend server for further processing. It shows loading states during capture and upload, handles errors appropriately, and confirms successful upload with toast notifications.',
58+
},
59+
{
60+
codeblock: generateJsxCodeBlockForCameraApi(
61+
'Capture and display a photo',
62+
'take-photo-display',
63+
),
64+
description:
65+
'This example demonstrates using `shopify.camera.takePhoto()` to capture an image with the device camera and displaying it immediately using the `image` component.',
66+
},
67+
],
68+
},
69+
};
70+
71+
export default data;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {render} from 'preact';
2+
import {useState} from 'preact/hooks';
3+
4+
export default async () => {
5+
render(<Extension />, document.body);
6+
};
7+
8+
const Extension = () => {
9+
const [imageData, setImageData] = useState(null);
10+
const [isCapturing, setIsCapturing] = useState(false);
11+
12+
const handleTakePhoto = async () => {
13+
setIsCapturing(true);
14+
try {
15+
const photo = await shopify.camera.takePhoto();
16+
setImageData(photo);
17+
shopify.toast.show('Photo captured successfully!');
18+
} catch (error) {
19+
// skip showing errors when the user cancels the photo capture.
20+
if (!error.message.includes('User cancelled')) {
21+
shopify.toast.show(`Error: ${error.message}`);
22+
}
23+
} finally {
24+
setIsCapturing(false);
25+
}
26+
};
27+
28+
return (
29+
<s-page heading="Camera Capture">
30+
<s-scroll-box>
31+
<s-stack>
32+
<s-button onClick={handleTakePhoto} disabled={isCapturing}>
33+
{isCapturing ? 'Capturing...' : 'Take Photo'}
34+
</s-button>
35+
36+
{imageData && (
37+
<>
38+
<s-image
39+
src={`data:${imageData.type};base64,${imageData.base64}`}
40+
/>
41+
<s-section heading="Image Details">
42+
<s-text>Width: {imageData.width}px</s-text>
43+
<s-text>Height: {imageData.height}px</s-text>
44+
<s-text>
45+
File Size: {(imageData.fileSize / 1024).toFixed(2)} KB
46+
</s-text>
47+
<s-text>Type: {imageData.type}</s-text>
48+
</s-section>
49+
</>
50+
)}
51+
</s-stack>
52+
</s-scroll-box>
53+
</s-page>
54+
);
55+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {render} from 'preact';
2+
import {useState} from 'preact/hooks';
3+
4+
export default async () => {
5+
render(<Extension />, document.body);
6+
};
7+
8+
const Extension = () => {
9+
const [isProcessing, setIsProcessing] = useState(false);
10+
11+
const handleCaptureAndUpload = async () => {
12+
setIsProcessing(true);
13+
try {
14+
const photo = await shopify.camera.takePhoto({
15+
quality: 0.8,
16+
maxWidth: 1520,
17+
maxHeight: 1520,
18+
});
19+
20+
// Upload the image to your backend server
21+
// (Replace with your actual backend endpoint)
22+
await fetch('https://your-backend.com/api/upload', {
23+
method: 'POST',
24+
headers: {'Content-Type': 'application/json'},
25+
body: JSON.stringify({
26+
image: photo.base64,
27+
mimeType: photo.type,
28+
}),
29+
});
30+
31+
shopify.toast.show('Photo uploaded successfully!');
32+
} catch (error) {
33+
shopify.toast.show(`Error: ${error.message}`);
34+
} finally {
35+
setIsProcessing(false);
36+
}
37+
};
38+
39+
return (
40+
<s-tile
41+
heading="Upload Photo"
42+
onClick={handleCaptureAndUpload}
43+
disabled={isProcessing}
44+
/>
45+
);
46+
};

packages/ui-extensions/src/surfaces/point-of-sale/api/camera-api/camera-api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* Specifies configuration options for capturing photos using the device camera.
3+
*/
14
export interface CameraMediaOptions {
25
/**
36
* The camera that will be active when the camera interface first opens.
@@ -24,6 +27,9 @@ export interface CameraMediaOptions {
2427
quality?: number;
2528
}
2629

30+
/**
31+
* Represents the captured image and associated metadata returned by `shopify.camera.takePhoto()`.
32+
*/
2733
export interface CameraMediaResponse {
2834
/** The image data as base64 string. */
2935
base64: string;
@@ -37,6 +43,9 @@ export interface CameraMediaResponse {
3743
type: string;
3844
}
3945

46+
/**
47+
* Provides camera capabilities for the POS device.
48+
*/
4049
export interface CameraApiContent {
4150
/**
4251
* Launch the device's camera to take a photo.
@@ -51,6 +60,10 @@ export interface CameraApiContent {
5160
takePhoto: (options?: CameraMediaOptions) => Promise<CameraMediaResponse>;
5261
}
5362

63+
/**
64+
* The `CameraApi` object provides access to device camera functionality for capturing photos.
65+
* Access these properties through `shopify.camera`.
66+
*/
5467
export interface CameraApi {
5568
camera: CameraApiContent;
5669
}

0 commit comments

Comments
 (0)