Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 76 additions & 34 deletions docs/upload/storage-adapters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ export default buildConfig({

### Configuration Options#vercel-blob-configuration

| Option | Description | Default |
| -------------------- | -------------------------------------------------------------------- | ----------------------------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Vercel Blob adapter to | |
| `addRandomSuffix` | Add a random suffix to the uploaded file name in Vercel Blob storage | `false` |
| `cacheControlMaxAge` | Cache-Control max-age in seconds | `365 * 24 * 60 * 60` (1 Year) |
| `token` | Vercel Blob storage read/write token | `''` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| Option | Description | Default |
| ---------------------- | ---------------------------------------------------------------------------------------- | ----------------------------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Vercel Blob adapter to | |
| `addRandomSuffix` | Add a random suffix to the uploaded file name in Vercel Blob storage | `false` |
| `cacheControlMaxAge` | Cache-Control max-age in seconds | `365 * 24 * 60 * 60` (1 Year) |
| `token` | Vercel Blob storage read/write token | `''` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| `useCompositePrefixes` | Combine collection prefix with document prefix instead of document prefix overriding it. | `false` |

## S3 Storage

Expand Down Expand Up @@ -127,15 +128,16 @@ export default buildConfig({

### Configuration Options#s3-configuration

| Option | Description | Default |
| ----------------- | ----------------------------------------------------------------------- | ----------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the S3 adapter to | |
| `bucket` | The name of the S3 bucket | |
| `config` | `S3ClientConfig` object passed to the AWS SDK client | |
| `acl` | Access control list for uploaded files (e.g. `'public-read'`) | `undefined` |
| `clientUploads` | Do uploads directly on the client to bypass Vercel's 4.5MB server limit | |
| `signedDownloads` | Use presigned URLs for file downloads. Can be overridden per collection | |
| Option | Description | Default |
| ---------------------- | ---------------------------------------------------------------------------------------- | ----------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the S3 adapter to | |
| `bucket` | The name of the S3 bucket | |
| `config` | `S3ClientConfig` object passed to the AWS SDK client | |
| `acl` | Access control list for uploaded files (e.g. `'public-read'`) | `undefined` |
| `clientUploads` | Do uploads directly on the client to bypass Vercel's 4.5MB server limit | |
| `signedDownloads` | Use presigned URLs for file downloads. Can be overridden per collection | |
| `useCompositePrefixes` | Combine collection prefix with document prefix instead of document prefix overriding it. | `false` |

For full `S3ClientConfig` options, see the [AWS SDK Package](https://github.com/aws/aws-sdk-js-v3) and [`S3ClientConfig`](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3) docs.

Expand Down Expand Up @@ -243,15 +245,16 @@ export default buildConfig({

### Configuration Options#azure-configuration

| Option | Description | Default |
| ---------------------- | ------------------------------------------------------------------------ | ------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Azure Blob adapter to | |
| `allowContainerCreate` | Whether or not to allow the container to be created if it does not exist | `false` |
| `baseURL` | Base URL for the Azure Blob storage account | |
| `connectionString` | Azure Blob storage connection string | |
| `containerName` | Azure Blob storage container name | |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| Option | Description | Default |
| ---------------------- | ---------------------------------------------------------------------------------------- | ------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the Azure Blob adapter to | |
| `allowContainerCreate` | Whether or not to allow the container to be created if it does not exist | `false` |
| `baseURL` | Base URL for the Azure Blob storage account | |
| `connectionString` | Azure Blob storage connection string | |
| `containerName` | Azure Blob storage container name | |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| `useCompositePrefixes` | Combine collection prefix with document prefix instead of document prefix overriding it. | `false` |

## Google Cloud Storage

Expand Down Expand Up @@ -296,14 +299,15 @@ export default buildConfig({

### Configuration Options#gcs-configuration

| Option | Description | Default |
| --------------- | --------------------------------------------------------------------------------------------------- | --------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the storage to | |
| `bucket` | The name of the bucket to use | |
| `options` | Google Cloud Storage client configuration. See [Docs](https://github.com/googleapis/nodejs-storage) | |
| `acl` | Access control list for files that are uploaded | `Private` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| Option | Description | Default |
| ---------------------- | --------------------------------------------------------------------------------------------------- | --------- |
| `enabled` | Whether or not to enable the plugin | `true` |
| `collections` | Collections to apply the storage to | |
| `bucket` | The name of the bucket to use | |
| `options` | Google Cloud Storage client configuration. See [Docs](https://github.com/googleapis/nodejs-storage) | |
| `acl` | Access control list for files that are uploaded | `Private` |
| `clientUploads` | Do uploads directly on the client to bypass limits on Vercel. | |
| `useCompositePrefixes` | Combine collection prefix with document prefix instead of document prefix overriding it. | `false` |

## Uploadthing Storage

Expand Down Expand Up @@ -456,6 +460,44 @@ This plugin is configurable to work across many different Payload collections. A
| `generateFileURL` | [GenerateFileURL](https://github.com/payloadcms/payload/blob/main/packages/plugin-cloud-storage/src/types.ts#L67) | Override the generated file URL with one that you create. |
| `prefix` | `string` | Set to `media/images` to upload files inside `media/images` folder in the bucket. |

## Prefix Composition

Storage adapters support two types of prefixes:

- **Collection prefix**: Set at the adapter configuration level (e.g., `prefix: 'media-folder'`)
- **Document prefix**: Set per-document via the `prefix` field on the upload collection

By default, if a document has a prefix, it **overrides** the collection prefix entirely.

With `useCompositePrefixes: true`, the prefixes are **combined**:

```
# Without useCompositePrefixes (default)
Collection prefix: media-folder
Document prefix: user-123
Result: user-123/image.jpg

# With useCompositePrefixes: true
Collection prefix: media-folder
Document prefix: user-123
Result: media-folder/user-123/image.jpg
```

This is useful when you want a base folder structure (collection prefix) while still allowing per-document organization (document prefix).

```ts
s3Storage({
collections: {
media: {
prefix: 'uploads', // All files go under uploads/
},
},
useCompositePrefixes: true, // Document prefixes append to collection prefix
bucket: process.env.S3_BUCKET,
// ...
})
```

## Payload Access Control

Payload ships with [Access Control](../access-control/overview) that runs _even on statically served files_. The same `read` Access Control property on your `upload`-enabled collections is used, and it allows you to restrict who can request your uploaded files.
Expand Down
14 changes: 12 additions & 2 deletions packages/plugin-cloud-storage/src/admin/fields/getFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ interface Args {
alwaysInsertFields?: boolean
collection: CollectionConfig
prefix?: string
/**
* When true, do not default the `prefix` field to the collection prefix; the
* document field holds only the document-level segment.
*/
useCompositePrefixes?: boolean
}

export const getFields = ({ alwaysInsertFields, collection, prefix }: Args): Field[] => {
export const getFields = ({
alwaysInsertFields,
collection,
prefix,
useCompositePrefixes = false,
}: Args): Field[] => {
const baseURLField: TextField = {
name: 'url',
type: 'text',
Expand Down Expand Up @@ -122,7 +132,7 @@ export const getFields = ({ alwaysInsertFields, collection, prefix }: Args): Fie
fields.push({
...basePrefixField,
...(existingPrefixField || {}),
defaultValue: prefix ? path.posix.join(prefix) : '',
defaultValue: useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : '',
} as TextField)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const createClientUploadHandler = <T extends Record<string, unknown>>({
handler: (args: {
apiRoute: string
collectionSlug: UploadCollectionSlug
docPrefix?: string
extra: T
file: File
prefix?: string
Expand Down Expand Up @@ -48,10 +49,11 @@ export const createClientUploadHandler = <T extends Record<string, unknown>>({
if (enabled) {
setUploadHandler({
collectionSlug,
handler: ({ file, updateFilename }) => {
handler: ({ docPrefix, file, updateFilename }) => {
return handler({
apiRoute,
collectionSlug,
docPrefix,
extra,
file,
prefix,
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-cloud-storage/src/exports/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { getFileKey } from '../utilities/getFileKey.js'
export { getFilePrefix } from '../utilities/getFilePrefix.js'
export { initClientUploads } from '../utilities/initClientUploads.js'
export { sanitizePrefix } from '../utilities/sanitizePrefix.js'
8 changes: 7 additions & 1 deletion packages/plugin-cloud-storage/src/fields/getFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ interface Args {
disablePayloadAccessControl?: true
generateFileURL?: GenerateFileURL
prefix?: string
/**
* When true, do not default the `prefix` field to the collection prefix; the
* document field holds only the document-level segment.
*/
useCompositePrefixes?: boolean
}

export const getFields = ({
Expand All @@ -26,6 +31,7 @@ export const getFields = ({
disablePayloadAccessControl,
generateFileURL,
prefix,
useCompositePrefixes = false,
}: Args): Field[] => {
const baseURLField: TextField = {
name: 'url',
Expand Down Expand Up @@ -194,7 +200,7 @@ export const getFields = ({
fields.push({
...basePrefixField,
...(existingPrefixField || {}),
defaultValue: prefix ? path.posix.join(prefix) : '',
defaultValue: useCompositePrefixes ? '' : prefix ? path.posix.join(prefix) : '',
} as TextField)
}

Expand Down
9 changes: 8 additions & 1 deletion packages/plugin-cloud-storage/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import { getPreserveFileDataHook } from './hooks/preserveFileData.js'
export const cloudStoragePlugin =
(pluginOptions: PluginOptions) =>
(incomingConfig: Config): Config => {
const { alwaysInsertFields, collections: allCollectionOptions, enabled } = pluginOptions
const {
alwaysInsertFields,
collections: allCollectionOptions,
enabled,
useCompositePrefixes,
} = pluginOptions
const config = { ...incomingConfig }

// If disabled but alwaysInsertFields is true, only insert fields without full plugin functionality
Expand All @@ -46,6 +51,7 @@ export const cloudStoragePlugin =
disablePayloadAccessControl: options.disablePayloadAccessControl,
generateFileURL: options.generateFileURL,
prefix: options.prefix,
useCompositePrefixes,
})

return {
Expand Down Expand Up @@ -85,6 +91,7 @@ export const cloudStoragePlugin =
disablePayloadAccessControl: options.disablePayloadAccessControl,
generateFileURL: options.generateFileURL,
prefix: options.prefix,
useCompositePrefixes,
})

const handlers = [
Expand Down
10 changes: 10 additions & 0 deletions packages/plugin-cloud-storage/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,14 @@ export interface PluginOptions {
* Default: true
*/
enabled?: boolean
/**
* When true (compositional prefixes), the stored `prefix` field is only the
* document-level segment; the collection prefix comes from plugin options and
* must not be pre-filled as the field default.
*
* Set by storage adapters that support compositional prefixes (e.g. S3, Azure, R2, Vercel Blob, GCS).
*
* @default false
*/
useCompositePrefixes?: boolean
}
Loading
Loading