Cloudinary Blog

Image moderation made easy using cloud-based UI and API

Image moderation made easy using cloud-based UI & API

Many websites and mobile apps today allow their visitors to share their own photos. Users upload their profile images to dating sites, photos of their personal belongings to second hand market websites and real estate billboards. Users share their personal photos on social networks, and upload images to their favorite eCommerce websites, showcasing and reviewing their latest purchases.

It’s sometimes quite important to moderate these images. You might want to keep out offensive content. You may want to actively reject images that do not answer your website’s needs, for example, make sure there are visible faces in profile images, or that only photos of your brand appear on a shared marketing campaign. You may also want to make sure that photos are of high enough quality before making them available on your website.

While image moderation can be very important, from the developer’s perspective, implementing such a flow can be very time consuming.

To eliminate all the hassle from implementing your own image moderation solution, we’ve built a complete image moderation solution into Cloudinary’s cloud-based image management service.

Manual image moderation console

You can start using Cloudinary’s image moderation in minutes, with a straightforward integration.

Webinar
Marketing Without Barriers Through Dynamic Asset Management

Upload images to Cloudinary for image moderation

Cloudinary is an end-to-end cloud-based image management solution. To start moderating your images with Cloudinary, start by tagging them as “requiring moderation” during upload. Your moderators will then be able to view all the images that require moderation, and accept or reject them, in our online interface.

To upload images to Cloudinary, use the server-side upload API call, or allow users to upload the images directly from their browser using our jQuery plugin.

In the upload call, set the moderation parameter to manual. This tells Cloudinary to mark this image as pending manual moderation.

Check out the simple upload code below in Ruby, PHP, Node.js, Python and Java.

Ruby:
Copy to clipboard
Cloudinary::Uploader.upload("user_photo.jpg", :moderation => 'manual')
PHP:
Copy to clipboard
\Cloudinary\Uploader::upload("user_photo.jpg",
    array( "moderation" => "manual" ));
Python:
Copy to clipboard
cloudinary.uploader.upload(
  "user_photo.jpg",
  moderation = 'manual'
)
Node.js:
Copy to clipboard
cloudinary.uploader.upload(
  "user_photo.jpg",
  function(result) { console.log(result); },
  { moderation: 'manual' }
);
Java:
Copy to clipboard
cloudinary.uploader().upload(new File("user_photo.jpg"), Cloudinary.asMap(
  "moderation", "manual"));

The response includes all image details and URLs by which you can deliver the image to your users, including the moderation status:

