Skip to main content
The Payload Cloudinary plugin supports advanced versioning features that allow you to track file changes, maintain version history, and automatically manage CDN cache invalidation.

Overview

Cloudinary automatically creates new versions when you upload a file with an existing public ID. The plugin can track these versions and provide additional functionality like history storage and automatic cache invalidation.

Configuration Options

versioning
CloudinaryVersioningOptions
Configuration object for versioning support.
versioning.enabled
boolean
default:false
Whether to enable versioning support. When true, the plugin tracks Cloudinary version information.
versioning.autoInvalidate
boolean
default:false
Whether to automatically invalidate old versions in the CDN when a new version is uploaded. Ensures users always see the latest version.
versioning.storeHistory
boolean
default:false
Whether to store the complete version history in PayloadCMS database. When enabled, adds a versions field to your media collection.

Basic Versioning

Enable basic versioning to track current version information:
import { cloudinaryStorage } from 'payload-cloudinary';

export default buildConfig({
  plugins: [
    cloudinaryStorage({
      config: { /* ... */ },
      collections: { 'media': true },
      versioning: {
        enabled: true,
      },
    })
  ]
});
With basic versioning enabled, each media document includes:
{
  id: '123',
  filename: 'product.jpg',
  cloudinary: {
    public_id: 'my-app/product',
    version: '1710512345',      // Current version number
    version_id: 'abc123def456',  // Current version ID
    secure_url: 'https://res.cloudinary.com/...v1710512345/product.jpg',
    // ... other metadata
  }
}

Auto-Invalidation

Enable automatic CDN cache invalidation to ensure users always see the latest version:
versioning: {
  enabled: true,
  autoInvalidate: true, // Invalidate CDN cache on updates
}
Auto-invalidation is useful when you upload a new file with the same public ID. It ensures CDN caches are cleared and users see the updated version immediately.

How Auto-Invalidation Works

  1. You upload a new version of an existing file
  2. Cloudinary creates a new version (e.g., v1710512345 → v1710512999)
  3. The plugin automatically invalidates the old version in the CDN
  4. Users fetch the new version without manual cache clearing
// Before: Old version cached
https://res.cloudinary.com/demo/image/upload/v1710512345/product.jpg

// After upload: New version, old cache invalidated
https://res.cloudinary.com/demo/image/upload/v1710512999/product.jpg

Version History Storage

Store complete version history in your PayloadCMS database:
versioning: {
  enabled: true,
  autoInvalidate: true,
  storeHistory: true, // Store all versions in database
}
With storeHistory enabled, a versions array field is added to your media collection:
{
  id: '123',
  filename: 'product.jpg',
  cloudinary: {
    public_id: 'my-app/product',
    version: '1710512999',     // Latest version
    version_id: 'xyz789abc012',
    secure_url: 'https://res.cloudinary.com/.../v1710512999/product.jpg',
  },
  versions: [
    {
      version: '1710512345',
      version_id: 'abc123def456',
      created_at: '2024-03-15T14:30:45.000Z',
      secure_url: 'https://res.cloudinary.com/.../v1710512345/product.jpg',
    },
    {
      version: '1710512678',
      version_id: 'def456ghi789',
      created_at: '2024-03-15T15:45:00.000Z',
      secure_url: 'https://res.cloudinary.com/.../v1710512678/product.jpg',
    },
    {
      version: '1710512999',
      version_id: 'xyz789abc012',
      created_at: '2024-03-15T16:12:30.000Z',
      secure_url: 'https://res.cloudinary.com/.../v1710512999/product.jpg',
    },
  ]
}

Complete Configuration Example

Here’s a full example with all versioning features enabled:
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,
      },
      versioning: {
        enabled: true,        // Track version info
        autoInvalidate: true, // Clear CDN cache automatically
        storeHistory: true,   // Keep full version history
      },
    })
  ]
});

Accessing Version Information

Current Version

const media = await payload.findByID({
  collection: 'media',
  id: 'your-media-id',
});

console.log(media.cloudinary.version);     // '1710512999'
console.log(media.cloudinary.version_id);  // 'xyz789abc012'
console.log(media.cloudinary.secure_url);  // URL with version

