Skip to content
Cloudflare Docs

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

Setup

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"
}
}

Within 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).

Methods

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.

.input()

The starting point for the Media binding, which accepts raw content.

  • Accepts a ReadableStream<Uint8Array> containing the video bytes.

.transform() (optional)

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 as contain, but only scales down. Does not upscale.
  • Refer to Transform videos options for more details.

.output()

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 for video, audio, or spritesheet modes (for example, "5s").
    • imageCount: Number of frames to include in a spritesheet.
    • format: Output format for frame mode (jpg, png) or audio mode (m4a).
    • audio: Boolean to include or exclude audio in video mode. Default: true.

Result methods

Finally, after configuring the output, three methods are available to receive results. These methods return Promises and must be awaited:

  • .response(): Returns a Promise<Response> — the transformed media as an HTTP Response object, ready to return to the client or store in cache.
  • .media(): Returns a Promise<ReadableStream<Uint8Array>> — the transformed media as a byte stream.
  • .contentType(): Returns a Promise<string> — the MIME type of the output (for example, video/mp4, image/jpeg, audio/mp4).

Examples

Generate an optimized video clip

Resize a video and extract a five-second clip:

TypeScript
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 still frame

Extract a single frame as a JPEG thumbnail:

TypeScript
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 audio

Extract the audio track from a video as an M4A file. This example demonstrates skipping .transform() since no resizing is needed:

TypeScript
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();
},
};

Store transformed output in R2

Transform a video and store the result directly in R2:

TypeScript
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 });
},
};

Error handling

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:

TypeScript
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;
}
},
};

Caching

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.

Billing

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.

Local development

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
}
}

Then run:

Terminal window
npx wrangler dev