Skip to main content
This guide covers common issues you might encounter when using the Payload Cloudinary plugin and their solutions.

Custom Fields Issues

If your custom fields aren’t showing up in the Payload CMS admin panel, check the following:

1. Check Collection Slug

Ensure the collection slug in your plugin configuration matches exactly with your Media collection slug.
// In your collection
export const Media: CollectionConfig = {
  slug: 'media', // Must match plugin config
};

// In your plugin config
cloudinaryStorage({
  collections: {
    'media': true, // Must match collection slug
  },
})

2. Plugin Order

Make sure the cloudinaryStorage plugin is registered before your collections are processed. Try moving it earlier in your plugins array:
export default buildConfig({
  plugins: [
    cloudinaryStorage({ /* ... */ }), // Register plugin first
    // ... other plugins
  ],
  collections: [
    Media,
    // ... other collections
  ],
})

3. Field Name Conflicts

If you already have fields with the same names in your collection, there might be conflicts. Try using different field names.

4. Restart Dev Server

Sometimes a full restart of your development server is needed after making plugin configuration changes:
# Stop the server (Ctrl+C) and restart
npm run dev
# or
yarn dev

5. Debug Plugin Configuration

Add a temporary debug log to see what’s happening (from README.md:447-459):
export default buildConfig({
  // ... your config
  onInit: async (payload) => {
    // Log the complete Media collection configuration
    console.log('Media collection fields:',
      payload.collections['media'].config.fields.map(f => f.name)
    );
  }
});
If custom fields appear but values don’t save:

Check Field Configuration

Ensure fields are properly defined with correct types:
customFields: [
  {
    name: 'alt',
    type: 'text', // Correct type
    label: 'Alt Text',
  },
  // Not:
  // { name: 'alt', label: 'Alt Text' } // Missing type!
]

Verify Database Schema

Check if the field exists in your database. You may need to update documents:
// Migrate existing documents
const media = await payload.find({
  collection: 'media',
  limit: 1000,
});

for (const doc of media.docs) {
  await payload.update({
    collection: 'media',
    id: doc.id,
    data: {
      alt: doc.alt || '',
    },
  });
}

Upload Issues

Check Cloudinary Credentials

Verify your environment variables are set correctly:
# .env.local or .env
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
Test credentials with a simple script:
import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

// Test upload
cloudinary.uploader.upload(
  'test-image.jpg',
  { folder: 'test' },
  (error, result) => {
    if (error) console.error('Error:', error);
    else console.log('Success:', result);
  }
);

Check File Size Limits

Cloudinary free tier has upload limits:
  • Images: 10 MB by default
  • Videos: 100 MB by default
Adjust limits in your Cloudinary account or upgrade your plan.

Verify Folder Configuration

Check your folder setting in the plugin config:
cloudinaryStorage({
  folder: 'my-app-media', // Base folder path
  // ...
})

Dynamic Folder Mode

If folders aren’t appearing as expected, check if Dynamic Folder Mode is enabled:
cloudinaryStorage({
  folder: 'media',
  supportDynamicFolderMode: true, // Default: true
  // ...
})
See the Dynamic Folder Mode section below for more details.

Check Public ID Configuration

Ensure uniqueFilename is enabled to prevent overwrites:
cloudinaryStorage({
  publicID: {
    enabled: true,
    useFilename: true,
    uniqueFilename: true, // Adds unique suffix
  },
})

Custom Public ID Generator

If using a custom generator, ensure it produces unique IDs:
publicID: {
  enabled: true,
  generatePublicID: (filename, prefix, folder) => {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substring(7);
    const safeName = filename.replace(/[^a-z0-9]/gi, '-').toLowerCase();
    return `${folder}/${safeName}-${timestamp}-${random}`;
  },
}

Dynamic Folder Mode Issues

Newer Cloudinary accounts use Dynamic Folder Mode, which separates the folder structure in the UI from the public ID path.

Enable Support

Ensure Dynamic Folder Mode support is enabled (it’s on by default):
cloudinaryStorage({
  folder: 'payload-media',
  supportDynamicFolderMode: true, // Default: true
  // ...
})

How It Works

With Dynamic Folder Mode:
  • The plugin sets asset_folder parameter during upload
  • Assets appear in correct folders in Cloudinary Media Library
  • Public IDs remain unchanged
  • URLs continue to work as expected

Check Account Type

Determine if your account uses Dynamic Folder Mode:
  1. Upload a test file with the plugin
  2. Check the API response or document in Payload:
    • Dynamic Mode: Has asset_folder and display_name fields
    • Fixed Mode: Has folder field
See README.md:391-432 for more details.

Versioning Issues

Enable Version History

Make sure storeHistory is enabled:
cloudinaryStorage({
  versioning: {
    enabled: true,
    storeHistory: true, // Required to store history
  },
})

Check Version Fields

