Skip to main content
Run bunny skills install --global so AI coding agents, such as Claude Code, Cursor, and Codex, can run this migration for you. Then ask your agent: “Migrate my Cloudflare Stream videos to Bunny Stream.”
bunny stream import is in Public Preview and still in development. Commands, flags, and output may change, so what you see can differ from the examples in this guide. To report a bug, open an issue on the Bunny CLI repository.
Bunny Stream bills only for the storage and bandwidth you use. See the Bunny Stream pricing page for current rates.

Overview

Diagram of the Bunny CLI listing Cloudflare Stream videos and Bunny Stream fetching each MP4 download directly from Cloudflare
The Bunny CLI lists every ready video on your Cloudflare Stream account, asks Cloudflare to generate an MP4 download for each one, and hands that download to Bunny Stream, which fetches the file directly from Cloudflare. Nothing is downloaded to your machine. Each imported video is tagged with its Cloudflare Stream ID under the cfStreamId metatag, so running the import again skips videos that are already imported.

Scope

The import copies the video files, names, and descriptions. Settings that control how and where videos play stay on Cloudflare, so plan to set up their Bunny equivalents.
Signed URLs and allowed origins are not migrated. Every imported video follows the security settings of its Bunny library, and a new library allows playback from any site.
  • requireSignedURLs: The CLI cannot import a video that requires signed URLs, so it fails these videos with an error that names the setting. To import them, turn off Require Signed URLs on each one in Cloudflare Stream, run the import, then turn it back on. While it is off, the video plays on Cloudflare without a token, so keep that window short.
  • Allowed origins: Cloudflare’s per-video allowed origins are not copied. Add the same sites to the library’s Allowed domains on Bunny.
Before you publish the new URLs, turn on embed view token authentication or CDN token authentication if you signed your Cloudflare playback URLs.

Prerequisites

Migrate your videos

1

Install and authenticate the Bunny CLI

Install the Bunny CLI with the shell installer or npm:
The installer downloads a prebuilt binary for your platform and places it on your PATH.
See Installation for more options. Then authenticate the CLI with your bunny.net account:
bunny login opens your browser to complete authentication and stores the credentials locally. The first time you log in from an interactive terminal, it also offers to install the agent skill for AI coding tools. On a remote machine or in CI without a browser, run bunny login --api-key "$BUNNYNET_API_KEY" instead, with a key from Account Settings > API key.
2

Create a Bunny Stream library

Create a library for the imported videos, or skip this step to use an existing one:
DE is always the main storage region. Replicate to the regions closest to your audience, from UK, SE, NY, LA, SG, SYD, BR, and JH. You can’t remove a replication region later.
Set encoding options, such as --resolutions or --encoding-tier, when you create the library. Changes you make later apply only to new uploads, not to videos that are already imported. Run bunny stream libraries create --help to see every option, or read Encoding.
When the CLI asks how to add videos, choose Later. You start the import in the next steps.Copy the library ID from the output. You pass it to --library in the import commands. The flag also accepts the library’s name, and --lib works as a short alias.
3

Create a Cloudflare Stream API token

The Bunny CLI needs your Cloudflare Account ID and an API token with the Stream:Edit permission. Edit lets the CLI list your videos and generate an MP4 download for any video that does not already have one.To find your Account ID, open the Cloudflare dashboard and go to Build > Images & Stream > Hosted videos. The Account ID appears in the sidebar under Account details.
The Hosted videos page in the Cloudflare dashboard with the Account ID under Account details
To create the token, open User API Tokens and click + Create Token.
The User API Tokens page in Cloudflare with the Create Token button
Click Create Custom Token, enter a token name, such as migrate-to-bunny, and add the permission Account > Stream > Edit. Edit includes read access, so this one permission covers both listing videos and generating their MP4 downloads. Click Continue to summary, then Create Token.
The custom token form in Cloudflare with the Account Stream Edit permission
Copy the token. Cloudflare shows it only once.
The Cloudflare confirmation page showing the new API token
4

Preview the migration with a dry run

Run the import with --dry-run, using the library ID from step 2:
--dry-run prints the import plan without making any changes to either platform.The CLI then prompts for your Cloudflare credentials. Paste the API token at the API Token prompt and the Account ID at the Account ID prompt:
Once Cloudflare accepts them, the CLI offers to save them so later runs skip these prompts. Press Enter to accept:
The CLI stores saved credentials in ~/.config/bunnynet/stream-import-credentials.json (or under $XDG_CONFIG_HOME/bunnynet/ when it is set). Only your user account can read the file, and it is separate from the main Bunny CLI config. Saved credentials belong to the active Bunny CLI profile. If Cloudflare later rejects them, for example after you revoke the token, the CLI deletes them and prompts again on the next run. If you answer no, the CLI prompts on every run.The CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables always take priority over saved credentials.
The dry run then prints a summary and the import plan:
Already imported counts videos that are already in the library, and To import counts the ones a real run would import. Every video is listed under (no collection), since Cloudflare Stream has no folders.Bunny Stream accepts sources up to 2160p (3840x2160, or 2160x3840 in portrait) and up to 72 hours long. The dry run warns about any video beyond those limits, as it will fail on Bunny:
The dry run does not check requireSignedURLs. A video with Require Signed URLs turned on still appears in the plan and fails during the import, so turn the setting off on those videos first, as described in Scope.
5

