Reviewing user-supplied media before it becomes publicly visible — either by hand or with an automatic moderation add-on.
<?php
require 'vendor/autoload.php';
use Cloudinary\Cloudinary;
$cloudinary = new Cloudinary();
$result = $cloudinary->uploadApi()->upload(
'https://res.cloudinary.com/demo/image/upload/sample.jpg',
[
'public_id' => 'docs/needs-review',
'moderation' => 'manual',
]
);
echo json_encode($result['moderation']), PHP_EOL;
// [{"kind":"manual","status":"pending"}]Runnable version: examples/moderate-upload.php.
This trips people up. The upload response carries a moderation array only:
$result['moderation']; // [['kind' => 'manual', 'status' => 'pending']]
$result['moderation_status']; // not setThe Admin API returns both:
$asset = $cloudinary->adminApi()->asset('docs/needs-review');
$asset['moderation']; // [['kind' => 'manual', 'status' => 'pending']]
$asset['moderation_status']; // 'pending'Read moderation[0]['status'] if you have an upload response; read either if you fetched
the asset.
| Field | Meaning |
|---|---|
moderation[].kind |
Which moderation performed the check — manual, or an add-on name. |
moderation[].status |
pending, approved, or rejected. |
moderation_status |
Same status, flattened. Admin API responses only. |
public_id |
Needed to approve or reject later. |
pending is a moderation state, not a guaranteed access state. Whether a pending asset is
publicly deliverable is governed by a product-environment setting, so it differs between
accounts — on many it is deliverable while awaiting review, which surprises people.
Do not rely on moderation as an access-control mechanism in either direction. Verify the
behaviour on your own environment, and if content must not be reachable before review,
enforce that explicitly: upload as
'type' => 'private' or into a
restricted folder, then publish after approval. The same applies to what the asset looks
like in the Media Library versus on the CDN — those are separate surfaces.
// Approve.
$cloudinary->adminApi()->update('docs/needs-review', [
'moderation_status' => 'approved',
]);
// Reject.
$cloudinary->adminApi()->update('docs/needs-review', [
'moderation_status' => 'rejected',
]);How each state maps to delivery is again environment-configurable — commonly rejected
is taken out of delivery and approved stays, but confirm it on your environment rather
than assuming it.
$pending = $cloudinary->adminApi()->assetsByModeration('manual', 'pending', [
'max_results' => 20,
]);
foreach ($pending['resources'] as $asset) {
echo $asset['public_id'], PHP_EOL;
}'moderation' => 'manual' needs no add-on. Automatic kinds — AI-based visual moderation,
perceptual duplicate detection — must be enabled on your account first, which the account
owner does in the Console; some add-ons also require accepting the provider's terms of
service before the first call will succeed. An agent cannot do either step: if the call
fails for this reason, tell the user what to enable rather than retrying.
The available kinds and their provider-specific response shapes are listed in moderation add-ons.
Because the verdict is model output, assert on the shape of the response — that a status exists and is one of the expected values — not on a specific verdict for a given image.
| Symptom | Cause |
|---|---|
$result['moderation_status'] is undefined |
Expected on upload responses; read moderation[0]['status'], or fetch via adminApi()->asset(). |
AuthorizationRequired on an automatic kind |
The moderation add-on is not enabled for the account. |
RateLimited when barely sending requests |
Unsubscribed add-ons can surface as rate-limit errors. |
| Asset publicly reachable while pending | By design — moderation does not gate delivery. |
- Upload an image
- Search and manage assets
- What this SDK does and does not do
- Moderation add-ons — the
kinds beyond
manual, and what each returns.