Version History

const media = await payload.findByID({
  collection: 'media',
  id: 'your-media-id',
});

// List all versions
media.versions?.forEach(version => {
  console.log(`Version ${version.version}`);
  console.log(`Created: ${version.created_at}`);
  console.log(`URL: ${version.secure_url}`);
});

// Get specific version URL
const firstVersion = media.versions?.[0];
if (firstVersion) {
  console.log(`Original upload: ${firstVersion.secure_url}`);
}

Frontend Usage

Display Current Version

interface MediaType {
  cloudinary: {
    public_id: string;
    version: string;
    secure_url: string;
  };
}

const MediaImage = ({ media }: { media: MediaType }) => {
  return (
    <div>
      <img src={media.cloudinary.secure_url} alt="Media" />
      <p>Version: {media.cloudinary.version}</p>
    </div>
  );
};

Version History Viewer

interface VersionType {
  version: string;
  version_id: string;
  created_at: string;
  secure_url: string;
}

interface MediaWithHistoryType {
  filename: string;
  cloudinary: {
    version: string;
  };
  versions?: VersionType[];
}

const VersionHistory = ({ media }: { media: MediaWithHistoryType }) => {
  if (!media.versions || media.versions.length === 0) {
    return <p>No version history available</p>;
  }

  return (
    <div className="version-history">
      <h3>Version History for {media.filename}</h3>
      <div className="versions">
        {media.versions.map((version) => (
          <div 
            key={version.version_id} 
            className={version.version === media.cloudinary.version ? 'current' : ''}
          >
            <img 
              src={version.secure_url} 
              alt={`Version ${version.version}`}
            />
            <p>Version: {version.version}</p>
            <p>Created: {new Date(version.created_at).toLocaleString()}</p>
            {version.version === media.cloudinary.version && (
              <span className="badge">Current</span>
            )}
          </div>
        ))}
      </div>
    </div>
  );
};

Access Specific Version URL

const getVersionURL = (
  publicId: string,
  version: string,
  cloudName: string
) => {
  return `https://res.cloudinary.com/${cloudName}/image/upload/v${version}/${publicId}`;
};

// Usage
const CompareVersions = ({ media }) => {
  const cloudName = process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME;
  const oldVersion = media.versions?.[0];
  const newVersion = media.cloudinary;

  return (
    <div className="compare">
      <div>
        <h4>Original</h4>
        <img src={oldVersion.secure_url} alt="Original" />
      </div>
      <div>
        <h4>Current</h4>
        <img src={newVersion.secure_url} alt="Current" />
      </div>
    </div>
  );
};

Use Cases

Asset Management

Track all changes to marketing materials:
versioning: {
  enabled: true,
  storeHistory: true,
}
Benefit: See when product images were updated, revert to previous versions

CDN Cache Control

Ensure users always see the latest version:
versioning: {
  enabled: true,
  autoInvalidate: true,
}
Benefit: No stale cached images when you update files

Compliance & Auditing

Maintain complete audit trail:
versioning: {
  enabled: true,
  autoInvalidate: true,
  storeHistory: true,
}
Benefit: Track who uploaded what and when, maintain history for legal compliance

Performance Considerations

Storage Impact

storeHistory: true stores version metadata in your database. Each version adds ~200 bytes to the document. For high-volume applications, monitor database size.

CDN Invalidation

Cloudinary may have limits on CDN invalidation requests. Check your plan’s quota if you enable autoInvalidate on high-traffic applications.

TypeScript Types

import type { CloudinaryVersioningOptions } from 'payload-cloudinary';

const versioningConfig: CloudinaryVersioningOptions = {
  enabled: true,
  autoInvalidate: true,
  storeHistory: true,
};
Version metadata type:
interface CloudinaryMetadata {
  public_id: string;
  version?: string;
  version_id?: string;
  secure_url: string;
  // ... other fields
}

interface VersionHistory {
  version: string;
  version_id: string;
  created_at: string;
  secure_url: string;
}

interface MediaDocument {
  cloudinary: CloudinaryMetadata;
  versions?: VersionHistory[];
}

Next Steps

Build docs developers (and LLMs) love