R2 storage (generateFileURL per-collection) + filename normalization

This commit is contained in:
2026-08-27 18:57:14 +02:00
parent 247b849759
commit c6730335ef
3 changed files with 38 additions and 41 deletions
+19 -20
View File
@@ -31,32 +31,31 @@ import { s3Storage } from '@payloadcms/storage-s3';
return config;
};
}
// s3Storage wants Record<string, true> (the literal true, per collection),
// not Record<string, boolean>. Object.fromEntries widens true → boolean, so
// build the map with an explicitly-typed accumulator to keep the literal.
const collectionsConfig = {};
for (const slug of collections){
collectionsConfig[slug] = true;
}
// Public URL for served media. R2 is private by default; with a custom domain
// Public URL for served media. R2 is private by default; its S3 endpoint only
// accepts uploads and won't serve files (403). With a custom domain
// (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs
// pointing there instead of the private S3 endpoint (which 403s on the front).
// Without it, uploads work but images don't display publicly. See docs/storage.md.
// pointing there. Without it, uploads work but images don't display publicly.
// See docs/storage.md.
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '') // strip trailing slash
;
// generateFileURL is a PER-COLLECTION option in @payloadcms/storage-s3 (not a
// top-level one) — R2 needs it to point served URLs at the custom domain
// instead of the private S3 endpoint. Each collection gets either `true`
// (plain offload) or an object carrying generateFileURL when a public URL is set.
const generateFileURL = publicUrl ? ({ filename, prefix })=>[
publicUrl,
prefix,
filename
].filter(Boolean).join('/') : undefined;
const collectionsConfig = {};
for (const slug of collections){
collectionsConfig[slug] = generateFileURL ? {
generateFileURL
} : true;
}
return s3Storage({
bucket,
collections: collectionsConfig,
...publicUrl ? {
// generateFileURL overrides the stored/returned URL to the CDN domain.
// Params come from Payload's storage plugin; type them explicitly since
// the callback shape isn't inferred here (would be implicit any).
generateFileURL: ({ filename, prefix })=>[
publicUrl,
prefix,
filename
].filter(Boolean).join('/')
} : {},
config: {
credentials: {
accessKeyId,