Image & Video APIs

Profile picture sample project

Last updated: Aug-31-2025

This guide demonstrates how to build a secure social media-style application that handles user-generated content (UGC) using Cloudinary's advanced capabilities. The app features a Profile page where users can manage their personal information and upload a profile picture, along with a Posts page where they can share thoughts and images.

Profile page Profile page Posts page Posts page

View the code
You can find the code for this sample project in GitHub.

On this page:

Overview

The app serves as a demonstration platform for handling user-generated content in a social media context. It implements these main features:

On the Profile page:

  • The uploaded image is moderated for appropriate content and checked for malware before being displayed on the page.
  • If the image is poor quality, then the quality is improved.
  • The image is displayed as a square, focusing on the face, if there is one, or the most interesting part of the image, if not.

On the Posts page:

  • The post is displayed against the profile picture, which is resized and made circular with an outline.
  • The uploaded image, if there is one, is moderated for appropriate content and checked for malware before being displayed on the page.
  • The post image is displayed with padding, if required, to show the whole image in a dedicated space.

Key Cloudinary features

Learn about how each of these Cloudinary features have been implemented in this app:

Try it out

Here's the app in action:


To run the app yourself:

  1. Clone or fork the GitHub repo.
  2. In app/config/cloudinary.ts, replace MY_CLOUD_NAME with your Cloudinary product environment cloud name.
  3. Register for the following add-ons (they both have free tiers):
  4. To try out your app locally, you need to set up a secure tunnel connecting the internet to your locally running application so that the webhooks sent by Cloudinary on upload are caught and handled by the app. You can use a tool such as Ngrok to do this. Otherwise, you need to deploy the app using a service such as Vercel. Whichever method you choose, make a note of your app's domain (for example, a-b-c-d.ngrok-free.app or a-b-c-d.vercel.app). By default, the app runs on port 3000.
  5. Create an upload preset called ugc-profile-photo. (You can use a different name, but if you do, you also need update the uploadPreset value in cloudinary.ts.) See instructions on how to configure your upload preset.
  6. Ensure that the Notification URL in your upload preset is set to:
    https://<your app's domain>/api/moderate
  7. Upload an image to use as the default image (for example this image), and set its public ID to avatar-pic. Alternatively, use an image that's already in your product environment, and change the value of defaultImage in cloudinary.ts to its public ID.
  8. If running locally, run:

    then

    Then open http://localhost:3000 in your browser to see the app running.

Upload preset configuration

To configure the upload preset:

  1. Log into your Cloudinary Console.
  2. Navigate to Settings > Upload > Upload Presets.
  3. Click Add Upload Preset.
  4. Configure each of the sections as shown below, then click Save:

General

Parameter Value Meaning
Upload preset name ugc-profile-photo The name of the upload preset. This must match the uploadPreset parameter used in the Upload widget configuration (set in cloudinary.ts).
Signing mode Unsigned No signature is required for uploading assets using this upload preset.
Auto-generate an unguessable public ID value true It's best to generate a random value to avoid conflicts if you have many users uploading their images to your product environment.

The rest of the General settings can be set as you like.

Advanced upload preset settings

Transform

Parameter Value Meaning
Incoming transformation c_limit,h_1000,w_1000/fl_force_strip Limit the dimensions of the image to 1000 by 1000 pixels and strip embedded metadata associated with the image.

Transform upload preset settings

Manage and Analyze

Parameter Value Meaning
Retrieve quality analysis data true Return quality analysis data in the upload response.

Manage and analyze upload preset settings

Optimize and Deliver

Parameter Value Meaning
Delivery type Upload Make the image publicly available.

Optimize and deliver upload preset settings

Advanced

Parameter Value Meaning
Notification URL https://<your app's domain>/api/moderate The API endpoint for your app. To try out your app locally, you need to set up a secure tunnel connecting the internet to your locally running application so that the webhooks sent by Cloudinary on upload are caught and handled by the app. You can use a tool such as Ngrok to do this. Otherwise, you need to deploy the app using a service such as Vercel.
Eval script if (resource_info.quality_analysis.focus < 0.7) { upload_options['tags'] = 'poor_quality' } JavaScript code that checks the quality analysis and adds a poor_quality tag if the focus is less than 0.7.

