Skip to main content
Cloudinary uses public IDs to uniquely identify assets. The Payload Cloudinary plugin gives you complete control over how these IDs are generated, allowing you to organize your media library effectively.

What is a Public ID?

A public ID is the unique identifier for an asset in Cloudinary. For example:
  • URL: https://res.cloudinary.com/demo/image/upload/sample.jpg
  • Public ID: sample
With folders:
  • URL: https://res.cloudinary.com/demo/image/upload/my-folder/sample.jpg
  • Public ID: my-folder/sample

Configuration Options

publicID
PublicIDOptions
Configuration object for customizing public ID generation.
publicID.enabled
boolean
default:true
Whether to enable custom public ID generation. When false, Cloudinary generates random IDs.
publicID.useFilename
boolean
default:true
Whether to use the original filename as part of the public ID.
publicID.uniqueFilename
boolean
default:true
Whether to add a random suffix to ensure unique filenames and prevent collisions.
publicID.generatePublicID
(filename: string, prefix?: string, folder?: string) => string
Custom function to generate public IDs. When provided, overrides useFilename and uniqueFilename options.

Basic Usage

Default Behavior

By default, the plugin generates public IDs using the filename with a unique suffix:
import { cloudinaryStorage } from 'payload-cloudinary';

export default buildConfig({
  plugins: [
    cloudinaryStorage({
      config: { /* ... */ },
      collections: { 'media': true },
      folder: 'my-app',
      // Default publicID settings:
      publicID: {
        enabled: true,
        useFilename: true,
        uniqueFilename: true,
      },
    })
  ]
});
Example output:
  • Input: photo.jpg
  • Public ID: my-app/photo_abc123.jpg

Disable Unique Suffix

If you want to use exact filenames (be careful of overwrites):
publicID: {
  enabled: true,
  useFilename: true,
  uniqueFilename: false, // No random suffix
}
Example output:
  • Input: photo.jpg
  • Public ID: my-app/photo.jpg
Setting uniqueFilename: false may cause files with the same name to overwrite each other. Use with caution.

Random IDs Only

Let Cloudinary generate completely random IDs:
publicID: {
  enabled: false, // Use Cloudinary's default random IDs
}
Example output:
  • Public ID: my-app/a8sd7f6g5h4j3k2l1

Custom Generation Function

For complete control, provide your own generatePublicID function:
import { cloudinaryStorage } from 'payload-cloudinary';

export default buildConfig({
  plugins: [
    cloudinaryStorage({
      config: { /* ... */ },
      collections: { 'media': true },
      folder: 'my-app',
      publicID: {
        generatePublicID: (filename, prefix, folder) => {
          // Custom logic here
          return `${folder}/custom-${filename}`;
        },
      },
    })
  ]
});

Function Parameters

filename
string
The original filename of the uploaded file (e.g., photo.jpg)
prefix
string | undefined
Optional prefix from the collection configuration
folder
string
The base folder path from plugin configuration

Function Return Value

The function must return a string representing the complete public ID (including folder path).

Advanced Examples

Timestamp-Based IDs

Add timestamps for uniqueness and sorting:
publicID: {
  generatePublicID: (filename, prefix, folder) => {
    const sanitizedName = filename
      .toLowerCase()
      .replace(/\.[^/.]+$/, '') // Remove extension
      .replace(/[^a-z0-9]/g, '-') // Replace special chars
      .replace(/-+/g, '-') // Remove duplicate hyphens
      .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens

    const timestamp = new Date().toISOString()
      .replace(/[^0-9]/g, '')
      .slice(0, 14); // YYYYMMDDHHmmss

    const prefixPath = prefix ? `${prefix}/` : '';
    return `${folder}/${prefixPath}${sanitizedName}_${timestamp}`;
  },
}
Example output:
  • Input: My Photo.jpg
  • Public ID: my-app/my-photo_20240315143022

Date-Based Folder Structure

Organize by year/month:
publicID: {
  generatePublicID: (filename, prefix, folder) => {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    
    const sanitized = filename
      .replace(/\.[^/.]+$/, '')
      .replace(/[^a-z0-9]/gi, '-')
      .toLowerCase();
    
    const random = Math.random().toString(36).substring(2, 8);
    
    return `${folder}/${year}/${month}/${sanitized}_${random}`;
  },
}
Example output:
  • Input: product-image.jpg
  • Public ID: my-app/2024/03/product-image_k7m9n2

