Skip to main content

User-generated content

Learn how to create, display, and moderate user-generated image content in Shop Minis using the content system.


Shop Minis provides a built-in content system for handling user-generated images. This system includes automatic moderation, reporting capabilities, and shareable links. All user-generated images must use this system to ensure content safety and comply with Shop's Content Policy.


Use the useCreateImageContent hook to upload images and create content. This hook handles the upload process and returns content metadata including moderation status.

Creating image content

import {useCreateImageContent} from '@shopify/shop-minis-react'
import {useImagePicker} from '@shopify/shop-minis-react'

export default function UploadImage() {
const {createImageContent, loading} = useCreateImageContent()
const {pickImage} = useImagePicker()

const handleUpload = async () => {
// Pick an image from the user's device
const image = await pickImage()

if (image) {
// Upload and create content
const result = await createImageContent({
image: image.uri,
title: 'My uploaded image',
description: 'A great photo',
externalId: 'unique-id-123', // Optional: Your own ID for reference
visibility: ['DISCOVERABLE', 'LINKABLE'],
})

if (result.data) {
console.log('Content created:', result.data.publicId)
console.log('Status:', result.data.status) // PENDING, READY, or REJECTED
console.log('Shareable URL:', result.data.shareableUrl)
}

if (result.userErrors) {
console.error('Upload failed:', result.userErrors)
}
}
}

return (
<Button onPress={handleUpload} disabled={loading}>
{loading ? 'Uploading...' : 'Upload Image'}
</Button>
)
}

The visibility parameter controls how content can be discovered and shared. It accepts an array of ContentVisibility values:

  • DISCOVERABLE: Content appears in Shop's recommendation surfaces, such as feeds and content discovery areas.
  • LINKABLE: Content gets a shareable URL, enabling users to share it outside of Shop.

Pass both values to make content fully public, or pass null or an empty array to keep content private within your Mini.


Content goes through moderation and has three possible statuses:

  • PENDING: Content is being reviewed for moderation.
  • READY: Content has passed moderation and can be displayed.
  • REJECTED: Content was rejected during moderation.

Users will always be able to see their own content, but will not be able to see other users' rejected content. In this case, it will be null.

Handling content status

import {MinisContentStatus} from '@shopify/shop-minis-react'

function ContentStatusBadge({status}: {status: MinisContentStatus}) {
switch (status) {
case MinisContentStatus.PENDING:
return <Badge>Under review</Badge>
case MinisContentStatus.READY:
return <Badge variant="success">Approved</Badge>
case MinisContentStatus.REJECTED:
return <Badge variant="error">Rejected</Badge>
}
}

Use the ContentWrapper component to display user-generated images. This component provides built-in moderation reporting functionality that allows users to long-press on content to report inappropriate material.

Caution

Always use ContentWrapper instead of rendering user-generated content directly. This is required for content moderation.

Displaying content

import {ContentWrapper, Image, MinisContentStatus} from '@shopify/shop-minis-react'

export default function DisplayContent({publicId}: {publicId: string}) {
return (
<ContentWrapper publicId={publicId}>
{({content, loading}) => {
if (loading) return <div>Loading...</div>

if (!content || content.status !== MinisContentStatus.READY) {
return null
}

return (
<Image
src={content.image.url}
alt={content.title}
/>
)
}}
</ContentWrapper>
)
}

You can also use an externalId if you're tracking content with your own identifiers:

Using external ID

import {ContentWrapper, Image} from '@shopify/shop-minis-react'

<ContentWrapper externalId="unique-id-123">
{({content, loading}) => {
if (loading) return <div>Loading...</div>

if (!content) return null

return (
<Image
src={content.image.url}
alt={content.title}
/>
)
}}
</ContentWrapper>

Anchor to Long-press moderationLong-press moderation

Content displayed with ContentWrapper automatically supports long-press gestures for user reporting. When a user long-presses on the content, they can report it for moderation if it violates Shop's content policies.

This functionality is built into the component and requires no additional configuration. Shop's moderation team will review reported content and take appropriate action.

Long-press is automatic

// No additional setup needed - long-press reporting
// is automatically enabled on ContentWrapper
<ContentWrapper publicId={content.publicId}>
{({content, loading}) => {
if (loading || !content) return null

return (
<Image
src={content.image.url}
alt={content.title}
/>
)
}}
</ContentWrapper>

Content created with useCreateImageContent includes a shareableUrl property that you can use to link to the content from outside your Mini or share with other users.

Using shareable URLs

import {useShare} from '@shopify/shop-minis-react'

export default function ShareContent({content}) {
const {share} = useShare()

const handleShare = async () => {
if (content.shareableUrl) {
await share({
title: content.title,
message: content.description,
url: content.shareableUrl,
})
}
}

return (
<Button onPress={handleShare}>
Share Content
</Button>
)
}

Anchor to Deeplinking to contentDeeplinking to content

When a user opens a content link (shop.app/mc/<public_id>), the Shop app opens your Mini and passes the publicId through the deeplink. Use the useDeeplink hook to retrieve the content ID and display the appropriate content.

Handling content deeplinks

import {useDeeplink} from '@shopify/shop-minis-react'
import {ContentWrapper, Image, MinisContentStatus} from '@shopify/shop-minis-react'

export default function ContentPage() {
const {publicId} = useDeeplink()

if (!publicId) {
return <div>No content specified</div>
}

return (
<ContentWrapper publicId={publicId}>
{({content, loading}) => {
if (loading) return <div>Loading...</div>

if (!content || content.status !== MinisContentStatus.READY) {
return <div>Content not available</div>
}

return (
<div>
<h1>{content.title}</h1>
{content.description && <p>{content.description}</p>}
<Image
src={content.image.url}
alt={content.title}
/>
</div>
)
}}
</ContentWrapper>
)
}

When working with user-generated content, follow these guidelines:

  • Required moderation: All user-generated images must use useCreateImageContent and ContentWrapper. This is enforced during the Mini review process.
  • Check status: Always check the content status before displaying. Only show content with READY status.
  • Use the wrapper: Never render user-generated content directly. Always use ContentWrapper to enable moderation reporting.
  • Content policy: Ensure your moderation practices align with Shop's Content Policy.
  • Additional review: Minis with user-generated content will face additional review scrutiny during submission.


Was this page helpful?