---
title: User Generated Content
description: >
Learn how to create, display, and moderate user-generated image content in
Shop Minis using the content system.
api_name: shop-minis
source_url:
html: 'https://shopify.dev/docs/api/shop-minis/user-generated-content'
md: 'https://shopify.dev/docs/api/shop-minis/user-generated-content.md'
---
# User-generated content
Learn how to create, display, and moderate user-generated image content in Shop Minis using the content system.
***
## Introduction
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](https://shop.app/content-policies).
***
## Creating content
Use the `useCreateImageContent` hook to upload images and create content. This hook handles the upload process and returns content metadata including moderation status.
[Hook - Learn more about the useCreateImageContent hook](./hooks/content/usecreateimagecontent)
## Creating image content
```tsx
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 (
)
}
```
### Content visibility
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 status
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
```tsx
import {MinisContentStatus} from '@shopify/shop-minis-react'
function ContentStatusBadge({status}: {status: MinisContentStatus}) {
switch (status) {
case MinisContentStatus.PENDING:
return Under review
case MinisContentStatus.READY:
return Approved
case MinisContentStatus.REJECTED:
return Rejected
}
}
```
***
## Displaying content
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.
[Component - Learn more about the ContentWrapper component](./components/primitives/contentwrapper)
## Displaying content
```tsx
import {ContentWrapper, Image, MinisContentStatus} from '@shopify/shop-minis-react'
export default function DisplayContent({publicId}: {publicId: string}) {
return (
{({content, loading}) => {
if (loading) return
Loading...
if (!content || content.status !== MinisContentStatus.READY) {
return null
}
return (
)
}}
)
}
```
You can also use an `externalId` if you're tracking content with your own identifiers:
## Using external ID
```tsx
import {ContentWrapper, Image} from '@shopify/shop-minis-react'
{({content, loading}) => {
if (loading) return
Loading...
if (!content) return null
return (
)
}}
```
***
## Long-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
```tsx
// No additional setup needed - long-press reporting
// is automatically enabled on ContentWrapper
{({content, loading}) => {
if (loading || !content) return null
return (
)
}}
```
***
## Linking to content
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
```tsx
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 (
)
}
```
***
## Deeplinking to content
When a user opens a content link (`shop.app/mc/`), 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.
[Hook - Learn more about the useDeeplink hook](./hooks/navigation/usedeeplink)
## Handling content deeplinks
```tsx
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
No content specified
}
return (
{({content, loading}) => {
if (loading) return
Loading...
if (!content || content.status !== MinisContentStatus.READY) {
return
Content not available
}
return (
{content.title}
{content.description &&
{content.description}
}
)
}}
)
}
```
***
## Guidelines
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](https://shop.app/content-policies).
* **Additional review**: Minis with user-generated content will face additional review scrutiny during submission.
[Guide - Review submission guidelines](./guidelines)
***
## Related resources
* [useCreateImageContent hook](./hooks/content/usecreateimagecontent) - Create and upload image content
* [ContentWrapper component](./components/primitives/contentwrapper) - Display moderated content
* [useDeeplink hook](./hooks/navigation/usedeeplink) - Handle content deeplinks
* [useImagePicker hook](./hooks/util/useimagepicker) - Pick images from device
* [useShare hook](./hooks/util/useshare) - Share content with others
* [Guidelines](./guidelines) - Review and submission requirements
***