UUID-Based IDs

Use UUIDs for guaranteed uniqueness:
import { v4 as uuidv4 } from 'uuid';

publicID: {
  generatePublicID: (filename, prefix, folder) => {
    const ext = filename.match(/\.[^/.]+$/)?.[0] || '';
    const uuid = uuidv4();
    const prefixPath = prefix ? `${prefix}/` : '';
    
    return `${folder}/${prefixPath}${uuid}${ext}`;
  },
}
Example output:
  • Input: image.jpg
  • Public ID: my-app/a1b2c3d4-e5f6-4g7h-8i9j-k0l1m2n3o4p5.jpg

Collection-Specific Prefixes

Organize by collection type:
publicID: {
  generatePublicID: (filename, prefix, folder) => {
    // Use prefix to determine subfolder
    const subfolder = prefix || 'general';
    
    const sanitized = filename
      .replace(/\.[^/.]+$/, '')
      .replace(/\s+/g, '-')
      .toLowerCase();
    
    const timestamp = Date.now();
    
    return `${folder}/${subfolder}/${sanitized}-${timestamp}`;
  },
}
Example output:
  • Input: Avatar Image.png with prefix: avatars
  • Public ID: my-app/avatars/avatar-image-1710512345678

Preserve Original Structure

Keep the original filename structure intact:
publicID: {
  generatePublicID: (filename, prefix, folder) => {
    // Keep original filename but ensure uniqueness
    const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
    const ext = filename.match(/\.[^/.]+$/)?.[0] || '';
    const random = Math.random().toString(36).substring(2, 6);
    
    const prefixPath = prefix ? `${prefix}/` : '';
    return `${folder}/${prefixPath}${nameWithoutExt}-${random}${ext}`;
  },
}
Example output:
  • Input: Company Logo.svg
  • Public ID: my-app/company-logo-k9m2.svg

Best Practices

1. Sanitize Filenames

Always clean up filenames to avoid issues:
const sanitize = (filename: string) => {
  return filename
    .toLowerCase()
    .replace(/\.[^/.]+$/, '') // Remove extension
    .replace(/[^a-z0-9]/g, '-') // Only alphanumeric and hyphens
    .replace(/-+/g, '-') // No duplicate hyphens
    .replace(/^-|-$/g, ''); // No leading/trailing hyphens
};

2. Ensure Uniqueness

Always add some form of unique identifier:
// Option 1: Timestamp
const unique = Date.now();

// Option 2: Random string
const unique = Math.random().toString(36).substring(2, 8);

// Option 3: UUID
import { v4 as uuidv4 } from 'uuid';
const unique = uuidv4();

3. Consider SEO

For public-facing images, keep filenames descriptive:
publicID: {
  generatePublicID: (filename, prefix, folder) => {
    const seo = filename
      .toLowerCase()
      .replace(/\.[^/.]+$/, '')
      .replace(/[^a-z0-9]+/g, '-')
      .slice(0, 50); // Limit length
    
    const unique = Date.now();
    return `${folder}/${seo}-${unique}`;
  },
}

4. Plan for Scale

Organize files in a way that scales:
// Good: Organized by date
`my-app/2024/03/15/image-abc123`

// Avoid: Flat structure with thousands of files
`my-app/image-abc123`
`my-app/image-abc124`
`my-app/image-abc125`
// ... thousands more

Accessing Public IDs

Once uploaded, access the public ID from your media documents:
// Upload a file
const media = await payload.create({
  collection: 'media',
  data: { /* file data */ },
});

// Access the public ID
const publicId = media.cloudinary.public_id;
console.log(publicId); // 'my-app/photo_abc123'

// Use in Cloudinary transformations
const transformedUrl = `https://res.cloudinary.com/${cloudName}/image/upload/w_500,h_500,c_fill/${publicId}`;

TypeScript Support

The generatePublicID function has full type support:
import type { PublicIDOptions } from 'payload-cloudinary';

const publicIDConfig: PublicIDOptions = {
  generatePublicID: (
    filename: string,
    prefix?: string,
    folder?: string
  ): string => {
    // Your implementation
    return `${folder}/${filename}`;
  },
};

Next Steps

Build docs developers (and LLMs) love