Copy to clipboard
{ 
  "public_id": "zutqljpun3qc37asxbgi",`
  "version": 1398848882, 
  "url":  
    "https://.../image/upload/v1398848882/zutqljpun3qc37asxbgi.jpg", 
  "moderation": [{"status": "pending", "kind": "manual"}],  
    
}

Cloudinary’s image moderation UI

Images that are uploaded to Cloudinary, and tagged for manual moderation, can be viewed by moderators using Cloudinary’s Media Library. Moderators can browse recently uploaded images and decide to accept or reject them.

You can use Cloudinary’s user management capabilities to define multiple moderator users that can access only the image moderation console, and do not have access to other Cloudinary features such as image manipulations, reports, etc.

Queue of pending images

In this scenario, a social web app asked that users upload a front-facing photo of themselves. While all the images might seem valid at first glance, a moderator might opt to reject some of them.

The first photo shows two people, however the social app specified a photo of a single person, so the image would be rejected.

The second and fourth photos show a front-facing person. The moderator should decide, based on site policy, whether both the close-up and the longer distance photo should be approved.

The third photo shows a front facing person, however there is an offensive symbol on the person’s T-shirt, which would cause it to be rejected. The fifth photo would also be rejected, as it is too blurry.

Rejecting and approving pending images can be done simply by hovering over a thumbnail in the media library, and clicking on the Reject (red) or Approve (green) button. Moderators can also click on an image to view further details, see a larger view and approve / reject the image in expanded mode.

Pending moderation photo

The list of approved images can be viewed from the same web interface, and moderators can change their minds at any time, and choose to reject a previously-approved image. Note that it can take up to ten minutes for cached copies to be updated.

Approved images queue

Rejected images are still saved in your cloud-based media library, using Cloudinary’s backup repository. Only the moderator can access and view the original uploaded images. If the moderator changes his mind, the originally rejected images will be recovered from backup and made available for delivery on your site.

Rejected images queue

Rejected image

You can add a notification_url parameter while uploading the image, which instructs Cloudinary to notify your application of changes to moderation status, at this point your application will receive a notification with the details of the moderation event (approval or rejection).

Replacing a rejected images with a placeholder image

When an image is rejected, you can use Cloudinary’s default image mechanism to display a placeholder image instead.

The default_image parameter (or d for URLs) allows you to specify a placeholder image to display, in case an image was rejected by your moderator.

The following sample code creates a 150x150 face-detection-based thumbnail of the uploaded images, then rounds the corner of the photos and increases color saturation. This is all done on-the-fly using Cloudinary’s image manipulation feature. The example below also specified an alternative image to show if the original has been actively rejected.

Ruby:
Copy to clipboard
cl_image_tag("j6ayuxwr76qwvyjioq7b.jpg", :width=>150, :height=>150, :gravity=>"face", :radius=>30, :effect=>"saturation:50", :default_image=>"avatar.png", :crop=>"fill")
PHP:
Copy to clipboard
cl_image_tag("j6ayuxwr76qwvyjioq7b.jpg", array("width"=>150, "height"=>150, "gravity"=>"face", "radius"=>30, "effect"=>"saturation:50", "default_image"=>"avatar.png", "crop"=>"fill"))
Python:
Copy to clipboard
CloudinaryImage("j6ayuxwr76qwvyjioq7b.jpg").image(width=150, height=150, gravity="face", radius=30, effect="saturation:50", default_image="avatar.png", crop="fill")
Node.js:
Copy to clipboard
cloudinary.image("j6ayuxwr76qwvyjioq7b.jpg", {width: 150, height: 150, gravity: "face", radius: 30, effect: "saturation:50", default_image: "avatar.png", crop: "fill"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().width(150).height(150).gravity("face").radius(30).effect("saturation:50").defaultImage("avatar.png").crop("fill")).imageTag("j6ayuxwr76qwvyjioq7b.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('j6ayuxwr76qwvyjioq7b.jpg', {width: 150, height: 150, gravity: "face", radius: 30, effect: "saturation:50", defaultImage: "avatar.png", crop: "fill"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("j6ayuxwr76qwvyjioq7b.jpg", {width: 150, height: 150, gravity: "face", radius: 30, effect: "saturation:50", default_image: "avatar.png", crop: "fill"})
React:
Copy to clipboard
<Image publicId="j6ayuxwr76qwvyjioq7b.jpg" >
  <Transformation width="150" height="150" gravity="face" radius="30" effect="saturation:50" defaultImage="avatar.png" crop="fill" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="j6ayuxwr76qwvyjioq7b.jpg" >
  <cld-transformation width="150" height="150" gravity="face" radius="30" effect="saturation:50" defaultImage="avatar.png" crop="fill" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="j6ayuxwr76qwvyjioq7b.jpg" >
  <cl-transformation width="150" height="150" gravity="face" radius="30" effect="saturation:50" default-image="avatar.png" crop="fill">
  </cl-transformation>
</cl-image>
.Net:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(150).Height(150).Gravity("face").Radius(30).Effect("saturation:50").DefaultImage("avatar.png").Crop("fill")).BuildImageTag("j6ayuxwr76qwvyjioq7b.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().width(150).height(150).gravity("face").radius(30).effect("saturation:50").defaultImage("avatar.png").crop("fill")).generate("j6ayuxwr76qwvyjioq7b.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setWidth(150).setHeight(150).setGravity("face").setRadius(30).setEffect("saturation:50").setDefaultImage("avatar.png").setCrop("fill")).generate("j6ayuxwr76qwvyjioq7b.jpg")!, cloudinary: cloudinary)
150x150 face detection based cropped thumbnail

Another face thumbnail

Placeholder thumbnail of a rejected image

Implementing pre/post-publishing moderation with Cloudinary

Does moderation need to occur before an image is published on the site, or can the image be published and visible in the meantime? The answer will determine your moderation workflow.

Post-publishing moderation is easier to implement, but can sometimes result in users seeing inappropriate or even problematic images until they are moderated.

Pre-publishing moderation is safer, but comes at the cost of users having to wait for their images to appear, and a higher cost of manpower, because moderation needs to happen urgently as images are uploaded.

With Cloudinary, you can choose the workflow that is most appropriate to your website.

Pre-publishing moderation

To implement pre-publishing moderation, it is recommended to use Cloudinary’s randomly-generated public IDs. As you can see in the response code above, Cloudinary returns a long URL with a random ID which is difficult to guess. This URL is publically-accessible, and you can use it to show the uploaded image to the user who uploaded the photo.

As for other visitors of your site, you can simply code your backend so it doesn’t display an image until it is set as “moderated”, and they won’t stumble upon it accidentally due to the long ‘unguessable’ URL.

If you want to completely hide images while they are under moderation, you can use our private images feature, detailed in this blog post.

Post-publishing moderation

To implement post-publishing moderation, you can simply take the publically-available URL returned by Cloudinary (or specify a more friendly URL) and display the image on the relevant page of your site.

When your moderators rejects and image, you’ll be able to catch this event and handle it accordingly.

Build your own image moderation interface

Do you want to build your own image moderation interface, or already have one in place? You can use Cloudinary’s Admin API to easily integrate your custom interface with Cloudinary’s image upload, storage, CDN and moderation workflow.

The following sample code calls Cloudinary’s Admin API for listing all pending images waiting for manual moderation:

Ruby:
Copy to clipboard
list = Cloudinary::Api.resources_by_moderation("manual", "pending")
PHP:
Copy to clipboard
$api = new \Cloudinary\Api();
$list = $api->resources("manual", "pending");
Python:
Copy to clipboard
list = cloudinary.api.resources_by_moderation("manual", "pending")
Node.js:
Copy to clipboard
cloudinary.api. resources_by_moderation("manual", "pending",
  function(result) { console.log(result) });
Java:
Copy to clipboard
Cloudinary cloudinary = new Cloudinary(config);

ApiResponse result = cloudinary.api(). resourcesByModeration(
  "manual", "pending",  Cloudinary.emptyMap());

Here’s a sample output result:

Copy to clipboard
    {"resources"=>
     [{
       "public_id"=>"omjisa17oaih05me4k4h", "format"=>"jpg",  
       "version"=>1398848978,
       "resource_type"=>"image", "type"=>"upload", 
       "created_at"=>"2014-04-30T09:09:38Z",
       "bytes"=>87932, "width"=>849, "height"=>565, "backup"=>true,
       "url"=>"https://.../image/upload/v1398848978/omjisa17oaih05me4k4h.jpg",
       "secure_url"=>"https://.../image/upload/v1398848978/omjisa17oaih05me4k4h.jpg"
      }, 
     {
       "public_id"=>"j6ayuxwr76qwvyjioq7b", "format"=>"jpg", 
       "version"=>1398848970, 
       "resource_type"=>"image", "type"=>"upload",
       "created_at"=>"2014-04-30T09:09:30Z",
       "bytes"=>43214, "width"=>390, "height"=>500, "backup"=>true,
       "url"=>"https://.../image/upload/v1398848970/j6ayuxwr76qwvyjioq7b.jpg",     
       "secure_url"=>"https://.../image/upload/v1398848970/j6ayuxwr76qwvyjioq7b.jpg"
      }, 
      ...
    ]}

You can then use the Admin’s API update method to approve or reject images. The following sample code shows an approval request of a pending image with ID j6ayuxwr76qwvyjioq7b:

Copy to clipboard
Cloudinary::Api.update("j6ayuxwr76qwvyjioq7b", :moderation_status => "approved")

Same goes for the rejection of pending images:

Copy to clipboard
Cloudinary::Api.update("uznjsi2nzcbwqzacym7c", :moderation_status => "rejected")

A notification callback (HTTP post) will be sent to your web application for each approval or rejection event, if you specified the notification_url while uploading images using the API.

You can also use the API to list images that were approved or rejected. The output of these methods has the same format as the pending queue listing shown above. You can allow moderators to revisit their decision regarding the approved or rejected images, and you can call the update method again for the same images, based on their public_id value, to change their moderation status.

The following sample code shows how to list approved and rejected images:

Copy to clipboard
approved_list = Cloudinary::Api.resources_by_moderation("manual", "approved")

rejected_list = Cloudinary::Api.resources_by_moderation("manual", "rejected")

Summary

Moderation of user contributed content can be critical for modern websites and mobile applications. Cloudinary’s image management solution makes it easy to implement an image moderation workflow on your service, while optimizing storage and delivery of images, and enabling easy on-the-fly manipulation of images as well. We also showed how Cloudinary’s powerful Admin API can be used to create a custom image moderation interface.

In this post we covered manual moderation by trained staff, which can ensure almost 100% accuracy, but might be time consuming. Cloudinary also supports automatic image moderation - stay tuned for an upcoming blog post discussing this feature.

The image moderation features discussed in this post, including the user interface and all API methods, are available for all Cloudinary plans, including the free tier. Try it out now by creating a free account. If you have any feedback or ideas on our manual image moderation, you’re most welcome to contact us, or add a comment at the bottom of this post.

Recent Blog Posts

A New, Simple Tool for Creating Text Overlays for Images

Many Cloudinary users desire a UI for tasks like creating text overlays for images, which they then embed on webpages or download for marketing campaigns. Generating such overlays with the Cloudinary Media Library UI involves a bit of a learning curve, especially if they require multiple fonts or text lines, which even experienced users might find challenging to implement.

Read more
Transitioning JPEG-Based to JPEG XL-Based Images for Web Platforms

When the JPEG codec was being developed in the late 1980s, no standardized, lossy image-compression formats existed. JPEG became ready at exactly the right time in 1992, when the World Wide Web and digital cameras were about to become a thing. The introduction of HTML’s <img> tag in 1995 ensured the recognition of JPEG as the web format—at least for photographs. During the 1990s, digital cameras replaced analog ones and, given the limited memory capacities of that era, JPEG became the standard format for photography, especially for consumer-grade cameras.

Read more

Amplify Your Jamstack With Video

By Alex Patterson
Amplify Your Jamstack With Cloudinary Video

As defined by Amazon Web Services (AWS), Amplify is a set of products and tools with which mobile and front-end web developers can build and deploy AWS-powered, secure, and scalable full-stack apps. Also, you can efficiently configure their back ends, connect them to your app with just a few lines of code, and deploy static web apps in only three steps. Historically, because of their performance issues, managing images and videos is a daunting challenge for developers. Even though you can easily load media to an S3 bucket with AWS Amplify, transforming, compressing, and responsively delivering them is labor intensive and time consuming.

Read more
Cloudinary Helps Move James Hardie’s Experience Online

While COVID has affected most businesses, it has been particularly hard on those that sell products for the physical ‘brick and mortar’ world. One company that literally fits that bill is our Australian customer James Hardie, the largest global manufacturer of fibre cement products used in both domestic and commercial construction. These are materials that its buyers ideally want to see up close, in detail. When customers have questions, they expect personal service.

Read more