Laravel example on GitHub
Laravel 13, Vite, and tus-js-client. No database.
Quickstart
1
Add your library credentials
Copy both from your library’s API page. The API key can delete videos, and belongs in
.env only..env
config/services.php
2
Create the Bunny Stream service
The upload signature is a SHA-256 of the library ID, API key, expiry, and video ID.
app/Services/BunnyStream.php
3
Add the API routes
routes/api.php
php artisan install:api first if the app has no routes/api.php. API routes skip the CSRF check, which keeps the browser side simple.Pass a videoId from an unfinished upload and the controller re-signs that video. Anything else gets a new one.app/Http/Controllers/UploadController.php
app/Http/Controllers/VideoController.php
4
Upload from the browser
resources/js/video-uploader.js
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 untilstatus 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
Put the routes behindauth:sanctum or your own middleware, and store which user owns each video ID. As written, anyone can fill your library.
Troubleshooting
/api/uploads returns 404
/api/uploads returns 404
Laravel isn’t loading
routes/api.php. Run php artisan install:api. If you changed .env after caching the config, run php artisan config:clear too.