Skip to main content
Django creates the video in Bunny Stream and signs an upload. The browser sends the file to us over TUS, and your API key stays in the server’s environment. Everything here runs on Django and the standard library.

Django example on GitHub

Django 6, no database, started with one uv run command.

Quickstart

1

Add your library credentials

.env
Copy both from your library’s API page. Django won’t read .env on its own, and uv run --env-file .env manage.py runserver loads it for you.
2

Create the Bunny Stream module

urllib covers the two API calls. The upload signature is a SHA-256 of the library ID, API key, expiry, and video ID.
uploads/bunny_stream.py
3

Add the views

create_upload re-signs an unfinished video when the browser sends its ID, and creates a new one otherwise.
uploads/views.py
config/urls.py
4

Upload from the browser

Render the CSRF token into the page. The CSRF middleware rejects the POST without it.
uploads/templates/uploads/index.html
There’s no build step. tus-js-client comes from jsDelivr, and the token travels as X-CSRFToken.
uploads/static/uploads/video-uploader.js
tus-js-client sends the credentials as headers with every request. The video ID goes into localStorage against the file, which is how a reload finds its way back to the same upload.
abort() pauses. start() picks up from the last chunk we acknowledged.A 401 from the TUS endpoint means the signature doesn’t match the headers. Check that the library ID and API key belong to the same library. Re-signing keeps the upload’s original expiry, as the TUS FAQ explains.

Play it once it’s encoded

We start encoding when the last chunk arrives. Poll your status route until status reaches 4 (finished), 5 or 6 (failed), then embed embedUrl.
encodeProgress gives you a percentage to show in the meantime. A webhook tells your server when encoding finishes.

Before you deploy

Wrap both views in login_required and record who owns each video ID. The example’s settings.py is for development, and production needs DJANGO_SECRET_KEY, DEBUG = False, and ALLOWED_HOSTS.

Troubleshooting

The template needs the csrf-token meta tag, and the fetch needs the X-CSRFToken header.
Last modified on October 6, 2026