---
title: User Generated Content
description: >
  Learn how to create, display, and moderate user-generated image content in
  Shop Minis using the content system.
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](https://shopify.dev/docs/api/shop-minis/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 (
    <Button onPress={handleUpload} disabled={loading}>
      {loading ? 'Uploading...' : 'Upload Image'}
    </Button>
  )
}
```

### 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 <Badge>Under review</Badge>
    case MinisContentStatus.READY:
      return <Badge variant="success">Approved</Badge>
    case MinisContentStatus.REJECTED:
      return <Badge variant="error">Rejected</Badge>
  }
}
```

***

## 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](https://shopify.dev/docs/api/shop-minis/components/primitives/contentwrapper)

## Displaying content

```tsx
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

```tsx
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>
```

***

## 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
<ContentWrapper publicId={content.publicId}>
  {({content, loading}) => {
    if (loading || !content) return null


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

***

## 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 (
    <Button onPress={handleShare}>
      Share Content
    </Button>
  )
}
```

***

## Deeplinking 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.

[Hook - Learn more about the useDeeplink hook](https://shopify.dev/docs/api/shop-minis/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 <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>
  )
}
```

***

## 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](https://shopify.dev/docs/api/shop-minis/guidelines)

***

## Related resources

* [useCreateImageContent hook](https://shopify.dev/docs/api/shop-minis/hooks/content/usecreateimagecontent) - Create and upload image content
* [ContentWrapper component](https://shopify.dev/docs/api/shop-minis/components/primitives/contentwrapper) - Display moderated content
* [useDeeplink hook](https://shopify.dev/docs/api/shop-minis/hooks/navigation/usedeeplink) - Handle content deeplinks
* [useImagePicker hook](https://shopify.dev/docs/api/shop-minis/hooks/util/useimagepicker) - Pick images from device
* [useShare hook](https://shopify.dev/docs/api/shop-minis/hooks/util/useshare) - Share content with others
* [Guidelines](https://shopify.dev/docs/api/shop-minis/guidelines) - Review and submission requirements

***
