use Create Image Content
The useCreateImageContent hook combines image upload with content creation to generate user-generated content entries. Built on top of useImageUpload, it extends the basic upload functionality with content management features including titles, descriptions, visibility controls, product associations, and unique IDs for sharing and discovery.
Anchor to ParametersParameters
The createImageContent function accepts an object with the following fields:
image(required): AFileobject representing the image to upload.contentTitle(required): The title for the content entry.visibility(optional): An array ofContentVisibilityvalues (see below).externalId(optional): A unique identifier from your own system that lets you look up content later viaContentWrapperusing an ID you already know. If not provided, the content can only be retrieved by itspublicId. EachexternalIdmust be unique per Mini — creating content with a duplicateexternalIdreturns aDUPLICATE_EXTERNAL_IDerror.description(optional): A text description for the content. Displayed alongside the content in Shop surfaces such as feeds and content detail views. Use this to provide context about the image, such as a caption or review text.productIds(optional): An array of Shopify product GIDs (e.g.'gid://shopify/Product/123') to associate with the content. Maximum 20 products. Associated products appear alongside the content, enabling shoppable content experiences. If any product IDs are ineligible, the mutation will return anINELIGIBLE_PRODUCTSerror.
Anchor to Visibility OptionsVisibility Options
The visibility parameter accepts an optional array of ContentVisibility values:
DISCOVERABLE: Makes content eligible for Shop's recommendation and discovery systems. Your content can appear in feeds and recommendations across the Shop app.LINKABLE: Enables shareable URLs for the content. When set, the createdContentobject includes ashareableUrlfield containing a URL that can be shared externally. The shareable page renders OpenGraph meta tags (og:title,og:image,og:description) using the content'scontentTitle, uploaded image, anddescription— so link previews in social media, messaging apps, and other platforms will display a rich card with the content's title, image, and description. Use theuseSharehook to trigger the native share sheet with this URL.
Anchor to Error HandlingError Handling
The hook may return userErrors in the response with the following codes:
DUPLICATE_EXTERNAL_ID: Returned when anexternalIdis provided that already exists for this Mini. EachexternalIdmust be unique per content entry.INELIGIBLE_PRODUCTS: Returned when one or moreproductIdsrefer to products that are not eligible for Shop. The error message includes the specific ineligible product GIDs.
Anchor to Use CasesUse Cases
- Shareable content with rich link previews: Include
LINKABLEin thevisibilityarray and provide acontentTitle,description, and image. The returnedshareableUrlpoints to a page with OpenGraph tags, so when shared via social media or messaging apps the link preview displays the content's title, image, and description as a rich card. Pass theshareableUrlto theuseSharehook to open the native share sheet. - User reviews with photos: Provide
descriptionfor the review text,productIdsto link the reviewed products, and['DISCOVERABLE', 'LINKABLE']visibility for maximum reach. - Shoppable lookbooks: Upload styled images with associated
productIdsso users can shop the look. - Content lookup by your own ID: Set
externalIdto an identifier from your system (e.g. a database row ID or slug) so you can retrieve the content later viaContentWrapperwithout storing thepublicId.
The hook handles both the image upload pipeline and content entity creation in a single operation, automatically associating content with your Mini and integrating with Shop's content systems.
You must run the setup CLI command before using this hook so the content can be associated with the Mini.
You must run the setup CLI command before using this hook so the content can be associated with the Mini.
Anchor to useCreateImageContentuse Create Image Content()
UseCreateImageContentReturns
- createImageContent
Upload an image and create content.
(params: CreateImageContentParams) => Promise<{ data: Content; userErrors?: ContentCreateUserErrors[]; }> - loading
Whether the content is being created.
boolean
CreateImageContentParams
- contentTitle
The title for the content entry.
string - description
A text description for the content. Displayed alongside the image in Shop surfaces such as feeds and content detail views. Use this to provide context about the image, such as a caption or review text.
string - externalId
A unique identifier from your own system that lets you look up content later via `ContentWrapper` using an ID you already know. If not provided, the content can only be retrieved by its `publicId`. Each `externalId` must be unique per Mini — creating content with a duplicate returns a `DUPLICATE_EXTERNAL_ID` error.
string - image
The image file to upload.
File - productIds
An array of Shopify product GIDs (e.g. `'gid://shopify/Product/123'`) to associate with the content. Maximum 20 products. Associated products appear alongside the content, enabling shoppable content experiences.
string[] - visibility
Visibility options for the content. Use `['DISCOVERABLE']` to appear in recommendations, `['LINKABLE']` to enable shareable URLs, or both. Pass `null` or `[]` to keep content private within your Mini.
ContentVisibility[] | null
ContentVisibility
'DISCOVERABLE' | 'LINKABLE'Content
- description
string | null - externalId
string | null - image
ContentImage - products
ContentProduct[] | null - publicId
string - shareableUrl
string | null - status
MinisContentStatus | null - title
string - visibility
ContentVisibility[]
ContentImage
- altText
string | null - height
number | null - id
string | null - thumbhash
string | null - url
string - width
number | null
ContentProduct
- featuredImage
ContentImage | null - id
string - title
string
MinisContentStatus
- PENDING
PENDING - READY
READY - REJECTED
REJECTED
ContentCreateUserErrors
- code
ContentCreateUserErrorCode - message
string
ContentCreateUserErrorCode
- DUPLICATE_EXTERNAL_ID
DUPLICATE_EXTERNAL_ID - INELIGIBLE_PRODUCTS
INELIGIBLE_PRODUCTS
tsx
Examples
tsx
import { useCreateImageContent, useImagePicker, Button, } from '@shopify/shop-minis-react' export default function MyComponent() { const {createImageContent, loading} = useCreateImageContent() const {openCamera, openGallery} = useImagePicker() const handleCameraCapture = async () => { try { const file = await openCamera() const result = await createImageContent({ image: file, contentTitle: 'Photo from camera', visibility: ['DISCOVERABLE', 'LINKABLE'], // Optional: set an external ID for deduplication and lookup externalId: 'my-unique-id-123', // Optional: add a description description: 'A photo captured with the camera', // Optional: associate up to 20 products (by GID) productIds: ['gid://shopify/Product/1'], }) console.log({data: result.data, userErrors: result.userErrors}) } catch (error) { console.error('Failed to capture and upload image:', error) } } const handleGallerySelect = async () => { try { const file = await openGallery() const result = await createImageContent({ image: file, contentTitle: 'Photo from gallery', // Visibility options: // - ['DISCOVERABLE'] - Appears in Shop recommendations // - ['LINKABLE'] - Enables shareable URLs // - ['DISCOVERABLE', 'LINKABLE'] - Both features // - null or [] - Private within Mini only visibility: ['DISCOVERABLE', 'LINKABLE'], description: 'A photo selected from the gallery', }) console.log({data: result.data, userErrors: result.userErrors}) } catch (error) { console.error('Failed to select and upload image:', error) } } return ( <> <Button onClick={handleCameraCapture} disabled={loading}> Take Photo and Upload </Button> <Button onClick={handleGallerySelect} disabled={loading}> Select from Gallery and Upload </Button> {loading && <p>Uploading image...</p>} </> ) }Description
A minimal example that uploads an image with a title and visibility.
tsx
import { useCreateImageContent, useImagePicker, Button, } from '@shopify/shop-minis-react' export default function BasicUpload() { const {createImageContent, loading} = useCreateImageContent() const {openGallery} = useImagePicker() const handleUpload = async () => { try { const file = await openGallery() const result = await createImageContent({ image: file, contentTitle: 'My photo', visibility: ['DISCOVERABLE', 'LINKABLE'], }) console.log('Created content:', result.data.publicId) } catch (error) { console.error('Upload failed:', error) } } return ( <Button onClick={handleUpload} disabled={loading}> {loading ? 'Uploading...' : 'Upload Photo'} </Button> ) }Description
Set `externalId` to an identifier from your own system so you can look up the content later via `ContentWrapper` without needing to store the `publicId`.
tsx
import { useCreateImageContent, useImagePicker, ContentWrapper, Button, Image, } from '@shopify/shop-minis-react' /** * Use `externalId` to look up content later using an ID you already know, * without needing to store the `publicId`. * * For example, if your Mini lets users upload a profile photo, you can set * the `externalId` to the user's ID in your system and later retrieve * the content with `<ContentWrapper externalId="user-42">`. */ export default function WithExternalId() { const {createImageContent, loading} = useCreateImageContent() const {openCamera} = useImagePicker() // In practice this would come from your own user/session data const userId = 'user-42' const handleCapture = async () => { try { const file = await openCamera() const result = await createImageContent({ image: file, contentTitle: 'Profile photo', visibility: ['DISCOVERABLE'], // Set externalId to an identifier from your own system. // You can then look up this content later via ContentWrapper // using the same ID — no need to persist the publicId. // Each externalId must be unique per Mini; duplicates return // a DUPLICATE_EXTERNAL_ID error. externalId: userId, }) console.log('Created content:', result.data.publicId) } catch (error) { console.error('Upload failed:', error) } } return ( <> <Button onClick={handleCapture} disabled={loading}> {loading ? 'Uploading...' : 'Upload Profile Photo'} </Button> {/* Later, retrieve the content by the same externalId */} <ContentWrapper externalId={userId}> {({content, loading: contentLoading}) => { if (contentLoading) return <p>Loading...</p> if (!content) return null return <Image src={content.image.url} alt={content.title} /> }} </ContentWrapper> </> ) }Description
Add a `description` to provide a caption or review text displayed alongside the image in Shop surfaces.
tsx
import {useState} from 'react' import { useCreateImageContent, useImagePicker, Button, } from '@shopify/shop-minis-react' /** * Add a description to provide context alongside the image. * Descriptions are displayed in Shop feeds and content detail views. */ export default function WithDescription() { const {createImageContent, loading} = useCreateImageContent() const {openGallery} = useImagePicker() const [caption, setCaption] = useState('') const handleUpload = async () => { try { const file = await openGallery() const result = await createImageContent({ image: file, contentTitle: 'Product review', // The description appears as body text alongside // the image in Shop surfaces like feeds and detail views. description: caption || 'Check out this product!', visibility: ['DISCOVERABLE', 'LINKABLE'], }) console.log('Created content:', result.data.publicId) } catch (error) { console.error('Upload failed:', error) } } return ( <> <input type="text" placeholder="Write a caption..." value={caption} onChange={e => setCaption(e.target.value)} /> <Button onClick={handleUpload} disabled={loading}> {loading ? 'Posting...' : 'Post Review'} </Button> </> ) }Description
Associate products with content using `productIds` to create shoppable images. Users can browse and purchase products directly from the content.
tsx
import {useState} from 'react' import { useCreateImageContent, useImagePicker, Button, } from '@shopify/shop-minis-react' /** * Associate products with content to create shoppable images. * Users can browse products directly from the content in Shop. */ export default function WithProducts() { const {createImageContent, loading} = useCreateImageContent() const {openGallery} = useImagePicker() const [error, setError] = useState<string | null>(null) // These would typically come from your Mini's product selection UI const selectedProductIds = [ 'gid://shopify/Product/111', 'gid://shopify/Product/222', ] const handleUpload = async () => { setError(null) try { const file = await openGallery() const result = await createImageContent({ image: file, contentTitle: 'Shop the look', description: 'Styled outfit featuring our latest collection', visibility: ['DISCOVERABLE', 'LINKABLE'], // Associate up to 20 products by their Shopify GIDs. // If any product is ineligible, userErrors will contain // an INELIGIBLE_PRODUCTS error with the specific GIDs. productIds: selectedProductIds, }) if (result.userErrors?.length) { setError(result.userErrors.map(e => e.message).join(', ')) return } console.log('Created shoppable content:', result.data.publicId) } catch (err) { console.error('Upload failed:', err) } } return ( <> <Button onClick={handleUpload} disabled={loading}> {loading ? 'Creating...' : 'Create Shoppable Post'} </Button> {error && <p style={{color: 'red'}}>{error}</p>} </> ) }Description
Create content with `LINKABLE` visibility to get a `shareableUrl`. The URL renders OpenGraph meta tags (`og:title`, `og:image`, `og:description`) from the content's title, image, and description, so link previews in social media and messaging apps display a rich card. Use `useShare` to trigger the native share sheet.
tsx
import {useState} from 'react' import { useCreateImageContent, useImagePicker, useShare, Button, } from '@shopify/shop-minis-react' /** * Create shareable content with rich link previews. * * When `LINKABLE` is included in the `visibility` array, the created content * includes a `shareableUrl`. This URL points to a page that renders OpenGraph * meta tags (`og:title`, `og:image`, `og:description`) from the content's * title, image, and description — so link previews in social media, messaging * apps, and other platforms display a rich card automatically. */ export default function ShareableContent() { const {createImageContent, loading} = useCreateImageContent() const {openGallery} = useImagePicker() const {share} = useShare() const [shareableUrl, setShareableUrl] = useState<string | null>(null) const handleCreateAndShare = async () => { try { const file = await openGallery() // 1. Create the content with LINKABLE visibility. // The title, image, and description you provide here will appear // in OpenGraph link previews when the shareableUrl is shared. const result = await createImageContent({ image: file, contentTitle: 'My favorite outfit this week', description: 'Loving this spring look — the colors are perfect for the season!', visibility: ['DISCOVERABLE', 'LINKABLE'], }) const url = result.data.shareableUrl if (!url) { console.warn('No shareable URL returned — is LINKABLE set?') return } setShareableUrl(url) // 2. Open the native share sheet with the shareable URL. // Recipients will see a rich preview card with the content's // title, image, and description rendered via OpenGraph tags. await share({ title: result.data.title, url, }) } catch (error) { console.error('Failed to create or share content:', error) } } return ( <> <Button onClick={handleCreateAndShare} disabled={loading}> {loading ? 'Creating...' : 'Upload & Share'} </Button> {shareableUrl && ( <p> Shareable link: <a href={shareableUrl}>{shareableUrl}</a> </p> )} </> ) }