🎯 Objective
Target Feature: Direct Client Uploads for S3 Storage (@payloadcms/storage-s3)
Reference Component(s): src/payload.config.ts, @payloadcms/storage-s3
📌 Context & Motivation
Our Payload CMS is hosted on AWS Lambda behind AWS API Gateway.
Currently, media file uploads are proxied through the CMS backend (API Gateway → AWS Lambda → S3). This architecture introduces critical operational constraints:
- API Gateway Hard Payload Limit (6 MB): AWS API Gateway enforces an unconfigurable 6 MB payload limit on HTTP requests. Uploading any media file exceeding 6 MB returns a
413 Payload Too Large error.
- AWS Lambda Timeouts & Memory Overhead: Buffering large file binaries in Lambda memory and encoding/forwarding them to S3 increases invocation execution duration, memory consumption, and AWS costs.
Enabling direct client uploads (clientUploads: true) allows the browser to fetch a presigned S3 URL from Payload and upload file binaries directly to AWS S3, bypassing API Gateway and Lambda limits entirely.
🛠️ Technical Context
CMS Repository Notes
- PayloadCMS S3 Plugin Docs: https://payloadcms.com/docs/upload/storage-adapters#s3-storage
- Location: Modify
src/payload.config.ts under the plugins array.
- Configuration: Set
clientUploads: true on the s3Storage plugin options to enable direct client-to-S3 file binary uploads via presigned URLs.
- Environment Variables:
S3_BUCKET: Target S3 bucket name.
AWS_REGION: AWS region (default: ap-southeast-1).
CLOUDFRONT_DISTRIBUTION_DOMAIN: CDN domain for media URL resolution.
- Proposed Configuration:
plugins: [
...plugins,
...(process.env.S3_BUCKET
? [
s3Storage({
collections: {
media: {
disableLocalStorage: true,
generateFileURL: ({ filename, prefix }) => {
const domain = process.env.CLOUDFRONT_DISTRIBUTION_DOMAIN
if (!domain) return filename
const normalizedDomain = domain.replace(/\/$/, '')
const protocol = normalizedDomain.startsWith('http') ? '' : 'https://'
return `${protocol}${normalizedDomain}/${prefix}/${filename}`
},
prefix: 'media',
},
},
bucket: process.env.S3_BUCKET || '',
config: {
region: process.env.AWS_REGION || 'ap-southeast-1',
},
clientUploads: true,
enabled: isProduction,
}),
]
: []),
]
Infrastructure / AWS Notes
- S3 Bucket CORS Policy: Handled out-of-band by DevOps.
📋 Schema & Configuration Requirements
🧪 Test Case Planning
[To be filled by Developer: As per our Developer Workflow, before writing code, reply to this issue with your proposed test cases (Admin UI, Validation, API Fetch, etc.). Wait for approval from the Project Lead.]
🎯 Objective
Target Feature: Direct Client Uploads for S3 Storage (
@payloadcms/storage-s3)Reference Component(s):
src/payload.config.ts,@payloadcms/storage-s3📌 Context & Motivation
Our Payload CMS is hosted on AWS Lambda behind AWS API Gateway.
Currently, media file uploads are proxied through the CMS backend (API Gateway → AWS Lambda → S3). This architecture introduces critical operational constraints:
413 Payload Too Largeerror.Enabling direct client uploads (
clientUploads: true) allows the browser to fetch a presigned S3 URL from Payload and upload file binaries directly to AWS S3, bypassing API Gateway and Lambda limits entirely.🛠️ Technical Context
CMS Repository Notes
src/payload.config.tsunder thepluginsarray.clientUploads: trueon thes3Storageplugin options to enable direct client-to-S3 file binary uploads via presigned URLs.S3_BUCKET: Target S3 bucket name.AWS_REGION: AWS region (default:ap-southeast-1).CLOUDFRONT_DISTRIBUTION_DOMAIN: CDN domain for media URL resolution.Infrastructure / AWS Notes
📋 Schema & Configuration Requirements
clientUploads: true(Type: Boolean, Enabled: True ons3Storageplugin config insrc/payload.config.ts)mediacollection)🧪 Test Case Planning
[To be filled by Developer: As per our Developer Workflow, before writing code, reply to this issue with your proposed test cases (Admin UI, Validation, API Fetch, etc.). Wait for approval from the Project Lead.]