--- title: useCreateImageContent description: >- 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. ### Parameters The `createImageContent` function accepts an object with the following fields: - **`image`** (required): A `File` object representing the image to upload. - **`contentTitle`** (required): The title for the content entry. - **`visibility`** (optional): An array of `ContentVisibility` values (see below). - **`externalId`** (optional): 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 `externalId` returns a `DUPLICATE_EXTERNAL_ID` error. - **`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 an `INELIGIBLE_PRODUCTS` error. ### Visibility 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 created `Content` object includes a `shareableUrl` field containing a URL that can be shared externally. The shareable page renders OpenGraph meta tags (`og:title`, `og:image`, `og:description`) using the content's `contentTitle`, uploaded image, and `description` — 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 the `useShare` hook to trigger the native share sheet with this URL. ### Error Handling The hook may return `userErrors` in the response with the following codes: - **`DUPLICATE_EXTERNAL_ID`**: Returned when an `externalId` is provided that already exists for this Mini. Each `externalId` must be unique per content entry. - **`INELIGIBLE_PRODUCTS`**: Returned when one or more `productIds` refer to products that are not eligible for Shop. The error message includes the specific ineligible product GIDs. ### Use Cases - **Shareable content with rich link previews**: Include `LINKABLE` in the `visibility` array and provide a `contentTitle`, `description`, and image. The returned `shareableUrl` points 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 the `shareableUrl` to the `useShare` hook to open the native share sheet. - **User reviews with photos**: Provide `description` for the review text, `productIds` to link the reviewed products, and `['DISCOVERABLE', 'LINKABLE']` visibility for maximum reach. - **Shoppable lookbooks**: Upload styled images with associated `productIds` so users can shop the look. - **Content lookup by your own ID**: Set `externalId` to an identifier from your system (e.g. a database row ID or slug) so you can retrieve the content later via `ContentWrapper` without storing the `publicId`. 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. > Caution: You must run the [`setup`](/docs/api/shop-minis/commands/setup) CLI command before using this hook so the content can be associated with the Mini. api_name: shop-minis source_url: html: 'https://shopify.dev/docs/api/shop-minis/hooks/content/usecreateimagecontent' md: >- https://shopify.dev/docs/api/shop-minis/hooks/content/usecreateimagecontent.md --- # 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. ### Parameters The `createImageContent` function accepts an object with the following fields: * **`image`** (required): A `File` object representing the image to upload. * **`contentTitle`** (required): The title for the content entry. * **`visibility`** (optional): An array of `ContentVisibility` values (see below). * **`externalId`** (optional): 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 `externalId` returns a `DUPLICATE_EXTERNAL_ID` error. * **`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 an `INELIGIBLE_PRODUCTS` error. ### Visibility 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 created `Content` object includes a `shareableUrl` field containing a URL that can be shared externally. The shareable page renders OpenGraph meta tags (`og:title`, `og:image`, `og:description`) using the content's `contentTitle`, uploaded image, and `description` — 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 the `useShare` hook to trigger the native share sheet with this URL. ### Error Handling The hook may return `userErrors` in the response with the following codes: * **`DUPLICATE_EXTERNAL_ID`**: Returned when an `externalId` is provided that already exists for this Mini. Each `externalId` must be unique per content entry. * **`INELIGIBLE_PRODUCTS`**: Returned when one or more `productIds` refer to products that are not eligible for Shop. The error message includes the specific ineligible product GIDs. ### Use Cases * **Shareable content with rich link previews**: Include `LINKABLE` in the `visibility` array and provide a `contentTitle`, `description`, and image. The returned `shareableUrl` points 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 the `shareableUrl` to the `useShare` hook to open the native share sheet. * **User reviews with photos**: Provide `description` for the review text, `productIds` to link the reviewed products, and `['DISCOVERABLE', 'LINKABLE']` visibility for maximum reach. * **Shoppable lookbooks**: Upload styled images with associated `productIds` so users can shop the look. * **Content lookup by your own ID**: Set `externalId` to an identifier from your system (e.g. a database row ID or slug) so you can retrieve the content later via `ContentWrapper` without storing the `publicId`. 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. **Caution:** You must run the \\setup\\ CLI command before using this hook so the content can be associated with the Mini. ## use​Create​Image​Content() ### Returns * **UseCreateImageContentReturns** ### ### UseCreateImageContentReturns * **createImageContent** **(params: CreateImageContentParams) => Promise<{ data: Content; userErrors?: ContentCreateUserErrors\[]; }>** Upload an image and create content. * **loading** **boolean** Whether the content is being created. ### UseCreateImageContentReturns * createImageContent Upload an image and create content. ```ts (params: CreateImageContentParams) => Promise<{ data: Content; userErrors?: ContentCreateUserErrors[]; }> ``` * loading Whether the content is being created. ```ts boolean ``` ### CreateImageContentParams * contentTitle The title for the content entry. ```ts 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. ```ts 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. ```ts string ``` * image The image file to upload. ```ts 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. ```ts 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. ```ts ContentVisibility[] | null ``` ### ContentVisibility ```ts 'DISCOVERABLE' | 'LINKABLE' ``` ### Content * description ```ts string | null ``` * externalId ```ts string | null ``` * image ```ts ContentImage ``` * products ```ts ContentProduct[] | null ``` * publicId ```ts string ``` * shareableUrl ```ts string | null ``` * status ```ts MinisContentStatus | null ``` * title ```ts string ``` * visibility ```ts ContentVisibility[] ``` ### ContentImage * altText ```ts string | null ``` * height ```ts number | null ``` * id ```ts string | null ``` * thumbhash ```ts string | null ``` * url ```ts string ``` * width ```ts number | null ``` ### ContentProduct * featuredImage ```ts ContentImage | null ``` * id ```ts string ``` * title ```ts string ``` ### MinisContentStatus * PENDING ```ts PENDING ``` * READY ```ts READY ``` * REJECTED ```ts REJECTED ``` ### ContentCreateUserErrors * code ```ts ContentCreateUserErrorCode ``` * message ```ts string ``` ### ContentCreateUserErrorCode * DUPLICATE\_EXTERNAL\_ID ```ts DUPLICATE_EXTERNAL_ID ``` * INELIGIBLE\_PRODUCTS ```ts INELIGIBLE_PRODUCTS ``` Examples ### Examples * #### Example code ##### Default ```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 ( <> {loading &&

Uploading image...

} ) } ``` * #### Basic upload ##### Description A minimal example that uploads an image with a title and visibility. ##### Default ```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 ( ) } ``` * #### With an external ID ##### 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\`. ##### Default ```tsx import { useCreateImageContent, useImagePicker, ContentWrapper, Button, } 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 ``. */ 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 ( <> {/* Later, retrieve the content by the same externalId */} {({content, loading: contentLoading}) => { if (contentLoading) return

Loading...

if (!content) return null return {content.title} }}
) } ``` * #### With a description ##### Description Add a \`description\` to provide a caption or review text displayed alongside the image in Shop surfaces. ##### Default ```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 ( <> setCaption(e.target.value)} /> ) } ``` * #### With associated products ##### Description Associate products with content using \`productIds\` to create shoppable images. Users can browse and purchase products directly from the content. ##### Default ```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(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 ( <> {error &&

{error}

} ) } ``` * #### Shareable content with rich link previews ##### 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. ##### Default ```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(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 ( <> {shareableUrl && (

Shareable link: {shareableUrl}

)} ) } ``` ## Related [For displaying images - ImageContentWrapper](../../components/primitives/imagecontentwrapper) [For sharing content via the native share sheet - useShare](../../hooks/util/useshare)