Advanced upload preset settings

Addons

Add the Rekognition AI Moderation add-on. Click Options to optionally set thresholds for each type of moderation.

Add the Perception Point add-on.

Addons upload preset settings

Note
Ensure you're registered to both of these add-ons too.

Deep dive

If you want to learn how each of the features have been implemented in detail, expand the following sections:

-->
Project files

The main project files are as follows:

File Functionality
app/context/UserContext.tsx
  • Provides global state management
  • Stores user information: profile picture, name, location, birthday, posts
  • Makes user data accessible throughout the application
app/page.tsx (Profile Page)
  • Allows users to edit personal information
  • Handles profile picture uploads
  • Implements image moderation
  • Uses Cloudinary for image optimization and face detection
app/posts/page.tsx (Posts Page)
  • Enables users to create posts with text and optional images
  • Displays posts with profile pictures
  • Implements image moderation for uploaded images
  • Uses Cloudinary for image optimization and transformations
app/components/upload-widget.tsx
  • Handles image uploads using Cloudinary's widget
  • Manages upload states and error handling
  • Communicates with the moderation API
  • Used by both Profile and Posts pages
app/components/cld.ts
  • Configures the Cloudinary instance
  • Handles image transformations and optimization
app/config/cloudinary.ts
  • Contains configuration details specific to the app instance
    app/api/moderate/route.ts
    • Manages image moderation for uploaded files
    • Handles webhooks from Cloudinary when moderation is complete
    • Handles frontend requests checking the moderation status of an uploaded image

    layout.tsx

    Upload widget creation and configuration

    The Upload widget is created using the createUploadWidget function:

    upload-widget.tsx

    The configuration parameters are set as follows:

    Parameter Value Meaning
    cloudName CLOUDINARY_CONFIG.cloudName This is the name of product environment to which the images are uploaded. You need to change this for your environment in cloudinary.ts.
    clientAllowedFormats image Only images are permitted to be uploaded.
    uploadPreset CLOUDINARY_CONFIG.uploadPreset This is the name of the upload preset, which defines what happens on upload. It is set in cloudinary.ts. See Upload preset configuration.
    sources ['local'] Only images from the user's local environment can be uploaded. You can change this to allow more sources, if required.
    multiple false Only one image can be selected.
    maxFiles 1 Only one image at a time can be uploaded.

    The callback function async (error: any, result: any) => handles the upload results:

    • If the upload returns an error, then the error is displayed.
    • A successful upload clears any displayed errors, activates the loading state (to start the spinner), captures the current timestamp and starts the moderation check.

    Upload widget rendering

    The UploadWidget component renders a button, which opens the widget in response to a click:

    upload-widget.tsx

    The UploadWidget component is used in page.tsx and posts/page.tsx:

    page.tsx

    The code is identical for each page, except for the button text.

    posts/page.tsx

    The props are specific to the page, so for example, the handleUploadSuccess function in page.tsx has different functionality to the handleUploadSuccess function in posts/page.tsx.

    Upload widget state management

    The UploadWidget component primarily manages one piece of state - the Cloudinary Upload widget instance itself:

    upload-widget.tsx

    This state is handled in few places:

    Initialization - in the useEffect:

    upload-widget.tsx

    Usage - in the click handler:

    upload-widget.tsx

    Props for external state management - the component doesn't manage upload state itself, but instead relies on props to communicate with its parent:

    upload-widget.tsx

    The actual upload status, errors, and loading states are managed by the parent components (pages), making use of the "lifting state up" React pattern - the widget itself manages only what it absolutely needs to (the widget instance), while letting parent components manage the states that might need to be shared or displayed elsewhere in the app:

    page.tsx

    posts/page.tsx