modularavel / cloudflare-stream-video
A Laravel package that provides an idiomatic, strongly typed interface to the Cloudflare Stream Video API. Upload, manage, and stream videos with signed URLs, watermark overlays, and webhook-driven processing.
Package info
github.com/modularavel/cloudflare-stream-video
pkg:composer/modularavel/cloudflare-stream-video
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/support: ^12.0||^13.0
Requires (Dev)
- larastan/larastan: ^3.9
- laravel/pao: ^1.0
- laravel/pint: ^1.29
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.6
- pestphp/pest-plugin-laravel: ^4.1
- pestphp/pest-plugin-type-coverage: ^4.0
- phpstan/extension-installer: ^1.4
README
modularavel/cloudflare-stream-video
A Laravel package that provides an idiomatic, strongly typed interface to the Cloudflare Stream Video API. Upload, manage, and stream videos with signed URLs, watermark overlays, and webhook-driven processing — all from within your Laravel application.
Features
- Video Management — Upload (TUS), copy from URL, direct upload, list, get, update, and delete videos
- Signed Playback URLs — HMAC-SHA256 signed tokens for restricted video access
- Watermark Management — Create, list, update, delete watermarks; apply/remove watermarks on videos
- Webhook Integration — Receive Cloudflare Stream processing notifications, process them asynchronously via queued jobs, and broadcast completion events
- Blade Player Component — Responsive iframe embed component with signed URL support
- Artisan Commands — CLI commands for video import, sync, signed URL generation, and watermark management
- Config Publishing — All configuration, views, translations, and assets are publishable
Installation
You can install the package via Composer:
composer require modularavel/cloudflare-stream-video
Publishing Resources
You may publish all of the package's resources at once:
php artisan vendor:publish --tag="cloudflare-stream-video"
Or, publish each resource individually:
Configuration
php artisan vendor:publish --tag="cloudflare-stream-video-config"
After publishing, edit config/cloudflare-stream-video.php to configure your Cloudflare account.
Migrations
php artisan vendor:publish --tag="cloudflare-stream-video-migrations"
php artisan migrate
This will create:
cloudflare_stream_videos— Local cache of Cloudflare Stream videoscloudflare_stream_webhooks— Audit log for webhook deliveries
Views
php artisan vendor:publish --tag="cloudflare-stream-video-views"
Translations
php artisan vendor:publish --tag="cloudflare-stream-video-lang"
Public Assets
php artisan vendor:publish --tag="cloudflare-stream-video-assets"
Configuration
After publishing the config file, edit config/cloudflare-stream-video.php to match your Cloudflare account credentials and preferences. Environment variables (with CLOUDFLARE_STREAM_ prefix) take precedence over config file values.
🔑 Required Settings
| 🔧 Key | 🌿 Env Variable | 📝 Description |
|---|---|---|
account_id |
CLOUDFLARE_STREAM_ACCOUNT_ID |
Your Cloudflare account identifier |
api_token |
CLOUDFLARE_STREAM_API_TOKEN |
API token with "Stream Edit" or "Stream Read" permissions |
⚙️ Optional Settings
| 🔧 Key | 🌿 Env Variable | 💡 Default | 📝 Description |
|---|---|---|---|
timeout |
CLOUDFLARE_STREAM_TIMEOUT |
120 |
HTTP request timeout in seconds |
defaults |
— | [] |
Default key-value pairs merged into every API request body |
require_signed_urls |
CLOUDFLARE_STREAM_REQUIRE_SIGNED_URLS |
false |
Whether videos require signed URLs for playback |
signing_key |
CLOUDFLARE_STREAM_SIGNING_KEY |
'' |
HMAC-SHA256 signing key for signed URL generation |
customer_domain |
CLOUDFLARE_STREAM_CUSTOMER_DOMAIN |
'' |
Custom Cloudflare Stream customer domain for signed URLs |
watermark.default_position |
— | 'bottom-right' |
Default watermark position on the video frame |
watermark.default_opacity |
— | 1.0 |
Default watermark opacity (0.0–1.0) |
watermark.default_width |
— | 0 |
Default watermark width in pixels |
watermark.default_height |
— | 0 |
Default watermark height in pixels |
webhook.secret |
CLOUDFLARE_STREAM_WEBHOOK_SECRET |
'' |
Secret used to verify incoming webhook signatures |
webhook.route |
CLOUDFLARE_STREAM_WEBHOOK_ROUTE |
'cloudflare-stream-video/webhook' |
Route path for receiving webhook notifications |
🔐 Environment Example
CLOUDFLARE_STREAM_ACCOUNT_ID=your-account-id CLOUDFLARE_STREAM_API_TOKEN=your-api-token CLOUDFLARE_STREAM_SIGNING_KEY=your-64-character-signing-key CLOUDFLARE_STREAM_WEBHOOK_SECRET=your-webhook-secret
Usage
Basic Video Operations
The CloudflareStreamVideo facade (or inject CloudflareStreamVideoInterface) provides the full API surface.
Upload a Video (TUS)
use Modularavel\CloudflareStreamVideo\Facades\CloudflareStreamVideo; // Upload a local video file using the TUS resumable protocol. $video = CloudflareStreamVideo::upload('/path/to/video.mp4', [ 'meta' => ['name' => 'My Awesome Video', 'source' => 'laravel'], 'maxDurationSeconds' => 3600, ]); echo $video->uid; // e.g. "cab807e0c477d01baq20f66c3d1dfc26cf" echo $video->status; // e.g. "ready"
Copy a Video from a URL
// Fetch and process a video from a publicly accessible URL. $video = CloudflareStreamVideo::copy('https://example.com/video.mp4', [ 'meta' => ['name' => 'Imported Video'], ]);
Create a Direct Upload
// Generate a one-time upload URL for client-side use. $upload = CloudflareStreamVideo::createDirectUpload([ 'maxDurationSeconds' => 300, 'expiry' => now()->addHours(1)->toIso8601String(), ]); // Pass $upload->uploadUrl to your frontend client. // The upload URL is valid until expiry or until the video is uploaded.
List Videos
// Retrieve all videos with optional filtering. $videos = CloudflareStreamVideo::listVideos([ 'status' => 'ready', 'limit' => 100, ]); foreach ($videos as $video) { echo $video->uid . ' - ' . $video->status; }
Get a Single Video
$video = CloudflareStreamVideo::getVideo('cab807e0c477d01baq20f66c3d1dfc26cf'); echo $video->uid; echo $video->status; echo $video->duration; echo $video->getHlsUrl(); // e.g. "https://customer.cloudflarestream.com/.../manifest/video.m3u8" echo $video->getDashUrl(); // e.g. "https://customer.cloudflarestream.com/.../manifest/video.mpd" echo $video->getEmbedUrl(); // e.g. "https://embed.videodelivery.net/embed/iframe/..."
Update a Video
$video = CloudflareStreamVideo::updateVideo('cab807e0c477d01baq20f66c3d1dfc26cf', [ 'meta' => ['name' => 'Updated Title', 'tags' => 'tutorial,php'], 'requireSignedURLs' => true, ]);
Delete a Video
$deleted = CloudflareStreamVideo::deleteVideo('cab807e0c477d01baq20f66c3d1dfc26cf');
Signed URLs
When a signing key is configured, you can generate signed playback URLs and tokens.
use Modularavel\CloudflareStreamVideo\Facades\CloudflareStreamVideo; // Generate a full signed embed URL (valid for 2 hours by default). $signedUrl = CloudflareStreamVideo::signedEmbedUrl( 'cab807e0c477d01baq20f66c3d1dfc26cf', now()->addHours(2)->toDateTimeString(), ); // Generate only the signed token (for use in custom embed URLs or HLS/DASH manifests). $token = CloudflareStreamVideo::signedToken( 'cab807e0c477d01baq20f66c3d1dfc26cf', now()->addHours(2)->toDateTimeString(), );
Watermarking
Watermarks are overlay images (typically PNG or SVG with transparency) that can be applied to videos during playback. They are managed per-account and associated with videos via their UID.
Create a Watermark
use Modularavel\CloudflareStreamVideo\Facades\CloudflareStreamVideo; $watermark = CloudflareStreamVideo::createWatermark( name: 'Brand Logo', url: 'https://example.com/watermark.png', position: 'bottom-right', opacity: 0.8, width: 200, height: 50, ); echo $watermark->uid; // e.g. "wm-brand-123"
List All Watermarks
$watermarks = CloudflareStreamVideo::listWatermarks(); foreach ($watermarks as $wm) { echo $wm->uid . ' - ' . $wm->name; }
Get a Single Watermark
$watermark = CloudflareStreamVideo::getWatermark('wm-brand-123');
Update a Watermark
$watermark = CloudflareStreamVideo::updateWatermark('wm-brand-123', [ 'opacity' => 0.5, 'position' => 'top-left', ]);
Delete a Watermark
$deleted = CloudflareStreamVideo::deleteWatermark('wm-brand-123');
Apply a Watermark to a Video
$video = CloudflareStreamVideo::applyWatermark( 'cab807e0c477d01baq20f66c3d1dfc26cf', 'wm-brand-123', );
Remove a Watermark from a Video
$video = CloudflareStreamVideo::removeWatermark('cab807e0c477d01baq20f66c3d1dfc26cf');
Webhook Integration
Cloudflare Stream can send webhook notifications when video processing events occur (e.g. video.ready, video.error). The package provides a webhook endpoint, a background job for async processing, and an event for broadcasting completion.
Setup
- Configure the webhook secret in your
.env:
CLOUDFLARE_STREAM_WEBHOOK_SECRET=your-webhook-secret
- Configure the webhook route (optional, defaults to
cloudflare-stream-video/webhook):
CLOUDFLARE_STREAM_WEBHOOK_ROUTE=cloudflare-stream-video/webhook
- Register the webhook URL in the Cloudflare Stream dashboard under Notifications -> Webhooks. The URL will be:
https://your-app.com/cloudflare-stream-video/webhook
Handling Webhook Events
The package dispatches a StreamVideoProcessed event whenever a webhook is received. You can listen for this event in your application:
// In a service provider or event subscriber: use Modularavel\CloudflareStreamVideo\Events\StreamVideoProcessed; Event::listen(StreamVideoProcessed::class, function (StreamVideoProcessed $event) { // $event->eventType — e.g. "video.ready", "video.error" // $event->videoUid — the Cloudflare video UID // $event->payload — the full raw webhook payload if ($event->eventType === 'video.ready') { // Video encoding completed — update your database, notify users, etc. $video = Video::where('cloudflare_uid', $event->videoUid)->first(); if ($video) { $video->update(['status' => 'ready']); } } if ($event->eventType === 'video.error') { // Video encoding failed — log the error, notify admins, etc. Log::error('Cloudflare Stream encoding failed for video: ' . $event->videoUid, [ 'payload' => $event->payload, ]); } });
Webhook Payload Structure
Cloudflare Stream sends webhook payloads with the following structure:
{
"event_type": "video.ready",
"result": {
"uid": "cab807e0c477d01baq20f66c3d1dfc26cf",
"status": {
"state": "ready",
"pctComplete": "100"
},
"meta": { "name": "My Video" }
}
}
Webhook Signature Verification
The webhook controller verifies incoming requests using HMAC-SHA256 signatures. The signature is sent in the X-Cloudflare-Signature header as a hex-encoded HMAC digest of the raw request body.
If no CLOUDFLARE_STREAM_WEBHOOK_SECRET is configured, signature verification is skipped (not recommended for production).
Webhook Database Table
The package includes a migration that creates the cloudflare_stream_webhooks table for auditing webhook deliveries:
php artisan vendor:publish --tag="cloudflare-stream-video-migrations"
php artisan migrate
The table stores:
event_type— the Cloudflare Stream event typevideo_uid— the video identifier from the payloadpayload— the raw JSON payload (nullable)processed— boolean flag indicating whether the background job has processed the webhook
Blade Player Component
The package includes a responsive Blade component for embedding Cloudflare Stream videos.
Basic Usage
{{-- Simple unsigned embed --}} <x-cloudflare-stream-player :videoUid="$video->uid" />
Signed Mode
{{-- Signed embed with expiry --}} <x-cloudflare-stream-player :videoUid="$video->uid" :signed="true" :expiresAt="now()->addHours(2)" />
Custom Dimensions and Styling
<x-cloudflare-stream-player :videoUid="$video->uid" width="100%" height="400px" class="my-custom-player" />
The component renders a responsive iframe wrapper with the Cloudflare Stream embed player. When signed is true, the component generates a signed embed URL using the configured signing key.
Artisan Commands
Placeholder Command
php artisan cloudflare-stream-video:placeholder
A basic command that confirms the package is installed and working.
Import Video from URL
php artisan cloudflare-stream-video:import https://example.com/video.mp4 # With metadata php artisan cloudflare-stream-video:import https://example.com/video.mp4 \ --meta="name=My Video" \ --meta="source=laravel"
Sync Videos
php artisan cloudflare-stream-video:sync
Syncs videos from Cloudflare Stream with your local database.
Generate Signed URL
php artisan cloudflare-stream-video:sign video-uid-123 # With custom expiry php artisan cloudflare-stream-video:sign video-uid-123 --expires=2030-01-01T00:00:00Z # With details output php artisan cloudflare-stream-video:sign video-uid-123 --details
Watermark Management
# Create a watermark php artisan cloudflare-stream-video:watermark create \ --name="Brand Logo" \ --url="https://example.com/watermark.png" \ --position="bottom-right" \ --opacity=0.8 # List all watermarks php artisan cloudflare-stream-video:watermark list # Get a watermark by UID php artisan cloudflare-stream-video:watermark get --watermark-uid=wm-brand-123 # Update a watermark php artisan cloudflare-stream-video:watermark update \ --watermark-uid=wm-brand-123 \ --opacity=0.5 # Delete a watermark php artisan cloudflare-stream-video:watermark delete --watermark-uid=wm-brand-123 # Apply a watermark to a video php artisan cloudflare-stream-video:watermark apply \ --video-uid=cab807e0c477d01baq20f66c3d1dfc26cf \ --watermark-uid=wm-brand-123 # Remove a watermark from a video php artisan cloudflare-stream-video:watermark remove \ --video-uid=cab807e0c477d01baq20f66c3d1dfc26cf
🧪 Testes
O pacote conta com 86 testes e 150 assertions usando Pest PHP:
# Rodar o conjunto completo de testes composer test # Rodar apenas testes unitários composer test:unit # Rodar verificação de estilo (Pint) composer lint:check # Rodar análise estática (PHPStan) composer analyse
📚 Contribute
We welcome your contributions! Please check out our contributing guide for details.
🔒 Security Policy
Report security vulnerabilities responsibly through our security policy.
👨💻 Author
| | --- | |

Casimiro Rocha
📧 contato@crsistemas.dev.br
📄 License
This project is licensed under the MIT License — see the LICENSE.md file for details.
✨ Support
If you've found this package useful, please ⭐ the repository to show your support!
Made with ❤️ by Modularavel
Version 0.1.0

