Bind to Workers API
A binding connects your Worker to external resources on the Developer Platform, like Media Transformations, R2 buckets, or KV namespaces.
You can bind the Media Transformations API to your Worker to transform, resize, and extract content from videos without requiring them to be accessible through a URL.
For example, when you use Media Transformations within Workers, you can:
- Transform a video stored in a private R2 bucket or other protected source
- Optimize videos and store the output directly back in R2 without serving it to the browser
- Extract still frames and spritesheets for videos and use them for classification or description with Workers AI
- Extract audio tracks from video files for dynamic transcription using Workers AI
The Media binding is enabled on a per-Worker basis.
Bindings can be configured in the Cloudflare dashboard for your Worker or in the Wrangler configuration file in your project directory.
To bind Media Transformations to your Worker, add the following to the end of your Wrangler configuration file:
{ "$schema": "./node_modules/wrangler/config-schema.json", "media": { "binding": "MEDIA" }}[media]binding = "MEDIA" # available in your Worker on env.MEDIAWithin your Worker code, you can interact with this binding by using env.MEDIA.input() to build an object that can manipulate the video (passed as a ReadableStream).
The Media Transformations binding is similar to the Images binding, except the method chain order is fixed and the result of an input() cannot be reused across multiple transformations.
The starting point for the Media binding, which accepts raw content.
- Accepts a
ReadableStream<Uint8Array>containing the video bytes.
Defines how the video input should be transformed by resizing or cropping. This method is optional — if you do not need to resize or crop, you can call .output() directly on the result of .input().
- Accepts the following parameters (all optional):
width: Target width in pixels (10-2000).height: Target height in pixels (10-2000).fit: How to resize the video to fit the specified dimensions.contain: Scales the video to fit entirely within the output dimensions while respecting aspect ratio.cover: Scales the video to entirely cover the output dimensions with a center-weighted crop.scale-down: Same ascontain, but only scales down. Does not upscale.
- Refer to Transform videos options for more details.
Defines what to extract from the video and how the output will be formatted. Refer to source video requirements and limitations for input and output constraints.
- Accepts the following parameters:
mode: The type of output to generate.video: Outputs an H.264/AAC optimized MP4 file.frame: Outputs a still image (JPEG or PNG).spritesheet: Outputs a JPEG containing multiple frames.audio: Outputs an AAC encoded M4A file.
time: Start timestamp for extraction (for example,"2s","1m"). Default:"0s".duration: Duration of the output forvideo,audio, orspritesheetmodes (for example,"5s").imageCount: Number of frames to include in a spritesheet.format: Output format forframemode (jpg,png) oraudiomode (m4a).audio: Boolean to include or exclude audio invideomode. Default:true.
Finally, after configuring the output, three methods are available to receive results. These methods return Promises and must be awaited:
.response(): Returns aPromise<Response>— the transformed media as an HTTP Response object, ready to return to the client or store in cache..media(): Returns aPromise<ReadableStream<Uint8Array>>— the transformed media as a byte stream..contentType(): Returns aPromise<string>— the MIME type of the output (for example,video/mp4,image/jpeg,audio/mp4).
Resize a video and extract a five-second clip:
export default { async fetch(request, env) { const video = await env.R2_BUCKET.get("input.mp4");
const result = env.MEDIA.input(video.body) .transform({ width: 480, height: 270 }) .output({ mode: "video", time: "0s", duration: "5s" });
return await result.response(); },};Extract a single frame as a JPEG thumbnail:
export default { async fetch(request, env) { const video = await env.R2_BUCKET.get("input.mp4");
const result = env.MEDIA.input(video.body) .transform({ width: 640, height: 360 }) .output({ mode: "frame", time: "2s", format: "jpg" });
return await result.response(); },};Extract the audio track from a video as an M4A file. This example demonstrates skipping .transform() since no resizing is needed:
export default { async fetch(request, env) { const video = await env.R2_BUCKET.get("input.mp4");
const result = env.MEDIA.input(video.body).output({ mode: "audio", time: "0s", duration: "30s", });
return await result.response(); },};Transform a video and store the result directly in R2:
export default { async fetch(request, env) { const video = await env.R2_BUCKET.get("input.mp4");
const result = env.MEDIA.input(video.body) .transform({ width: 480, height: 270, fit: "contain" }) .output({ mode: "video", time: "0s", duration: "10s", audio: false });
// Store the transformed video directly in R2 await env.R2_BUCKET.put("output-480p.mp4", await result.media(), { httpMetadata: { contentType: await result.contentType() }, });
return new Response("Video transformed and stored", { status: 200 }); },};Errors can be thrown at different points in the method chain:
.input()can throw errors related to account limits (free tier or subscription) or service disruptions..output()can throw errors related to the transformation operation itself, such as invalid parameters or unsupported input formats.
Errors throw a MediaError, which extends the standard Error interface with additional information:
code: A numeric error code.message: A description of the error.stack: Optional stack trace.
Use a try-catch block to handle errors:
export default { async fetch(request, env) { const video = await env.R2_BUCKET.get("input.mp4");
try { const result = env.MEDIA.input(video.body) .transform({ width: 480, height: 270 }) .output({ mode: "video", time: "0s", duration: "5s" });
return await result.response(); } catch (e) { if (e instanceof Error && "code" in e) { // Handle MediaError return new Response(`Transformation failed: ${e.message}`, { status: 500, }); } throw e; } },};Unlike transformations via URL, responses from the Media binding are not automatically cached. Workers lets you interact directly with the Cache API to customize cache behavior. You can implement logic in your script to store transformations in Cloudflare's cache or R2 storage.
Refer to Stream's Pricing information for costs. Transformations executed via the binding are billed on a per-operation basis, not based on request uniqueness. For best cost and performance optimization, cache or store outputs for reuse.
The Media Transformations API is available in remote mode for local development through Wrangler, the command-line interface for Workers. Transformations operations will be performed using a remote resource and are subject to usage charges beyond the included free tier.
To enable usage in local development, add remote to the binding configuration:
{ "$schema": "./node_modules/wrangler/config-schema.json", "media": { "binding": "MEDIA", "remote": true }}[media]binding = "MEDIA" # available in your Worker on env.MEDIAremote = trueThen run:
npx wrangler dev