Verify the versions field appears in your media documents:
const media = await payload.findByID({
  collection: 'media',
  id: 'doc-id',
});

console.log(media.versions); // Should be an array

Enable Auto-Invalidation

Set autoInvalidate to clear CDN cache for old versions:
cloudinaryStorage({
  versioning: {
    enabled: true,
    autoInvalidate: true, // Invalidate old versions in CDN
  },
})
CDN invalidation may take a few minutes to propagate globally.

Frontend Integration Issues

Check Cloud Name

Verify your cloud name environment variable is set correctly:
# .env.local (Next.js)
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your-cloud-name
The variable must be prefixed with NEXT_PUBLIC_ to be accessible in the browser.

Verify Public ID Format

Check the public ID doesn’t have extra slashes or invalid characters:
console.log('Public ID:', media.cloudinary.public_id);
// Should be something like: "payload-media/image-name-123456"
// Not: "//payload-media//image-name" or similar

Test URL Manually

Try accessing the Cloudinary URL directly in your browser:
https://res.cloudinary.com/{cloud_name}/image/upload/{public_id}.{format}

Check Transformation Syntax

Ensure transformations use correct Cloudinary syntax:
// Correct:
`w_400,h_300,c_fill,q_auto`

// Incorrect:
`width:400,height:300` // Wrong syntax
`w_400 h_300` // Missing commas

Test Transformations Individually

Add transformations one at a time to identify issues:
// Test 1: Basic resize
`w_400`

// Test 2: Add crop
`w_400,c_fill`

// Test 3: Add quality
`w_400,c_fill,q_auto`

Check PDF Format

Verify the document is actually a PDF:
if (media.cloudinary.format === 'pdf') {
  // PDF-specific code
}

Use Correct Page Transformation

Ensure you’re using pg_X transformation and converting to image format:
// Correct:
`pg_1,w_300,h_400,c_fill,q_auto,f_jpg/${public_id}.pdf`

// Incorrect:
`page_1/${public_id}.pdf` // Wrong syntax
`pg_1/${public_id}.pdf` // Missing format conversion (f_jpg)

Check Page Number

Ensure the page number is within range:
const page = media.cloudinary.selected_page || 1;
const totalPages = media.cloudinary.pages || 1;

if (page > totalPages) {
  // Page out of range
}

Performance Issues

Check File Size

Large files take longer to upload. Consider:
  • Compressing images before upload
  • Using client-side compression libraries
  • Setting file size limits in upload config

Network Issues

Test upload speed to Cloudinary:
const start = Date.now();
await payload.create({
  collection: 'media',
  data: { /* ... */ },
});
console.log(`Upload took ${Date.now() - start}ms`);

Use Transformations

Always resize images to needed dimensions:
// Don't load full-size images for thumbnails
// Bad:
`${public_id}.${format}`

// Good:
`w_200,h_200,c_fill,q_auto,f_auto/${public_id}.${format}`

Enable Auto Format and Quality

`q_auto,f_auto/${public_id}.${format}`
This serves WebP/AVIF to supported browsers and optimizes quality.

Lazy Load Images

<img src={url} loading="lazy" />

Database Issues

Check Upload Success

Ensure the upload to Cloudinary succeeded:
const doc = await payload.create({
  collection: 'media',
  data: { /* ... */ },
});

if (!doc.cloudinary) {
  console.error('Cloudinary upload failed');
  // Check server logs for error details
}

Verify Plugin is Active

Confirm the plugin is enabled for the collection:
cloudinaryStorage({
  enabled: true, // Default: true
  collections: {
    'media': true, // Must be enabled
  },
})
The plugin automatically deletes files from Cloudinary when media documents are deleted from Payload.

Manual Cleanup

If you have orphaned records, clean them up manually:
import { v2 as cloudinary } from 'cloudinary';

// List all resources in folder
const result = await cloudinary.api.resources({
  type: 'upload',
  prefix: 'payload-media',
  max_results: 500,
});

// Check which ones exist in Payload
for (const resource of result.resources) {
  const exists = await payload.find({
    collection: 'media',
    where: {
      'cloudinary.public_id': {
        equals: resource.public_id,
      },
    },
  });
  
  if (exists.docs.length === 0) {
    // Orphaned - safe to delete
    await cloudinary.uploader.destroy(resource.public_id);
  }
}

Getting Help

If you’re still experiencing issues:
1

Check Plugin Version

Ensure you’re using the latest version:
npm list payload-cloudinary
npm update payload-cloudinary
2

Review Logs

Check server logs for error messages and stack traces
3

Minimal Reproduction

Create a minimal example that reproduces the issue
4

Report Issue

Contact the maintainer on X (Twitter): @syedmuzamilm

Next Steps

Configuration Reference

Complete plugin configuration options

Media Collection Structure

Understanding media document fields

Build docs developers (and LLMs) love