When working on an e-commerce platform, handling image uploads and optimizing visual content can quickly become overwhelming. Cloudinary’s robust platform not only simplifies these tasks but also adds AI-powered features to enhance your workflows. This blog post walks you through building a dynamic e-commerce demo with PHP and Cloudinary, focusing on client-side uploads, AI-driven alt text, and metadata management.
Here’s a diagram of the image workflow implemented in the PHP Product Catalog app:
As you read, think about which aspects this AI image workflows with PHP and Cloudinary might best suit your own app as we explore how it integrates seamlessly into this Product Catalog demo app.
If you’re also interested in video workflows, be sure to check out the next post in this series, where we’ll dive into optimizing video uploads, including asynchronous processes and AI-driven moderation.
The PHP Product Catalog app showcases a simulated product catalog with products stored in a database. Each product includes a name, metadata (SKU, price, and category), auto-generated description, image, and video. For details on the video upload-to-delivery workflow, check out the next post in this series.
With this app, you can:
- Add new products with images.
- View all products or individual product details.
- Edit product metadata and media.
See the app’s image features in action:
To follow along with this post, make sure you:
- Sign up for a Cloudinary account and try it for free!
- Find the full code for this app on GitHub and follow the README for setup instructions.
Cloudinary’s Upload widget streamlines image uploads directly from the browser, allowing users to upload images effortlessly.
- Add the Upload widget script to your HTML:
<!-- Include the Cloudinary Upload Widget library -->
<script src="https://upload-widget.cloudinary.com/global/all.js"></script>
Code language: HTML, XML (xml)
- Create a button to trigger the widget and include the JavaScript logic, and hidden inputs to received the data:
<div style="display:flex;margin-bottom:10px;">
<button type="button" id="upload_image_button">Upload Image</button>
<input type="hidden" name="image_url" id="image_url">
<input type="hidden" name="image_id" id="image_id">
</div>
Code language: HTML, XML (xml)
- Javascript logic:
- Specify the
cloudName
of your product environment (found on your Cloudinary dashboard). - Specify an unsigned upload preset — a collection of predefined settings applied during uploads — configured in your environment. (We’ll take a closer look at our
php_demo_preset
upload preset in the next section.) - Display a preview of the uploaded image, enhancing user experience.
- Specify the
<script>
// Configure the upload widget for images
const imageWidget = cloudinary.createUploadWidget({
cloudName: '<?php echo $_ENV['CLOUDINARY_CLOUD_NAME']; ?>', // Replace with your Cloudinary cloud name
uploadPreset: 'php_demo_preset', // Replace with your upload preset
sources: ['local', 'url'], // Allow uploads from local files and URLs
resourceType: 'image', // Specify resource type as image
maxFileSize: 5000000, // Set a max file size (optional)
folder: 'products/images', // Optional folder path
}, (error, result) => {
if (!error && result && result.event === "success") {
console.log('Image uploaded successfully:', result.info.secure_url);
document.getElementById('image_url').value = result.info.secure_url;
document.getElementById('image_id').value = result.info.public_id;
// Display the image preview
const imageSection = document.getElementById("image_section");
imageSection.innerHTML = `
<label>Image:</label>
<div>
<img src="${result.info.secure_url}" alt="New Product Image" style="max-width: 200px; height: auto; margin-bottom: 15px;">
</div>
`;
}
});
// Open the image upload widget
document.getElementById('upload_image_button').addEventListener('click', () => {
imageWidget.open();
});
</script>
Code language: HTML, XML (xml)
Alt text enhances both accessibility and SEO, making your website more inclusive and discoverable by search engines. With Cloudinary’s AI Content Analysis add-on, you can automatically generate descriptive alt text for uploaded images.
To enable this feature with the Upload widget, configure a preset to include the AI Content Analysis add-on. For example, when creating an upload preset (e.g., php_demo_preset
), add "detection" => "captioning"
to the preset settings. This activates the add-on for all uploads using this preset. In our app, the script to configure the php_demo_preset
is triggered when clicking the first-time setup button from the main page or the app.
Here’s the code to create the upload preset:
try {
// Create upload preset if it doesn't yet exist.
$result = $api
->createUploadPreset([
"name" => "php_demo_preset",
"unsigned" => true,
"tags" => "php_demo",
"detection" => "captioning"
]);
} catch (ApiError $e) {
echo 'API Error : ' . $e->getMessage();
} catch (Exception $e) {
echo 'Error: ' . $e->getMessage();
}
Code language: PHP (php)
Structured metadata enhances asset management, allowing quick searches, categorization, and handling by attributes like SKU or license restrictions. In the PHP Product Catalog app, metadata such as description, SKU, category, and price is added to each image.
- First, when you click the first-time setup button, the app creates your metadata fields (Description, Price, SKU, and Category), if they don’t already exist in your app. Save the external IDs for all these fields in an array for later use.
$allFieldsResponse = $api->listMetadataFields();
$allFields = $allFieldsResponse['metadata_fields'] ?? [];
$externalIds=[];
$exists=false;
try {
$newLabel = "Description";
// Check if the field already exists. If so, save its external_id in an array.
$exists = checkAndAppendExternalId($allFields, $newLabel, $externalIds);
// If the field doesn't exist, create it. Save its external_id in an array.
if (!$exists) {
// Prepare and add a "String" metadata field (e.g., Description)
$stringMetadataField = new StringMetadataField($noExternalId);
$stringMetadataField->setLabel($newLabel);
$stringMetadataField->setMandatory(true); // Makes this field required
$stringMetadataField->setDefaultValue('Product description'); // Sets a default value
$newField = $api->addMetadataField($stringMetadataField);
$externalIds[] = [$newLabel => $newField['external_id']]; // Append key-value pair
echo "String metadata field added successfully.\n";
}
Code language: PHP (php)
- Then, when submitting a product, the app adds metadata to Cloudinary image, using the explicit method. Capture the generated alt text and metadata from the response:
<code>$cloudinary_result = $cld->uploadApi()->explicit($image_public_id, ["type"=>"upload","metadata" => $metadata]);</code>
Code language: PHP (php)
Here’s the full code snippet:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Capture metadata values entered in the form.
$description = $_POST['description'];
$name = $_POST['name'];
$sku = $_POST['sku'];
$price = $_POST['price'];
$category = $_POST['category'];
// Get external ids for metadata fields.
$allFieldsResponse = $api->listMetadataFields();
$allFields = $allFieldsResponse['metadata_fields'] ?? [];
$externalIds=[];
checkAndAppendExternalId($allFields, "Description", $externalIds);
checkAndAppendExternalId($allFields, "SKU", $externalIds);
checkAndAppendExternalId($allFields, "Price", $externalIds);
checkAndAppendExternalId($allFields, "Category", $externalIds);
// Set up metadata entries for submission to cloudinary
$metadata =
$externalIds['SKU'] . '=' . $sku . '|' .
$externalIds['Category'] . '=["' . $category . '"]|' .
$externalIds['Price'] . '=' . $price . '|' .
$externalIds['Description'] . '=' . $description;
if (!empty($_POST['image_url'])) {
$product_image_url = $_POST['image_url']; // Retrieve the secure URL from the form submission
$image_public_id = $_POST['image_id']; // Retrieve the public ID from the form submission
// Update metadata
$cloudinary_result = $cld->uploadApi()->explicit($image_public_id, ["type"=>"upload","metadata" => $metadata]);
$image_caption = $cloudinary_result['info']['detection']['captioning']['data']['caption'] ?? null; // Save the image alt text from the response
} else {
// If there's no image, set values to null.
$product_image_url = null;
$image_public_id = null;
$image_caption = null;
}
// Save the values in the database.
$product_id = saveProduct($pdo, $name, $product_image_url, $product_video_url, $image_public_id, $video_public_id, $video_moderation_status, $image_caption, $video_public_id_temp);
header("Location: products.php");
exit;
}
Code language: PHP (php)
Here’s how the metadata and alt text look rendered on our product:
When the product is submitted, store the image details in your database for seamless integration with your application’s backend:
// Save product information to the database
$product_id = saveProduct(
$pdo, $name, $product_image_url, $product_video_url,
$image_public_id, $video_public_id, $video_moderation_status,
$image_caption, $video_public_id_temp
);
header("Location: products.php");
exit;
}
Code language: PHP (php)
In the saveProduct
function, all relevant data — like $image_url
, $image_public_id
, and $image_caption
(alt text) — are saved to the database for easy retrieval later on.
function saveProduct(
$pdo, $name, $image_url, $video_url, $image_public_id,
$video_public_id, $video_moderation_status, $image_caption, $video_public_id_temp
) {
$stmt = $pdo->prepare(
"INSERT INTO products (name, product_image_url, product_video_url,
image_public_id, video_public_id, video_moderation_status, image_caption, video_public_id_temp)
VALUES (:name, :product_image_url, :product_video_url, :image_public_id,
:video_public_id, :video_moderation_status, :image_caption, :video_public_id_temp)"
);
$stmt->execute([
':name' => $name,
':product_image_url' => $image_url,
':product_video_url' => $video_url,
':image_public_id' => $image_public_id,
':video_public_id' => $video_public_id,
':video_moderation_status' => $video_moderation_status,
':image_caption' => $image_caption,
':video_public_id_temp' => $video_public_id_temp
]);
return $pdo->lastInsertId();
}
Code language: PHP (php)
So far, in our AI image workflows with PHP and Cloudinary, we’ve uploaded an image, stored its metadata, generated alt text, and saved everything in Cloudinary with a database reference for easy access. Now, let’s move to the next stage: delivering optimized and impactful images.
Cloudinary’s dynamic image transformations let you resize, crop, overlay logos, and more — all in real time. With AI-powered smart cropping, Cloudinary automatically adjusts focus to preserve essential details, even when resizing or cropping for different devices and formats.
Here’s an example from our PHP Product Catalog demo app. It showcases the original image alongside a smart-cropped version (500×500, focused on key details) with an added overlay.
These transformations highlight the flexibility of Cloudinary. Other possibilities include adjusting aspect ratios, applying filters, or using generative AI features like restoration and recoloring. Let’s break down the steps to apply these transformations.
First, fetch the product data, including the image’s public ID, from the database:
use Cloudinary\Api\Upload\UploadApi;
// Fetch all products from the database
$products = getAllProducts($pdo);
<?php foreach ($products as $product): ?>
<?php
// Process image and video URLs for each product
if ($product['image_public_id']) {
Code language: PHP (php)
Next, use the public ID to identify the current image, and use Resize::fill()
to dynamically resize and crop it. Save the delivery URL to the modified image in the variable $image_url
:
$image_url = $cld->image($product['image_public_id'])
->resize(
Resize::fill()
->width(500)
->height(500)
->gravity(Gravity::autoGravity())
Code language: PHP (php)
Explanation: Resize::fill()
resizes the image to 500×500 pixels, and Gravity::autoGravity()
ensures Cloudinary focuses on the most important area of the image, keeping the subject front and center.
Add an overlay to product images, such as a logo, watermark, or promotional text. Here’s how to overlay the Cloudinary logo:
->overlay(
Overlay::source(Source::image("cloudinary_logo")->resize(Resize::scale()->width(50)))
->position(
(new Position())
->gravity(Gravity::compass(Compass::northEast()))
->offsetX(10)
->offsetY(10)
)
)
->toUrl();
} else {
$image_url = null; // No image if not set
}
Code language: PHP (php)
Explanation: This transformation overlays the Cloudinary logo (with the public ID cloudinary_logo
), resized to 50px wide, positioned 10px from the top-right corner. You can replace the logo with another image or text overlay.
Cloudinary automatically delivers your images through a fast CDN, boosting load times and enhancing performance. Further enhance the user experience with auto-generated alt text and metadata, which can be easily retrieved to enrich your application’s functionality.
Here’s the code snippet to fetch metadata from an image stored in Cloudinary:
// Get the metadata from Cloudinary to display.
$metadata_result = $api->asset($product['image_public_id']);
$description = isset($metadata_result['metadata']['descriptionb9ZqP6J']) ? $metadata_result['metadata']['descriptionb9ZqP6J'] : 'No description available';
$price = isset($metadata_result['metadata']['priceF2vK8tA']) ? $metadata_result['metadata']['priceF2vK8tA'] : 0; // Default price
$sku = isset($metadata_result['metadata']['skuX78615h']) ? $metadata_result['metadata']['skuX78615h'] : 'Unknown SKU';
$category = isset($metadata_result['metadata']['category4gT7pV1'][0]) ? $metadata_result['metadata']['category4gT7pV1'][0] : 'clothes'; // Default category
$category_labels = [
'clothes' => 'Clothes',
'accessories' => 'Accessories',
'footwear' => 'Footwear',
'home_and_living' => 'Home & Living',
'electronics' => 'Electronics',
];
// Find the display text for the category using the mapping
$category_display = isset($category_labels[$category]) ? $category_labels[$category] : 'Unknown';
Code language: PHP (php)
Here’s the HTML to deliver the alt text, metadata. Use the delivery URL you just stored in the variable $image_url
to render the resized, cropped, and watermarked version of the image:
<div class="product-card">
<h2><?php echo htmlspecialchars($product['name']); ?></h2>
<div>
<p>Description: <?php echo htmlspecialchars($description); ?></p>
<p>SKU: <?php echo htmlspecialchars($sku); ?></p>
<p>Price: $<?php echo htmlspecialchars($price); ?></p>
<p>Category: <?php echo htmlspecialchars($category_display); ?></p>
</div>
<div class="product-image" style="position:relative;margin:10px auto;max-width:99%;border:1px solid grey;">
<!-- Display product image if available -->
<div>
<p><b>Image</b></p>
</div>
<?php if ($image_url): ?>
<img class="product-image" src="<?php echo $image_url; ?>" alt="<?php echo $product['image_caption']; ?>">
<?php else: ?>
<p>No image available.</p>
<?php endif; ?>
<div class="product-image" style="position:relative;max-width:230px;margin:0 auto;">
<p><b>Alt text:</b> <?php echo $product['image_caption']; ?></p>
</div>
</div>
Code language: JavaScript (javascript)
Here’s how the full product listing looks:
With Cloudinary’s robust suite of features, you can manage AI image workflows with PHP and Cloudinary, from upload to delivery. The ability to leverage AI for automation, apply dynamic transformations like smart cropping, and deliver optimized media through Cloudinary’s fast CDN can drastically enhance your app’s media experience.
Now that you’ve seen how Cloudinary streamlines the process for images, don’t miss the next post in this series, where we’ll explore how to handle video uploads and moderation seamlessly within the same PHP product catalog app. Be sure to sign up for a free Cloudinary account and start building powerful media workflows for your app today!