{"id":34851,"date":"2024-07-25T07:00:00","date_gmt":"2024-07-25T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=34851"},"modified":"2025-11-26T18:21:13","modified_gmt":"2025-11-27T02:21:13","slug":"build-an-image-gallery-webpurify-moderation-notification","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification","title":{"rendered":"Build an Image Gallery With WebPurify Moderation and Notification"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>In this blog post, we\u2019ll explore how to create a public image gallery that allows users to upload their own images. This solution is ideal for websites focusing on <a href=\"https:\/\/cloudinary.com\/documentation\/user_generated_content\">user-generated content<\/a> (UGC) because it leverages Cloudinary\u2019s WebPurify moderation add-on service to ensure uploaded images meet content guidelines. This approach encourages community engagement by allowing user contributions and ensures the content meets quality and suitability standards. We\u2019ll cover every step, from setting up the project to handling notifications for image moderation status.<\/p>\n<h2>Project Overview<\/h2>\n<p>Our public image gallery allows users to upload images with Cloudinary, which are then sent to Cloudinary\u2019s WebPurify add-on for moderation. Users receive real-time notifications about their image\u2019s status: pending, approved, or rejected. Additionally, users can check for rejected images by clicking a button that lists their IDs.<\/p>\n<p>The project is built using Python with Flask, along with SocketIO and a few lines of JavaScript for the toast notifications.<\/p>\n<h3>Key Features<\/h3>\n<ol>\n<li>\n<strong>Image upload<\/strong>. Users can upload images to the gallery.<\/li>\n<li>\n<strong>Moderation<\/strong>. Images are moderated using Cloudinary\u2019s WebPurify add-on service.<\/li>\n<li>\n<strong>Real-time notifications<\/strong>. Users are notified of their image\u2019s status via toast notifications.<\/li>\n<li>\n<strong>Rejected images listing<\/strong>. Users can view IDs of rejected images.<\/li>\n<\/ol>\n<h2>Setting Up the Project<\/h2>\n<ol>\n<li>If you haven\u2019t yet done so, <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up for Cloudinary<\/a>  <a href=\"https:\/\/cloudinary.com\/users\/register_free\">here<\/a>.<\/li>\n<li>Install Cloudinary and the other necessary packages:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">pip install flask python-dotenv cloudinary flask_socketio\n<\/code><\/span><\/pre>\n<ol start=\"3\">\n<li>Configure Cloudinary<\/li>\n<\/ol>\n<ul>\n<li>In your project, create a file called  <code>.env<\/code>  containing your API key, API secret and cloud name, as shown in the example. You can find these in your Cloudinary account, by navigating to the Programmable Media dashboard, and clicking on \u2018Go to API Keys\u2019.<\/li>\n<\/ul>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">\n<span class=\"hljs-comment\"># Copy and paste your API environment variable.<\/span>\n\n<span class=\"hljs-comment\"># =============================================<\/span>\n\nCLOUDINARY_URL=cloudinary:<span class=\"hljs-comment\">\/\/&lt;api_key&gt;:&lt;api_secret&gt;@&lt;cloud_name&gt;<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ol start=\"4\">\n<li>In your project, create a new file called  <code>app.py<\/code>, then copy and paste the following into this file to configure Cloudinary for the project:<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">from<\/span>  dotenv  <span class=\"hljs-keyword\">import<\/span>  load_dotenv\n<span class=\"hljs-keyword\">from<\/span>  flask  <span class=\"hljs-keyword\">import<\/span>  Flask, render_template, request, redirect, url_for, jsonify\n<span class=\"hljs-keyword\">from<\/span>  flask_socketio  <span class=\"hljs-keyword\">import<\/span>  SocketIO\n<span class=\"hljs-keyword\">from<\/span>  cloudinary  <span class=\"hljs-keyword\">import<\/span>  CloudinaryImage\n<span class=\"hljs-keyword\">import<\/span>  cloudinary\n<span class=\"hljs-keyword\">import<\/span>  cloudinary.api\n<span class=\"hljs-keyword\">import<\/span>  cloudinary.uploader\n\nload_dotenv()\napp = Flask(__name__)\nsocketio = SocketIO(app)\nconfig=cloudinary.config(secure=True)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<ol start=\"5\">\n<li>To use WebPurify, you can subscribe to it from the <a href=\"https:\/\/cloudinary.com\/addons\">Add-ons menu<\/a>.<\/li>\n<\/ol>\n<h2>Reviewing the Code<\/h2>\n<p>We\u2019ll review the different parts of the code and explain the functionality.<\/p>\n<h3>Uploading an Image With Moderation<\/h3>\n<p>We\u2019ll upload an image from our computer and apply the moderation functionality.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-keyword\">@app<\/span>.route('\/upload', methods=&#91;<span class=\"hljs-string\">'POST'<\/span>])\ndef  upload():\n\tfiles_to_upload = request.files.getlist(<span class=\"hljs-string\">'file'<\/span>)\n\tfor  file_item  in  files_to_<span class=\"hljs-attribute\">upload:<\/span>\n\t\tif  file_<span class=\"hljs-attribute\">item:<\/span>\n\t\t\tcloudinary.uploader.upload(file_item,\n\t\t\t\t\t\t\t\t\t   folder = <span class=\"hljs-string\">\"public_gallery\"<\/span>,\n\t\t\t\t\t\t\t\t\t   moderation = <span class=\"hljs-string\">\"webpurify\"<\/span>,\n\t\t\t\t\t\t\t\t\t   notification_url = <span class=\"hljs-string\">'your_ngrok_url\/status_notification'<\/span>)\n\n\treturn  redirect(url_for(<span class=\"hljs-string\">'index'<\/span>))\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Using Cloudinary\u2019s upload method, we can define some settings while uploading the file.<\/p>\n<p>The <code>folder<\/code> part refers to what folder we\u2019d like to upload our images. In the <code>moderation<\/code> parameter, we have to specify that we\u2019d like to use the WebPurify add-on. To get notifications about the moderation status of an uploaded image, add the <code>notification_url<\/code> parameter. For this example, we\u2019ll use <a href=\"https:\/\/ngrok.com\">Ngrok<\/a> to help us expose the appropriate endpoint running within our Flask app to the internet, so the webhook notification can be handled.<\/p>\n<h3>Display the Uploaded Images and Apply Transformation<\/h3>\n<p>To get all the images from a specific folder, and to apply some transformations, we\u2019ll use the following code:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">def  get_transformed_images():\n\tall_images = cloudinary.Search().expression(<span class=\"hljs-string\">'folder:public_gallery'<\/span>).sort_by(<span class=\"hljs-string\">'public_id'<\/span>, <span class=\"hljs-string\">'desc'<\/span>).max_results(<span class=\"hljs-number\">10<\/span>).execute()\n\ttransformed_images = &#91;]\n\t<span class=\"hljs-keyword\">for<\/span>  item  <span class=\"hljs-keyword\">in<\/span>  all_images&#91;<span class=\"hljs-string\">\"resources\"<\/span>]:\n\t\tpublic_id = item&#91;<span class=\"hljs-string\">'public_id'<\/span>]\n\t\tcloudinary_image = CloudinaryImage(public_id)\n\t\timage_url = cloudinary_image.build_url(transformation=&#91;\n\t\t\t{<span class=\"hljs-string\">'width'<\/span>: <span class=\"hljs-number\">400<\/span>, <span class=\"hljs-string\">'height'<\/span>: <span class=\"hljs-number\">400<\/span>, <span class=\"hljs-string\">'crop'<\/span>: <span class=\"hljs-string\">'fill'<\/span>},\n\t\t\t{<span class=\"hljs-string\">'fetch_format'<\/span>: <span class=\"hljs-string\">'auto'<\/span>, <span class=\"hljs-string\">'quality'<\/span>: <span class=\"hljs-string\">'auto'<\/span>}\n\t\t])\n\t\ttransformed_images.append(image_url)\n\t<span class=\"hljs-keyword\">return<\/span>  transformed_images\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This function searches through our folder called <code>public_library<\/code> and grabs <code>10<\/code> images from the folder, which are sorted in descending order by the <code>public_id<\/code> of the images. This means that only 10 images will be displayed on the site from the specified folder.<\/p>\n<p>In the for loop, we\u2019ll apply transformations that define that the images have to be cropped to fill a 400 pixels wide and 400 pixels height square. The <code>'fetch_format': 'auto'<\/code> and the <code>'quality': 'auto'<\/code> refers to an optimisation method, where the image will be shown to the user in the best format appropriate for the requesting browser, and the quality of the image will be reduced in a non-visible way to decrease the file size.<\/p>\n<h3>Moderation Status Notification<\/h3>\n<p>While uploading an image to Cloudinary using WebPurify, the image has to go through moderation first. You can check the status of the moderation in your Media Library by clicking on \u2018Moderation\u2019 in the top menu and changing the view from \u2018Manual\u2019 to \u2018WebPurify\u2019.\nTo get notifications about the status of the uploaded image, we\u2019ll use Socket.IO to enable communication between the web client and the server, effectively subscribing to real-time notifications via the websocket protocol.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">@app.route(<span class=\"hljs-string\">'\/status_notification'<\/span>, methods=&#91;<span class=\"hljs-string\">'POST'<\/span>])\ndef  status_notification():\n\tdata = request.get_json(silent=<span class=\"hljs-keyword\">True<\/span>)\n\t<span class=\"hljs-keyword\">if<\/span>  data  is  None:\n\t\t<span class=\"hljs-keyword\">return<\/span>  jsonify({<span class=\"hljs-string\">\"status\"<\/span>: <span class=\"hljs-string\">\"No data received\"<\/span>}), <span class=\"hljs-number\">400<\/span>\n\n<span class=\"hljs-comment\"># Check for 'moderation' key in data<\/span>\n\t<span class=\"hljs-keyword\">if<\/span>  <span class=\"hljs-string\">'moderation'<\/span>  in  data:\n\t\tstatus = data&#91;<span class=\"hljs-string\">'moderation'<\/span>]&#91;<span class=\"hljs-number\">0<\/span>].get(<span class=\"hljs-string\">'status'<\/span>)\n\t\tsocketio.emit(<span class=\"hljs-string\">'moderation'<\/span>, {<span class=\"hljs-string\">'status'<\/span>: <span class=\"hljs-string\">'pending'<\/span>})\n\t\t\n<span class=\"hljs-comment\"># Check for 'approved' status<\/span>\n\telif (data&#91;<span class=\"hljs-string\">'moderation_status'<\/span>] == <span class=\"hljs-string\">'approved'<\/span>):\n\t\tsocketio.emit(<span class=\"hljs-string\">'moderation'<\/span>, {<span class=\"hljs-string\">'status'<\/span>: <span class=\"hljs-string\">'approved'<\/span>})\n<span class=\"hljs-comment\"># Check for 'rejected' status<\/span>\n\t<span class=\"hljs-keyword\">else<\/span>:\n\t\tsocketio.emit(<span class=\"hljs-string\">'moderation'<\/span>, {<span class=\"hljs-string\">'status'<\/span>: <span class=\"hljs-string\">'rejected'<\/span>})\n\t\t\n\t<span class=\"hljs-keyword\">return<\/span>  jsonify({<span class=\"hljs-string\">\"status\"<\/span>: <span class=\"hljs-string\">\"Received\"<\/span>}), <span class=\"hljs-number\">200<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In this code, we\u2019ll look through the data to find the status of the uploaded images. First, we\u2019ll identify the <code>pending<\/code> status in our data. In the <code>elif<\/code> part of the function, look for the <code>approved<\/code> status, and in the <code>else<\/code> part for the <code>rejected<\/code> status. When any of these three statuses have been found, the server will emit a message via SocketIO, which the client listens for. Based on the emitted events, we can enable the following actions in the browser:\nSocketIO will trigger the following script in our HTML file:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-comment\">\/\/ Status notification<\/span>\n\t<span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">'DOMContentLoaded'<\/span>, () =&gt; {\n\t\t<span class=\"hljs-keyword\">const<\/span>  socket = io();\n\t\t\t\tsocket.on(<span class=\"hljs-string\">'moderation'<\/span>, (data) =&gt; {\n\t\t\t<span class=\"hljs-keyword\">const<\/span>  toastEl = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'liveToast'<\/span>);\n\t\t\t<span class=\"hljs-keyword\">const<\/span>  toastBody = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'toastBody'<\/span>);\n\t\t\t<span class=\"hljs-keyword\">if<\/span> (data.status === <span class=\"hljs-string\">'pending'<\/span>) {\n\t\t\t\ttoastBody.innerHTML = <span class=\"hljs-string\">`&lt;div class=\"alert alert-info\" role=\"alert\"&gt;\n\t\t\t\t\t'Your photo is pending for moderation'\n\t\t\t\t  &lt;\/div&gt;`<\/span>\n\t\t\t} <span class=\"hljs-keyword\">else<\/span>  <span class=\"hljs-keyword\">if<\/span> (data.status === <span class=\"hljs-string\">'approved'<\/span>) {\n\t\t\t\ttoastBody.innerHTML = <span class=\"hljs-string\">`&lt;div class=\"alert alert-success\" role=\"alert\"&gt;\n\t\t\t\t\t'Your photo has been approved'\n\t\t\t\t  &lt;\/div&gt;`<\/span>\n\t\t\t} <span class=\"hljs-keyword\">else<\/span>  <span class=\"hljs-keyword\">if<\/span> (data.status === <span class=\"hljs-string\">'rejected'<\/span>) {\n\t\t\t\ttoastBody.innerHTML = <span class=\"hljs-string\">`&lt;div class=\"alert alert-danger\" role=\"alert\"&gt;\n\t\t\t\t\t'Your photo has been rejected'\n\t\t\t\t  &lt;\/div&gt;`<\/span>\n\t\t\t}\n\t\t\t<span class=\"hljs-keyword\">new<\/span>  bootstrap.Toast(toastEl).show();\n\t\t})\n\t})\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This script brings up the actual notification on our site using <a href=\"https:\/\/getbootstrap.com\/docs\/4.3\/components\/toasts\/\">Bootstrap\u2019s toast notifications<\/a>. The code will be activated from our <code>app.py<\/code> file, using SocketIO.<\/p>\n<p>As soon as the image is uploaded, you\u2019ll get a \u201cYour photo is pending for moderation\u201d message:\n<img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209955\/blog-Build_an_Image_Gallery_With_WebPurify_Moderation_and_Notification-1.webp\" alt=\"Pending for moderation\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"3780\" height=\"1720\"\/><\/p>\n<p>After a few minutes, when the image went through the moderation, the notification will appear again, with either a rejected or an approved message.\n<img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209956\/blog-Build_an_Image_Gallery_With_WebPurify_Moderation_and_Notification-2.webp\" alt=\"Approved notification\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"3802\" height=\"1744\"\/>\n<img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209958\/blog-Build_an_Image_Gallery_With_WebPurify_Moderation_and_Notification-3.webp\" alt=\"Rejected notification\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"3802\" height=\"1744\"\/><\/p>\n<h3>List the ID of the Rejected Images<\/h3>\n<p>Once an image is rejected, you can still get some information about it, like listing the IDs of the rejected images. To get the IDs, the following code will be used:<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">@app.route(<span class=\"hljs-string\">'\/rejected_images'<\/span>, methods=&#91;<span class=\"hljs-string\">'GET'<\/span>, <span class=\"hljs-string\">'POST'<\/span>])\ndef  rejected_images():\n\trejected_images = &#91;]\n\t<span class=\"hljs-keyword\">if<\/span>  request.method == <span class=\"hljs-string\">'POST'<\/span>:\n\t\tresults = cloudinary.api.resources_by_moderation(<span class=\"hljs-string\">\"webpurify\"<\/span>, <span class=\"hljs-string\">\"rejected\"<\/span>)\n\t\t<span class=\"hljs-keyword\">for<\/span>  img  <span class=\"hljs-keyword\">in<\/span>  results&#91;<span class=\"hljs-string\">'resources'<\/span>]:\n\t\timage_info = img&#91;<span class=\"hljs-string\">'public_id'<\/span>].split(<span class=\"hljs-string\">'\/'<\/span>)&#91;<span class=\"hljs-number\">-1<\/span>]\n\t\trejected_images.append(image_info)\n\n\ttransformed_images = get_transformed_images()\n\t<span class=\"hljs-keyword\">return<\/span>  render_template(<span class=\"hljs-string\">'index.html'<\/span>, transformed_images=transformed_images, rejected_images=rejected_images)\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This will check all moderated images, and will give back a list with the image IDs of the rejected ones. From here we can easily add these to our site on request.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764209960\/blog-Build_an_Image_Gallery_With_WebPurify_Moderation_and_Notification-4.webp\" alt=\"Rejected images\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"3802\" height=\"1744\"\/><\/p>\n<h2>Conclusion<\/h2>\n<p>Building an image gallery with Cloudinary\u2019s WebPurify moderation and notifications is a good way to protect your UG app from inappropriate images. With this feature, we can ensure that all uploaded images meet our content guidelines. The combination of Flask, SocketIO, and Bootstrap allows us to create an interactive user interface that notifies users of their image status promptly. Whether you\u2019re looking to add moderation to an existing project or build a new image gallery from scratch, we hope this post shows how you can integrate these technologies efficiently. You can find the working demo in <a href=\"https:\/\/github.com\/kemeb\/cloudinary-moderation\">this<\/a> Github repository.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":34853,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[370],"class_list":["post-34851","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-image"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build an Image Gallery With WebPurify Moderation and Notification<\/title>\n<meta name=\"description\" content=\"Create a public image gallery with Cloudinary&#039;s WebPurify moderation add-on service to ensure uploaded images meet content guidelines.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build an Image Gallery With WebPurify Moderation and Notification\" \/>\n<meta property=\"og:description\" content=\"Create a public image gallery with Cloudinary&#039;s WebPurify moderation add-on service to ensure uploaded images meet content guidelines.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-07-25T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-27T02:21:13+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Build an Image Gallery With WebPurify Moderation and Notification\",\"datePublished\":\"2024-07-25T14:00:00+00:00\",\"dateModified\":\"2025-11-27T02:21:13+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\"},\"wordCount\":9,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA\",\"keywords\":[\"Image\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2024\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\",\"url\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\",\"name\":\"Build an Image Gallery With WebPurify Moderation and Notification\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA\",\"datePublished\":\"2024-07-25T14:00:00+00:00\",\"dateModified\":\"2025-11-27T02:21:13+00:00\",\"description\":\"Create a public image gallery with Cloudinary's WebPurify moderation add-on service to ensure uploaded images meet content guidelines.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build an Image Gallery With WebPurify Moderation and Notification\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build an Image Gallery With WebPurify Moderation and Notification","description":"Create a public image gallery with Cloudinary's WebPurify moderation add-on service to ensure uploaded images meet content guidelines.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification","og_locale":"en_US","og_type":"article","og_title":"Build an Image Gallery With WebPurify Moderation and Notification","og_description":"Create a public image gallery with Cloudinary's WebPurify moderation add-on service to ensure uploaded images meet content guidelines.","og_url":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification","og_site_name":"Cloudinary Blog","article_published_time":"2024-07-25T14:00:00+00:00","article_modified_time":"2025-11-27T02:21:13+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Build an Image Gallery With WebPurify Moderation and Notification","datePublished":"2024-07-25T14:00:00+00:00","dateModified":"2025-11-27T02:21:13+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification"},"wordCount":9,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","keywords":["Image"],"inLanguage":"en-US","copyrightYear":"2024","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification","url":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification","name":"Build an Image Gallery With WebPurify Moderation and Notification","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","datePublished":"2024-07-25T14:00:00+00:00","dateModified":"2025-11-27T02:21:13+00:00","description":"Create a public image gallery with Cloudinary's WebPurify moderation add-on service to ensure uploaded images meet content guidelines.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/build-an-image-gallery-webpurify-moderation-notification#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Build an Image Gallery With WebPurify Moderation and Notification"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1721674661\/Moderation_WebPurify-blog\/Moderation_WebPurify-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34851","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=34851"}],"version-history":[{"count":4,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34851\/revisions"}],"predecessor-version":[{"id":39446,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/34851\/revisions\/39446"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/34853"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=34851"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=34851"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=34851"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}