vault backup: 2026-05-15 16:54:04
This commit is contained in:
parent
45f1e250f9
commit
57d13f19c1
3 changed files with 251 additions and 2 deletions
233
wiki/payloadcms/storage-adapters.md
Normal file
233
wiki/payloadcms/storage-adapters.md
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
---
|
||||
title: "Storage Adapters"
|
||||
aliases: [payload-storage, cloud-storage, s3-storage, gcs-storage, azure-storage]
|
||||
tags: [payloadcms, upload, storage, s3, gcs, azure, vercel, cloudflare, r2]
|
||||
sources: [raw/upload__storage-adapters.md]
|
||||
created: 2026-05-15
|
||||
updated: 2026-05-15
|
||||
---
|
||||
|
||||
# Storage Adapters
|
||||
|
||||
Payload ships with local disk storage by default. For production, swap to a cloud adapter — all adapters automatically set `disableLocalStorage: true` on the configured collections.
|
||||
|
||||
## Available Adapters
|
||||
|
||||
| Service | Package |
|
||||
|---------|---------|
|
||||
| Vercel Blob | `@payloadcms/storage-vercel-blob` |
|
||||
| AWS S3 | `@payloadcms/storage-s3` |
|
||||
| Azure Blob | `@payloadcms/storage-azure` |
|
||||
| Google Cloud Storage | `@payloadcms/storage-gcs` |
|
||||
| Uploadthing | `@payloadcms/storage-uploadthing` |
|
||||
| Cloudflare R2 (Workers) | `@payloadcms/storage-r2` |
|
||||
|
||||
## Vercel Blob
|
||||
|
||||
```ts
|
||||
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
|
||||
|
||||
vercelBlobStorage({
|
||||
enabled: true,
|
||||
collections: {
|
||||
media: true,
|
||||
'media-with-prefix': { prefix: 'my-prefix' },
|
||||
},
|
||||
token: process.env.BLOB_READ_WRITE_TOKEN,
|
||||
})
|
||||
```
|
||||
|
||||
- Requires `BLOB_READ_WRITE_TOKEN` (auto-set by Vercel after adding blob storage)
|
||||
- Vercel server upload limit: **4.5 MB** — use `clientUploads: true` to bypass
|
||||
|
||||
| Option | Default |
|
||||
|--------|---------|
|
||||
| `enabled` | `true` |
|
||||
| `addRandomSuffix` | `false` |
|
||||
| `cacheControlMaxAge` | 1 year |
|
||||
| `clientUploads` | — |
|
||||
| `useCompositePrefixes` | `false` |
|
||||
|
||||
## AWS S3
|
||||
|
||||
```ts
|
||||
import { s3Storage } from '@payloadcms/storage-s3'
|
||||
|
||||
s3Storage({
|
||||
collections: { media: true },
|
||||
bucket: process.env.S3_BUCKET,
|
||||
config: {
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
},
|
||||
region: process.env.S3_REGION,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- `config` accepts any [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3)
|
||||
- `signedDownloads` — use presigned URLs per-collection (useful for large files/videos)
|
||||
- `enabled: Boolean(process.env.S3_BUCKET)` — skip plugin when credentials absent in local dev
|
||||
|
||||
### Cloudflare R2 via S3 API
|
||||
|
||||
Use `@payloadcms/storage-s3` for R2 from Node.js/Vercel/Netlify — **not** `@payloadcms/storage-r2` (that's Cloudflare Workers only).
|
||||
|
||||
```ts
|
||||
s3Storage({
|
||||
enabled: Boolean(process.env.R2_BUCKET),
|
||||
collections: {
|
||||
media: {
|
||||
disablePayloadAccessControl: true,
|
||||
generateFileURL: ({ filename, prefix }) => {
|
||||
const key = prefix ? `${prefix}/${filename}` : filename
|
||||
return `${process.env.R2_PUBLIC_URL}/${key}`
|
||||
},
|
||||
},
|
||||
},
|
||||
bucket: process.env.R2_BUCKET,
|
||||
config: {
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
||||
},
|
||||
region: 'auto', // required by R2
|
||||
endpoint: process.env.R2_ENDPOINT, // S3 API endpoint, uploads only
|
||||
forcePathStyle: true, // required for R2
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Required env vars:
|
||||
```
|
||||
R2_BUCKET=my-bucket
|
||||
R2_ACCESS_KEY_ID=...
|
||||
R2_SECRET_ACCESS_KEY=...
|
||||
R2_ENDPOINT=https://<accountId>.r2.cloudflarestorage.com
|
||||
R2_PUBLIC_URL=https://media.yourdomain.com # R2.dev subdomain or custom domain
|
||||
```
|
||||
|
||||
> **Warning:** R2 buckets are private by default. The S3 API endpoint is for uploads only — you must enable the R2.dev subdomain or connect a custom domain to serve files publicly.
|
||||
|
||||
## Azure Blob Storage
|
||||
|
||||
```ts
|
||||
import { azureStorage } from '@payloadcms/storage-azure'
|
||||
|
||||
azureStorage({
|
||||
collections: { media: true },
|
||||
allowContainerCreate: process.env.AZURE_STORAGE_ALLOW_CONTAINER_CREATE === 'true',
|
||||
baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
|
||||
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
|
||||
containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
|
||||
})
|
||||
```
|
||||
|
||||
## Google Cloud Storage
|
||||
|
||||
```ts
|
||||
import { gcsStorage } from '@payloadcms/storage-gcs'
|
||||
|
||||
gcsStorage({
|
||||
collections: { media: true },
|
||||
bucket: process.env.GCS_BUCKET,
|
||||
options: {
|
||||
apiEndpoint: process.env.GCS_ENDPOINT,
|
||||
projectId: process.env.GCS_PROJECT_ID,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Uploadthing
|
||||
|
||||
```ts
|
||||
uploadthingStorage({
|
||||
collections: { media: true },
|
||||
options: {
|
||||
token: process.env.UPLOADTHING_TOKEN,
|
||||
acl: 'public-read',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Cloudflare R2 (Workers native)
|
||||
|
||||
```ts
|
||||
r2Storage({
|
||||
collections: { media: true },
|
||||
bucket: cloudflare.env.R2, // native R2 bucket binding
|
||||
})
|
||||
```
|
||||
|
||||
> Beta. Use this only in Cloudflare Workers where R2 is available as a native binding. For all other Node.js environments, use `@payloadcms/storage-s3` with the R2 S3-compatible endpoint.
|
||||
|
||||
## Prefix Composition
|
||||
|
||||
By default, a document-level prefix **overrides** the collection prefix entirely.
|
||||
|
||||
With `useCompositePrefixes: true`, they are **combined**:
|
||||
|
||||
```
|
||||
# Default
|
||||
Collection prefix: media-folder, Document prefix: user-123
|
||||
→ user-123/image.jpg
|
||||
|
||||
# useCompositePrefixes: true
|
||||
→ media-folder/user-123/image.jpg
|
||||
```
|
||||
|
||||
## Payload Access Control
|
||||
|
||||
By default, Payload proxies all file requests through `/<collectionSlug>/<staticURL>/<filename>` so that access control rules apply even to cloud-hosted files. The file URL does **not** point directly to the cloud host.
|
||||
|
||||
Set `disablePayloadAccessControl: true` on a collection to bypass the proxy and serve files directly from the cloud host. Only safe when `read: () => true` or similar.
|
||||
|
||||
## Custom Adapter
|
||||
|
||||
Use `@payloadcms/plugin-cloud-storage` as the base:
|
||||
|
||||
```ts
|
||||
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
|
||||
|
||||
cloudStoragePlugin({
|
||||
collections: {
|
||||
'my-collection-slug': { adapter: myCustomAdapter },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Implement the `GeneratedAdapter` interface:
|
||||
|
||||
```ts
|
||||
interface GeneratedAdapter {
|
||||
fields?: Field[]
|
||||
generateURL?: GenerateURL
|
||||
handleDelete: HandleDelete
|
||||
handleUpload: HandleUpload
|
||||
name: string
|
||||
onInit?: () => void
|
||||
staticHandler: StaticHandler
|
||||
}
|
||||
```
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
- All official adapters auto-set `disableLocalStorage: true` on their collections
|
||||
- **Vercel 4.5 MB limit** — set `clientUploads: true` + allow CORS PUT on your bucket
|
||||
- **R2 + Node.js** → use `storage-s3` with `region: 'auto'`, `forcePathStyle: true`, `endpoint` for uploads, separate `R2_PUBLIC_URL` for serving
|
||||
- `useCompositePrefixes: true` combines collection + document prefixes instead of document overriding collection
|
||||
- Access control proxying is on by default — disable only when collection is fully public
|
||||
- Conditionally enable with `enabled: Boolean(process.env.BUCKET)` to skip in local dev
|
||||
|
||||
## Related
|
||||
|
||||
- [[wiki/payloadcms/upload|Upload & Media]] — upload collection config, imageSizes, focal point
|
||||
- [[wiki/payloadcms/production-deployment|Production Deployment]] — ephemeral vs persistent file storage, Dockerfile
|
||||
- [[wiki/payloadcms/access-control|Access Control]] — read access applied to static file serving
|
||||
- [[wiki/payloadcms/plugin-form-builder|Form Builder Plugin]] — presigned URL pattern for form uploads
|
||||
|
||||
## Sources
|
||||
|
||||
- `raw/upload__storage-adapters.md`
|
||||
- https://payloadcms.com/docs/upload/storage-adapters
|
||||
|
|
@ -332,8 +332,24 @@ plugins: [
|
|||
- Restricted file types (`.exe`, `.php`, `.js`, HTML, scripts) are blocked by default unless `mimeTypes` is set or `allowRestrictedFileTypes: true`
|
||||
- Custom upload components must wrap Payload's `<Upload>` from `@payloadcms/ui` — a raw `<input type="file">` won't connect to the form state and causes 400 errors
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
- Enable uploads on any collection via `upload: true` or `upload: { ... }` — Payload auto-adds `filename`, `mimeType`, `filesize` fields
|
||||
- Upload only works via REST and Local API — **not** GraphQL
|
||||
- `sharp` must be in Payload config for image resizing (`create-payload-app` adds it by default)
|
||||
- `withoutEnlargement` defaults to `undefined` → images smaller than a defined size return `null` for that size
|
||||
- Image cropping happens **before** resizing — all sizes are derived from the cropped image
|
||||
- Custom Upload components **must** use `<Upload>` from `@payloadcms/ui` — raw `<input type="file">` causes 400 errors
|
||||
- Set `disableLocalStorage: true` when using cloud storage adapters (all official adapters do this automatically)
|
||||
- Restricted file types (`.exe`, `.php`, `.js`, HTML, scripts) are blocked by default unless `mimeTypes` is set or `allowRestrictedFileTypes: true`
|
||||
- `pasteURL` is enabled by default; use `allowList` for server-side fetching to bypass CORS on external URLs
|
||||
- Vercel limits server uploads to 4.5 MB — use `clientUploads: true` on the adapter to bypass
|
||||
|
||||
## Related
|
||||
|
||||
- [[wiki/payloadcms/configuration|Configuration]]
|
||||
- [[wiki/payloadcms/fields-complex|Fields Complex]]
|
||||
- [[wiki/payloadcms/configuration|Payload Config Overview]]
|
||||
- [[wiki/payloadcms/fields-upload|Upload Field]]
|
||||
- [[wiki/payloadcms/admin-panel-overview|Admin Panel Overview]]
|
||||
- [[wiki/payloadcms/custom-components-edit-view|Custom Components — Edit View]]
|
||||
- [[wiki/payloadcms/access-control|Access Control]]
|
||||
- [[wiki/payloadcms/production-deployment|Production Deployment]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue