> ## Documentation Index
> Fetch the complete documentation index at: https://payload-storage-bunny.seshuk.im/llms.txt
> Use this file to discover all available pages before exploring further.

# Collection overrides

> Override individual plugin options per collection, including partial vs full-replacement options and per-collection zones or libraries.

Every collection listed under `collections` can use the global options as-is, or override individual options. This lets you keep one set of plugin options while giving each collection different behavior.

```ts theme={null}
collections: {
  media: true,       // use all global settings
  videos: {
    prefix: 'videos',
    disablePayloadAccessControl: true,
    stream: {
      thumbnailTime: 3000,
    },
  },
}
```

## Collection options

| Option | Type | Default | Description |
| - | - | - | - |
| `prefix` | `string` | `''` (zone root) | Folder path inside Bunny Storage, e.g. `prefix: 'images'` stores files under an "images" folder. Defaults to the zone root (empty string). |
| `disablePayloadAccessControl` | `boolean` | `false` | `true` serves files directly from the Bunny CDN, bypassing Payload's access rules. `false` (default) proxies every request through Payload. |
| `storage` | `object \| false` | inherits global | Partial override (`uploadTimeout`, `clientUploads`), full options (own `apiKey`/`hostname`/`zoneName`) to use its own zone, or `false` to disable. See [Storage](/v4/configuration/storage/overview#collection-overrides). |
| `stream` | `object \| false` | inherits global | Partial override (`mimeTypes`, `mp4Fallback`, `thumbnailTime`, `uploadTimeout`, `tus` — `tus: true` or an object enables TUS, `tus: false` disables it), full options (own `apiKey`/`hostname`/`libraryId`) to use its own library, or `false` to disable. See [Stream](/v4/configuration/stream/overview#collection-overrides). |
| `storage.clientUploads` | `boolean \| object` | inherits global | Enable, disable, or override browser-direct uploads for this collection. See [Client uploads](/v4/configuration/storage/client-uploads#per-collection-client-uploads). |
| `purge` | `boolean \| object` | inherits global | `true` or an object enables purging, even when the global `purge` is off. An object overrides `async`. `false` disables purging. See [Cache purging](/v4/configuration/cache-purge#collection-overrides). |
| `signedUrls` | `boolean \| object` | inherits global | Override signed URL settings, or `false` to disable. See [Signed URLs](/v4/configuration/signed-urls#collection-overrides). |
| `thumbnail` | `boolean \| object` | inherits global | Override thumbnail settings, or `false` to disable. See [Thumbnails](/v4/configuration/thumbnails#collection-overrides). |
| `urlTransform` | `boolean \| object \| function` | inherits global | Override URL rewriting, or `false` to disable. See [URL transforms](/v4/configuration/url-transforms#collection-overrides). |

`prefix` and `disablePayloadAccessControl` come from `@payloadcms/plugin-cloud-storage` and apply to every collection using this plugin. `disableLocalStorage` can't be passed here — the plugin always sets it to `true` on managed collections.

## Override surface: partial vs full replacement

Not every option can be tweaked in isolation. Some are **partially overridable** (merged onto the global zone/library); others exist **only inside full replacement options** and can never be set on their own. The plugin flips `storage`/`stream` into full-replacement mode the moment the object contains `apiKey` — see [Own zone or library per collection](#own-zone-or-library-per-collection).

| Option | Partially overridable? | Full-replace only? | Notes |
| - | - | - | - |
| `storage.uploadTimeout` | yes | — | Merged onto the global zone. |
| `storage.clientUploads` | yes | also settable inside full options | Object merges `access`/`prefix`; the `edge` block replaces. |
| `storage.apiKey` / `hostname` / `zoneName` | no | yes — all three required together | Presence of `apiKey` triggers full replacement. |
| `storage.region` / `s3` / `tokenSecurityKey` | no | yes | Reachable only inside full storage options. |
| `stream.mimeTypes` / `mp4Fallback` / `thumbnailTime` / `uploadTimeout` | yes | — | Merged onto the global library. |
| `stream.tus` | yes — `true` / `false` / `{ autoMode, expiresIn }` | — | Works even when the global `stream.tus` is off. |
| `stream.tus.access` | no | yes | The partial nested surface is `autoMode`/`expiresIn` only. |
| `stream.apiKey` / `hostname` / `libraryId` | no | yes — all three required together | Presence of `apiKey` triggers full replacement. |
| `stream.tokenSecurityKey` / `webhook` / `referer` | no | yes | Reachable only inside full stream options. |
| `stream.cleanup` | yes — `maxAge` only | — | The cleanup `schedule` is global-only. |
| `purge` | yes — `false` / `true` / `{ async }` | — | Merged onto the global purge options. |
| `signedUrls` | yes — `false` / `true` / object | — | Object merged field-by-field onto the global options (including `staticHandler`). |
| `thumbnail` | yes — `false` / `true` / object | — | Merged onto the global thumbnail options. |
| `urlTransform` | yes — `false` / `true` / object / function | — | An object merges onto a global object; a function replaces it. |
| `prefix` / `disablePayloadAccessControl` | collection-level | — | From `@payloadcms/plugin-cloud-storage`; set directly on the collection. |

## How overrides merge

A **partial** override object (one without `apiKey`) is merged on top of the matching global option — it doesn't replace the whole block. An object that *includes* `apiKey` isn't a merge at all: it is a complete replacement of that zone/library, with every credential field required and nothing inherited — see [Own zone or library per collection](#own-zone-or-library-per-collection). For a partial override, for example:

```ts theme={null}
// Global options
stream: {
  uploadTimeout: 300000,
  mp4Fallback: false,
}

// Collection override
collections: {
  largeVideos: {
    stream: {
      uploadTimeout: 600000, // only this changes
    },
  },
}

// Result for largeVideos: uploadTimeout 600000, mp4Fallback still false (inherited)
// Result for every other collection: uploadTimeout 300000, mp4Fallback false
```

## Own zone or library per collection

A collection's `storage` or `stream` can go beyond a partial override and supply **full options** — its own `apiKey`, `hostname`, `zoneName` (storage) or `libraryId` (stream). That collection then uses its OWN Bunny zone / library, and the global one is ignored for it. This is how you point different collections at different tenants' zones and libraries.

```ts theme={null}
collections: {
  // Full options → own storage zone + own stream library
  tenantA: {
    storage: {
      apiKey: process.env.TENANT_A_STORAGE_API_KEY,
      hostname: 'tenant-a.b-cdn.net',
      zoneName: 'tenant-a-zone',
    },
    stream: {
      apiKey: process.env.TENANT_A_STREAM_API_KEY,
      hostname: 'vz-tenant-a-123.b-cdn.net',
      libraryId: 654321,
    },
  },
}
```

The rules:

* **How it's detected.** The plugin treats the override as full options when it contains `apiKey` (`'apiKey' in override`); otherwise it's a partial override merged onto the global options. `{ uploadTimeout: 5 }` is a partial override; `{ apiKey, hostname, zoneName }` is full options.
* **No inheritance.** Full options inherit **nothing** from the global zone/library. Omitted optional keys (storage: `region`, `s3`, `tokenSecurityKey`, `clientUploads`, `uploadTimeout`; stream: `mimeTypes`, `mp4Fallback`, `tus`, `referer`, …) fall back to plugin defaults — never to the global options' values.
* **Signed URLs.** Provide that zone/library's own `tokenSecurityKey` when [signed URLs](/v4/configuration/signed-urls) are enabled for the collection.
* **Webhooks.** A per-collection stream library can carry its own `webhook.secret`; the single webhook endpoint accepts every configured secret.
* **Cleanup.** Per-collection stream `cleanup` controls only `maxAge`. The cleanup `schedule` is global-only (one task for the whole plugin).
* **Client uploads travel with the zone.** Full storage options' `clientUploads` (including its `edge` script and secret) belongs to that zone and doesn't merge with the global `storage.clientUploads`.
* **Top-level optional.** Because a collection can own its whole zone/library, the top-level `storage`/`stream` are optional — options made entirely of per-collection zones is valid.

<Warning>
  **Adding `apiKey` to a small tweak turns it into a full replacement**

  The trigger is purely the presence of `apiKey`, so an object you meant as a minor tweak becomes a complete replacement the moment you add credentials to it. For example, `stream: { apiKey, hostname, libraryId, uploadTimeout: 5 }` does **not** inherit `mimeTypes`, `mp4Fallback`, `tus`, `webhook`, or anything else from the global library — all of those reset to plugin defaults. If you only want to change `uploadTimeout` on the global library, leave `apiKey` out.
</Warning>

The completeness of full options is validated at startup: full storage options need `apiKey`/`hostname`/`zoneName`; full stream options need `apiKey`/`hostname`/`libraryId`. A single stream library must not be configured with conflicting `apiKey` values across collections.

## Disabling a service or feature: use `false`

Some options accept `false` to turn a feature off for one collection, even if it is enabled globally.

<Warning>
  This only works with an explicit `=== false` check, so always pass the literal `false` — an empty object or
  `undefined` means "inherit the global setting," not "disable."
</Warning>

```ts payload.config.ts theme={null}
collections: {
  // Only Bunny Storage — video uploads are rejected
  images: {
    stream: false,
  },
  // Only Bunny Stream — non-video uploads fail
  videos: {
    storage: false,
  },
  // Both services enabled, but no signed URLs for this one
  publicMedia: {
    signedUrls: false,
  },
}
```

`purge`, `signedUrls`, `thumbnail`, `urlTransform`, `storage.clientUploads`, and `stream.tus` all support `false` this way. A collection must keep at least one of `storage` or `stream` active — disabling both is an options error.

## Enabling a collection with defaults

Pass `true` instead of an object to use every global setting unmodified. `false` leaves the collection out, together with its hidden `prefix` field, so it changes the SQL schema. To turn the plugin on or off with an environment variable, use `enabled`.

```ts theme={null}
collections: {
  media: true,
}
```

## Accessing the resolved options

Sometimes you need the settings the plugin resolved for a collection — the library id and API key for a Stream recipe, the zone name for a direct Storage call — from inside a hook, endpoint, or script. Rather than re-derive them from `process.env` or hand-maintain a lookup, ask the plugin. These accessors read the options it stashed on `payload.config.custom`, with global options and per-collection overrides already applied.

```ts theme={null}
import {
  getBunnyStreamForCollection,
  getBunnyStorageForCollection,
  getBunnyCollectionOptions,
  getBunnyOptions,
} from '@seshuk/payload-storage-bunny'

// inside a hook / endpoint
const stream = getBunnyStreamForCollection(req.payload, 'media')
// → { apiKey, libraryId, hostname, tokenSecurityKey? } | undefined
```

| Accessor | Returns |
| - | - |
| `getBunnyStorageForCollection(payload, slug)` | `{ apiKey, zoneName, hostname, region, s3, tokenSecurityKey? }` or `undefined`. `s3` is `true` or `false`. |
| `getBunnyStreamForCollection(payload, slug)` | `{ apiKey, libraryId, hostname, tokenSecurityKey? }` or `undefined`. |
| `getBunnyCollectionOptions(payload, slug)` | `{ storage?, stream? }` for a managed collection, or `undefined`. |
| `getBunnyOptions(payload)` | The full normalized plugin options (escape hatch), or `undefined`. |

Semantics:

* **`payload`** is a running Payload instance — `req.payload` in a hook or endpoint, or the instance from `getPayload(...)` in a script.
* **Never throws.** Each accessor returns `undefined` when the plugin is absent or disabled, the slug isn't one of the plugin's `collections`, or (for a per-backend accessor) that backend is off for the collection. `getBunnyCollectionOptions` returns an object for any managed slug — a backend the collection doesn't use is simply absent from it.
* **`getBunnyOptions` is an escape hatch.** It returns the plugin's internal normalized shape, which may change between minor releases. Treat it as read-only and prefer the curated accessors above.
* The curated results are fresh copies; mutating them doesn't affect the plugin.

<Warning>
  **Server-side only**

  The returned objects contain your Bunny API keys. Use them in server code — hooks, endpoints, scripts — and never send them to the client.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.