PayloadCMS Redis Plugin

A transparent Redis caching layer plugin for Payload CMS v3 that automatically caches database queries to improve performance.
Features
- Automatic Query Caching - Transparently caches all read operations (find, findOne, count, etc.)
- Smart Invalidation - Automatically invalidates cache on write operations (create, update, delete)
- Flexible Configuration - Enable caching per collection or globally with custom TTL
- Per-Request Override - Control cache behavior on individual requests
- Custom Cache Keys - Generate custom cache keys based on your needs
- Pattern-Based Invalidation - Invalidate related cache entries using Redis patterns
- Debug Mode - Optional logging for cache hits, misses, and invalidations
- Zero Breaking Changes - Works seamlessly with existing Payload applications
Installation
npm install payloadcms-redis-plugin ioredis
yarn add payloadcms-redis-plugin ioredis
pnpm add payloadcms-redis-plugin ioredis
Requirements
- Payload CMS v3.37.0 or higher
- Node.js 18.20.2+ or 20.9.0+
- Redis server
Quick Start
Basic Setup
import { buildConfig } from 'payload'
import { redisCache } from 'payloadcms-redis-plugin'
export default buildConfig({
plugins: [
redisCache({
redis: {
url: 'redis://localhost:6379',
},
collections: {
posts: true,
articles: true,
},
}),
],
})
Using Existing Redis Client
import { Redis } from 'ioredis'
import { redisCache } from 'payloadcms-redis-plugin'
const redisClient = new Redis({
host: 'localhost',
port: 6379,
password: 'your-password',
db: 0,
})
export default buildConfig({
plugins: [
redisCache({
redis: {
client: redisClient,
},
collections: {
posts: true,
},
}),
],
})
Configuration
Plugin Options
type RedisPluginConfig = {
redis: { client: Redis; url?: never } | { client?: never; url: string }
collections?: Partial<Record<CollectionSlug, CacheOptions | true>>
globals?: Partial<Record<GlobalSlug, CacheOptions | true>>
debug?: boolean
defaultCacheOptions?: {
generateKey?: (operation: string, args: DBOperationArgs) => string
keyPrefix?: string
ttl?: number
}
}
Cache Options
type CacheOptions = {
key?: string
skip?: boolean
tags?: string[]
ttl?: number
}
Advanced Configuration
redisCache({
redis: {
url: process.env.REDIS_URL,
},
collections: {
posts: {
ttl: 600,
skip: false,
},
articles: {
ttl: 1800,
},
users: true,
},
globals: {
settings: true,
},
defaultCacheOptions: {
keyPrefix: 'myapp',
ttl: 300,
generateKey: (operation, args) => {
const { slug, where, locale } = args
return `${slug}:${operation}:${locale || 'default'}:${JSON.stringify(where)}`
},
},
debug: true,
})
Usage
Per-Request Cache Control
Override cache behavior for individual requests:
const freshPosts = await payload.find({
collection: 'posts',
req: {
context: {
cache: {
skip: true,
},
},
},
})
const shortLivedPosts = await payload.find({
collection: 'posts',
req: {
context: {
cache: {
ttl: 60,
},
},
},
})
const customCachedPosts = await payload.find({
collection: 'posts',
req: {
context: {
cache: {
key: 'posts:featured',
},
},
},
})
Cached Operations
The following database operations are automatically cached:
Read Operations (cached before hitting database):
find - Query collections with pagination
findOne - Query single document by ID
findGlobal - Query global configurations
findGlobalVersions - Query global version history
count - Count documents
countVersions - Count document versions
countGlobalVersions - Count global versions
queryDrafts - Query draft documents
Write Operations (invalidate cache after database update):
create - Create new document
createMany - Batch create
updateOne - Update single document
updateMany - Batch update
deleteOne - Delete single document
deleteMany - Batch delete
upsert - Create or update
updateGlobal - Update global config
updateGlobalVersion - Update global version
deleteVersions - Delete document versions
How It Works
Cache Key Generation
By default, cache keys are generated using MD5 hashing:
[prefix]:[slug]:[operation]:[md5-hash]
The hash includes: { slug, locale, operation, where }
Example keys:
posts:find:a1b2c3d4e5f6g7h8
myapp:articles:count:x9y8z7w6v5u4t3s2
Cache Flow
Read Operations:
Request → Check cache config → Check skip flag
↓ (cache enabled)
Check Redis → HIT: Return cached → MISS: Hit DB → Store in Redis → Return
↓ (cache disabled/skipped)
Hit DB directly
Write Operations:
Request → Execute on DB → Get cache config → Check skip flag
↓ (cache enabled)
Invalidate pattern → Return result
↓ (cache disabled/skipped)
Return result directly
Automatic Invalidation
When data changes, the plugin automatically invalidates related cache entries using pattern matching:
await payload.create({
collection: 'posts',
data: { title: 'New Post' },
})
await payload.update({
collection: 'articles',
id: '123',
data: { title: 'Updated' },
})
Debug Mode
Enable debug logging to monitor cache behavior:
redisCache({
redis: { url: 'redis://localhost:6379' },
collections: { posts: true },
debug: true,
})
Console output:
[RedisPlugin] [find] [posts] Cache HIT
[RedisPlugin] [find] [articles] Cache MISS
[RedisPlugin] [create] [posts] Invalidating pattern: posts:*
[RedisPlugin] [update] [posts] Cache SKIP (per-request)
TypeScript Support
The plugin includes full TypeScript definitions and extends Payload's RequestContext type:
declare module 'payload' {
export interface RequestContext {
cache?: {
key?: string
skip?: boolean
tags?: string[]
ttl?: number
}
}
}
Performance Considerations
- Default TTL: 5 minutes (300 seconds)
- Pattern Matching: Uses
redis.keys() for invalidation (consider SCAN in production with large keyspaces)
- Silent Failures: Cache errors don't break database queries
- Memory: Monitor Redis memory usage based on your cache strategy
- Expiration: Redis automatically removes expired keys
Development
pnpm install
pnpm dev
pnpm test
pnpm build
pnpm lint
Examples
E-commerce Site
redisCache({
redis: { url: process.env.REDIS_URL },
collections: {
products: { ttl: 3600 },
categories: { ttl: 7200 },
orders: { skip: true },
customers: { ttl: 600 },
},
globals: {
siteSettings: { ttl: 86400 },
},
})
Blog Platform
redisCache({
redis: { url: process.env.REDIS_URL },
collections: {
posts: { ttl: 1800 },
authors: { ttl: 3600 },
comments: { ttl: 300 },
},
defaultCacheOptions: {
keyPrefix: 'blog',
ttl: 600,
},
debug: process.env.NODE_ENV === 'development',
})
Troubleshooting
Redis Connection Issues
const redis = new Redis('redis://localhost:6379')
await redis.ping()
Cache Not Working
- Enable debug mode to see cache behavior
- Verify collection/global is configured for caching
- Check if
skip: true is set
- Ensure Redis server is running and accessible
High Memory Usage
- Reduce TTL values
- Be selective about which collections to cache
- Monitor Redis memory with
redis-cli info memory
- Consider using Redis maxmemory policies
Contributing
Contributions are welcome! Please see the GitHub repository for issues and pull requests.
License
MIT
Author
Isaiah Anyimi pls hire me
Links