The Payload Cloudinary plugin allows you to add custom fields to your media collection. These fields are automatically merged with the default Cloudinary fields, giving you complete control over your media metadata.
Configuration
An array of Payload field configurations to add to the media collection. These fields will be merged with the default Cloudinary metadata fields.
Basic Usage
Add custom fields by including them in the customFields array:
import { cloudinaryStorage } from 'payload-cloudinary';
export default buildConfig({
plugins: [
cloudinaryStorage({
config: { /* ... */ },
collections: { 'media': true },
customFields: [
{
name: 'alt',
type: 'text',
label: 'Alt Text',
admin: {
description: 'Alternative text for accessibility',
},
},
{
name: 'caption',
type: 'text',
label: 'Caption',
},
],
})
]
});
Common Field Examples
Alt Text for Accessibility
{
name: 'alt',
type: 'text',
label: 'Alt Text',
admin: {
description: 'Alternative text for accessibility',
},
}
Caption Field
{
name: 'caption',
type: 'text',
label: 'Caption',
}
{
name: 'tags',
type: 'array',
label: 'Tags',
fields: [
{
name: 'tag',
type: 'text',
required: true,
},
],
}
{
name: 'copyright',
type: 'group',
label: 'Copyright',
fields: [
{
name: 'owner',
type: 'text',
label: 'Copyright Owner',
},
{
name: 'year',
type: 'number',
label: 'Copyright Year',
},
{
name: 'license',
type: 'select',
label: 'License Type',
options: [
{ label: 'All Rights Reserved', value: 'arr' },
{ label: 'Creative Commons', value: 'cc' },
{ label: 'Public Domain', value: 'pd' },
],
},
],
}
Complete Example
Here’s a comprehensive example with multiple custom fields:
import { buildConfig } from 'payload/config';
import { cloudinaryStorage } from 'payload-cloudinary';
export default buildConfig({
plugins: [
cloudinaryStorage({
config: {
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
},
collections: {
'media': true,
},
customFields: [
{
name: 'alt',
type: 'text',
label: 'Alt Text',
admin: {
description: 'Alternative text for accessibility',
},
},
{
name: 'caption',
type: 'text',
label: 'Caption',
},
{
name: 'tags',
type: 'array',
label: 'Tags',
fields: [
{
name: 'tag',
type: 'text',
required: true,
},
],
},
{
name: 'copyright',
type: 'group',
label: 'Copyright',
fields: [
{
name: 'owner',
type: 'text',
label: 'Copyright Owner',
},
{
name: 'year',
type: 'number',
label: 'Copyright Year',
},
],
},
{
name: 'category',
type: 'select',
label: 'Category',
options: [
{ label: 'Product', value: 'product' },
{ label: 'Marketing', value: 'marketing' },
{ label: 'Editorial', value: 'editorial' },
],
},
],
})
]
});
Using with Existing Collections
If you already have a Media collection defined in your Payload config, the plugin will automatically add custom fields to it. Ensure your collection slug matches the one in your plugin configuration.
// In your collection definition (collections/Media.ts)
export const Media: CollectionConfig = {
slug: 'media', // Must match plugin config
access: {
read: () => true,
},
fields: [
// Your existing fields
],
upload: true,
};
// In your payload.config.ts
export default buildConfig({
collections: [Media, Users],
plugins: [
cloudinaryStorage({
collections: {
'media': true, // Matches the slug above
},
customFields: [
// Custom fields are added to your Media collection
{
name: 'alt',
type: 'text',
label: 'Alt Text',
},
],
})
]
});
Field Merging Behavior
The plugin merges fields in this order:
- Your existing collection fields (if you have a pre-defined collection)
- Custom fields (from
customFields option)
- Cloudinary metadata fields (always added:
cloudinary group)
- Version history fields (only if
versioning.storeHistory is enabled)
// Final field structure:
[
// 1. Your existing fields
{ name: 'myField', type: 'text' },
// 2. Your custom fields
{ name: 'alt', type: 'text' },
{ name: 'caption', type: 'text' },
// 3. Cloudinary fields (auto-added)
{ name: 'cloudinary', type: 'group', fields: [...] },
// 4. Version fields (if versioning enabled)
{ name: 'versions', type: 'array', fields: [...] },
]
Accessing Custom Fields
In Your Frontend
const media = await payload.findByID({
collection: 'media',
id: 'your-media-id',
});
console.log(media.alt); // Your alt text
console.log(media.caption); // Your caption
console.log(media.tags); // Array of tags
console.log(media.copyright?.owner); // Copyright owner
In React Components
interface MediaType {
id: string;
filename: string;
cloudinary: {
public_id: string;
secure_url: string;
};
alt?: string;
caption?: string;
tags?: Array<{ tag: string }>;
}
const MediaCard = ({ media }: { media: MediaType }) => {
return (
<figure>
<img
src={media.cloudinary.secure_url}
alt={media.alt || media.filename}
/>
{media.caption && <figcaption>{media.caption}</figcaption>}
{media.tags && (
<div className="tags">
{media.tags.map((t, i) => <span key={i}>{t.tag}</span>)}
</div>
)}
</figure>
);
};
Advanced Field Types
Relationship Field
{
name: 'photographer',
type: 'relationship',
label: 'Photographer',
relationTo: 'users',
}
Rich Text Field
{
name: 'description',
type: 'richText',
label: 'Detailed Description',
}
Conditional Fields
{
name: 'mediaType',
type: 'select',
label: 'Media Type',
options: [
{ label: 'Photo', value: 'photo' },
{ label: 'Illustration', value: 'illustration' },
],
},
{
name: 'cameraSettings',
type: 'group',
label: 'Camera Settings',
admin: {
condition: (data) => data.mediaType === 'photo',
},
fields: [
{ name: 'iso', type: 'number' },
{ name: 'aperture', type: 'text' },
{ name: 'shutterSpeed', type: 'text' },
],
}
Troubleshooting
Custom Fields Not Appearing
If your custom fields aren’t visible in the admin panel:
- Check collection slug: Ensure it matches exactly
- Restart dev server: Changes require a full restart
- Verify plugin order: Place the plugin before collections are processed
- Check for naming conflicts: Avoid field names that conflict with built-in fields
Debug Field Configuration
export default buildConfig({
onInit: async (payload) => {
console.log('Media collection fields:',
payload.collections['media'].config.fields.map(f => f.name)
);
}
});
Avoid using these reserved field names: cloudinary, versions, filename, mimeType, filesize, width, height
Next Steps