> ## 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.

# Stream

> Configure the Bunny Stream block for video and audio with HLS/MP4 playback, thumbnails, cleanup, and per-collection libraries.

Bunny Stream handles video and audio: adaptive HLS/MP4 playback, thumbnails, and resumable uploads for large files. It is the `stream` block passed to `bunnyStorage(...)`.

```ts payload.config.ts theme={null}
bunnyStorage({
  collections: { media: true },
  stream: {
    apiKey: process.env.BUNNY_STREAM_API_KEY,
    hostname: process.env.BUNNY_STREAM_HOSTNAME, // e.g. vz-xxxx.b-cdn.net
    libraryId: Number(process.env.BUNNY_STREAM_LIBRARY_ID),
    mp4Fallback: true,
  },
})
```

With Payload access control on (the default), a Stream collection needs `mp4Fallback: true` or signed URLs with redirect — otherwise the plugin throws at startup. See [Access control and mp4Fallback](/v4/configuration/stream/overview#access-control-and-mp4-fallback). Set `disablePayloadAccessControl: true` on the collection to serve straight from the CDN instead, where this requirement doesn't apply.

Files whose MIME type matches `mimeTypes` are routed to Bunny Stream. Everything else goes to [Bunny Storage](/v4/configuration/storage/overview), if configured. Video metadata for each document lives in the [`bunnyData`](/v4/guides/stored-data) field.

## Options

| Option | Type | Required | Default | Description |
| - | - | - | - | - |
| `apiKey` | `string` | Required | – | Bunny Stream API key. |
| `hostname` | `string` | Required | – | Stream CDN domain, e.g. `vz-abc123def-456.b-cdn.net`. |
| `libraryId` | `number` | Required | – | Your Video Library ID. |
| `mimeTypes` | `string[]` | Optional | video/audio formats listed below | File types routed to Bunny Stream. A collection's own `upload.mimeTypes` setting can narrow this further — if a type is blocked there, it is rejected even if allowed here. |
| `mp4Fallback` | `boolean` | Optional | `false` | Also store MP4 renditions alongside HLS. Required when Payload access control is on, unless you use signed URLs with redirect — see [below](/v4/configuration/stream/overview#access-control-and-mp4-fallback). |
| `referer` | `string` | Optional | – | `Referer` header sent on server-side requests when serving the MP4 fallback through Payload access control. Set it only if your Stream library blocks requests without a referrer (BlockNoneReferrer); the value must satisfy the library's allowed referrers. Leave unset for default libraries. |
| `thumbnailTime` | `number` | Optional | – | Moment in the video (ms) to use as the thumbnail. Used with [`thumbnail: true`](/v4/configuration/thumbnails). |
| `tokenSecurityKey` | `string` | Optional | – | Security key for signing stream URLs. Required if you enable [signed URLs](/v4/configuration/signed-urls) for stream. |
| `uploadTimeout` | `number` | Optional | `300000` (ms) | Upload timeout for standard (non-TUS) uploads. |
| `tus` | `boolean \| object` | Optional | disabled | Enable resumable TUS uploads. See [TUS uploads](/v4/configuration/stream/tus). |
| `cleanup` | `boolean \| object` | Optional | disabled | Automatically remove videos from unfinished or unsaved uploads. See [Cleanup](/v4/configuration/stream/overview#cleanup). |
| `webhook` | `{ secret: string }` | Optional | disabled | Receive status updates from Bunny Stream. See [Webhook](/v4/configuration/stream/webhooks). |

Default `mimeTypes` cover the common video and audio formats: MP4, MKV, WebM, FLV, AVI, MOV, WMV, MPEG, TS, MXF, and MP3/OGG/WAV audio.

<Info>
  The top-level `stream` block is optional. If every collection points at [its own stream library](#collection-overrides), you can omit the global `stream` entirely.
</Info>

## Access control and MP4 fallback

By default, files are served through Payload's access control (`disablePayloadAccessControl` isn't set on the collection). For Stream videos in this mode, Bunny can't serve HLS directly through Payload's proxy, so you need one of:

* **`mp4Fallback: true`** — Payload proxies the MP4 rendition instead of HLS.
* **Signed URLs with redirect** — set `signedUrls.staticHandler.redirect: true` (globally or on the collection) so Payload redirects straight to Bunny's HLS stream instead of proxying it. This is the faster option — see [Signed URLs](/v4/configuration/signed-urls).

<Warning>
  The plugin's options validator rejects a collection at startup if neither is set and access control is on. If you set `disablePayloadAccessControl: true` on a collection instead, files are served directly from the Bunny CDN and this restriction doesn't apply.
</Warning>

When Payload proxies the MP4 fallback, it fetches the file from Bunny server-side. If your Stream library uses a BlockNoneReferrer rule, set [`referer`](#options) to a value the library allows so those requests aren't rejected. Default libraries need nothing here.

## Cleanup

Some uploads leave videos in your library that no document uses. This happens when an upload stops halfway, for example after a closed tab or a failed TUS session. It also happens when the upload finishes but the document is never saved, for example when a bulk upload save fails. Enable `cleanup` to remove these videos on a schedule.

Cleanup only checks uploads that this plugin started. Before it deletes a fully uploaded video, it makes sure no document references it. It never touches videos that other services or the Bunny dashboard add to the same library. Keep `maxAge` longer than an editor might leave an upload unsaved, because a document saved after cleanup has run points to a deleted video.

```ts theme={null}
stream: {
  // ...
  cleanup: true, // daily cleanup with defaults
}
```

Or customize:

```ts theme={null}
stream: {
  cleanup: {
    maxAge: 86400,                              // seconds before an unsaved upload is considered dead (default)
    schedule: { cron: '0 2 * * *', queue: 'storage-bunny' }, // default: daily at 2am
  },
}
```

| Option | Type | Default | Description |
| - | - | - | - |
| `maxAge` | `number` | `86400` | Seconds after which an unsaved upload is considered dead. |
| `schedule` | object | `{ cron: '0 2 * * *', queue: 'storage-bunny' }` | Cron schedule for the cleanup job. |

Cleanup registers a Payload job task, so your project needs [Jobs Queue](https://payloadcms.com/docs/jobs-queue/overview) configured and the queue processed (e.g. via a cron endpoint or `payload jobs:run`).

<Info>
  There is one cleanup task for the whole plugin, and its `schedule` is **global-only** — set it on the top-level `stream.cleanup`. When a collection points at its own library, its `cleanup` controls only `maxAge` (each library's stale sessions are swept with that library's own cutoff); it can't set a separate `schedule`.
</Info>

## Collection overrides

A collection's `stream` can be one of three things:

* a **partial override** (`mimeTypes`, `mp4Fallback`, `thumbnailTime`, `tus`, `uploadTimeout`) — merged onto the global library;
* **full options** (with their own `apiKey`/`hostname`/`libraryId`) — this collection uses its OWN stream library, ignoring the global library entirely;
* **`false`** — disable Bunny Stream for this collection.

```ts theme={null}
collections: {
  // Partial override — tweak the global library
  largeVideos: {
    stream: {
      mimeTypes: ['video/mp4'],  // narrow allowed types for this collection
      mp4Fallback: true,
      thumbnailTime: 3000,
      uploadTimeout: 600000,     // 10 minutes
      tus: {
        expiresIn: 7200,         // 2 hours
        autoMode: true,
      },
    },
  },
  // Full options — this collection uses its own library
  tenantA: {
    stream: {
      apiKey: process.env.TENANT_A_STREAM_API_KEY,
      hostname: 'vz-tenant-a-123.b-cdn.net',
      libraryId: 654321,
      mp4Fallback: true,
      tokenSecurityKey: process.env.TENANT_A_STREAM_TOKEN_KEY, // for signed URLs on this library
      webhook: { secret: process.env.TENANT_A_STREAM_READONLY_API_KEY }, // this library's Read-Only API key
      cleanup: { maxAge: 43200 }, // per-library cutoff; schedule stays global-only
    },
  },
  shortClips: {
    stream: {
      tus: false, // disable TUS for this collection only
    },
  },
  imagesOnly: {
    stream: false, // disable Bunny Stream for this collection — videos will fail to upload
  },
}
```

The plugin distinguishes full options from a partial override by whether it includes `apiKey`. Full options **inherit nothing** from the global library — `mimeTypes`, `mp4Fallback`, `tus`, and the rest fall back to plugin defaults, not the global library's values. Provide that library's own `tokenSecurityKey` for [signed URLs](/v4/configuration/signed-urls), and its own `webhook.secret` if you use webhooks. Per-collection `cleanup` carries only `maxAge` — the cleanup `schedule` is global-only.

A partial `tus: true` or `tus: { … }` turns TUS on for that collection, even when the global `stream.tus` is off. `tus: false` turns it off.

See [Collection overrides](/v4/configuration/collection-overrides) for the full-options vs partial-override rules and the complete list of options.


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