Run the import

Run the same command without --dry-run:
The CLI prints the same summary, then asks for confirmation. Type y and press Enter to start:
The CLI skips videos that are already imported, has Cloudflare generate an MP4 download for each new one, hands those to Bunny three at a time, and tags each video with cfStreamId. It returns once every video is queued. Bunny then fetches each file directly from Cloudflare and encodes it.
The import command also accepts these flags:
  • --wait keeps the command running, with a live encoding progress indicator, until Bunny has encoded every video.
  • --force skips the confirmation prompt and the offer to retry failed videos, for unattended or CI runs.
  • --concurrency N sets how many videos the CLI passes to Bunny in parallel, from 1 to 20.
  • --clean-failed deletes the failed Bunny copies of videos before importing them again, including videos that Bunny rejected.
  • --retry-stalled deletes and imports again any video stuck at zero bytes on Bunny.
  • --request-timeout sets how many seconds one request to Cloudflare or Bunny can take before it gives up (default 30).
  • --video-timeout sets how many seconds the hand-off of one video to Bunny, or with --wait its encoding, can take before it gives up (default 5400).
Run bunny stream import --help for the full list.
6

Watch encoding progress

The import returns before Bunny finishes encoding. Check progress with the status command:
It prints one row per queued video, with its status on Bunny, encode percentage, size, and when it was queued:
Run it again until the last line reports 0 processing. The command exits with a non-zero code when any video has failed, so you can also use it as a check in scripts.
7

Verify in the bunny.net dashboard

Open the bunny.net dashboard and go to Stream > Your library > Manage library to confirm every video has encoded.
The Manage library page in the bunny.net dashboard listing the imported videos
Set up the library’s security settings if you have not already, then replace the Cloudflare Stream embed URLs and player references in your site with their Bunny Stream equivalents. See Embedding videos for the embed and player options.
8

Clean up the MP4 downloads on Cloudflare

The import generates an MP4 download for every video that did not already have one. According to Cloudflare’s documentation, MP4 downloads add no storage cost, but Cloudflare bills for the video’s duration each time one is downloaded. Bunny’s fetch counts as one download per video.If you plan to delete your videos from Cloudflare Stream, deleting a video also deletes its downloads, so there is nothing else to do.If you are keeping the videos on Cloudflare for now, set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN in your shell, then delete the downloads you do not need through the Cloudflare API:
$VIDEO_UID is the Cloudflare video ID, stored in the cfStreamId metatag on each Bunny video. To list the video IDs on the account, run:
The command lists at most 10,000 videos. On a larger account, read the remaining IDs from the cfStreamId metatags or the Cloudflare API.The CLI reuses MP4 downloads that existed before the migration. If your site already serves some of those downloads, leave them in place.

Troubleshooting

When videos fail during an interactive import, the CLI lists each failure and offers to retry them:
Answer y to retry them in the same run. If the import was interrupted, or you declined the retry, run it again with --resume:
--resume retries failed videos, processes any that were still outstanding, and re-applies the cfStreamId tag to videos Bunny fetched before the run stopped. It stops retrying a video after three failed attempts and skips videos that Bunny rejected. To retry those too, add --clean-failed, which deletes their Bunny copies first.Running the import again is always safe. Videos that already carry the cfStreamId tag are skipped, and the CLI reports Everything is already imported once nothing is left.
Cloudflare did not finish generating the MP4 download within five minutes, which can happen with long or high-resolution videos. It can also mean the video was deleted from Cloudflare after the import started.Cloudflare keeps generating the download after the CLI stops waiting, so wait a few minutes, then retry. The retry picks up the finished download instead of requesting a new one.
The video has requireSignedURLs turned on. Turn off Require Signed URLs on it in Cloudflare Stream, retry, then turn it back on. See Scope.
Cloudflare reported an error while generating the download, and the CLI prints its reason. Check the video in the Cloudflare dashboard. A retry only helps once Cloudflare can generate the download.
If bunny stream import status reports videos stuck at zero bytes, run the import again with --retry-stalled to delete and fetch them again.
Last modified on November 5, 2026