collections can use the global config as-is, or override individual options. This lets you keep one plugin config while giving each collection different behavior.
Collection options
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 a full replacement config and can never be set on their own. The plugin flipsstorage/stream into full-replacement mode the moment the object contains apiKey — see Own zone or library per collection.
How overrides merge
A partial override object (one withoutapiKey) 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. For a partial override, for example:
Own zone or library per collection
A collection’sstorage or stream can go beyond a partial override and supply a full config — 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.
- How it’s detected. The plugin treats the override as a full config when it contains
apiKey('apiKey' in override); otherwise it’s a partial override merged onto the global config.{ uploadTimeout: 5 }is a partial override;{ apiKey, hostname, zoneName }is a full config. - No inheritance. A full config inherits 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 config’s values. - Signed URLs. Provide that zone/library’s own
tokenSecurityKeywhen 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
cleanupcontrols onlymaxAge. The cleanupscheduleis global-only (one task for the whole plugin). - Client uploads travel with the zone. A full storage config’s
clientUploads(including itsedgescript and secret) belongs to that zone and doesn’t merge with the globalstorage.clientUploads. - Top-level optional. Because a collection can own its whole zone/library, the top-level
storage/streamare optional — a config made entirely of per-collection zones is valid.
apiKey/hostname/zoneName; a full stream config needs 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.
payload.config.ts
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 a config error.
Enabling a collection with defaults
Passtrue instead of an object to use every global setting unmodified:
Accessing the resolved config
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 fromprocess.env or hand-maintain a lookup, ask the plugin. These accessors read the config it stashed on payload.config.custom, with global config and per-collection overrides already applied.
Semantics:
payloadis a running Payload instance —req.payloadin a hook or endpoint, or the instance fromgetPayload(...)in a script.- Never throws. Each accessor returns
undefinedwhen the plugin is absent or disabled, the slug isn’t one of the plugin’scollections, or (for a per-backend accessor) that backend is off for the collection.getBunnyCollectionConfigreturns an object for any managed slug — a backend the collection doesn’t use is simply absent from it. getBunnyConfigis 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.