Skip to content

Stream

Source: src/Cloudflare/Stream/Stream.ts

A Cloudflare Stream binding for managing videos, captions, downloads and watermarks from Workers — a Worker-only binding with no backing cloud resource.

Stream is a single value that is at once the Binding.Service tag, the callable that produces a StreamBinding, and the type. Declare it on a Worker’s env (it flows through InferEnv → the runtime StreamBinding handle) or yield* it inside an Effect-native Worker to attach the binding and obtain the StreamClient.

In alchemy dev the binding is emulated locally: uploads land in a local video store and each video’s preview URL is served unmodified at {devUrl}/cdn-cgi/mf/stream/<id>/watch. The local store performs no transcoding (the hlsPlaybackUrl/dashPlaybackUrl point at a placeholder host), no signed URLs, and createDirectUpload is unsupported. Pipe the binding through Alchemy.remote() to proxy to the real Stream service instead.

import * as Effect from "effect/Effect";
Cloudflare.Worker(
"StreamWorker",
{ main: import.meta.url },
Effect.gen(function* () {
const stream = yield* Cloudflare.Stream.Stream("STREAM");
return {
fetch: Effect.gen(function* () {
const video = yield* stream.upload("https://example.com/video.mp4");
const details = yield* stream.video(video.id).details();
return yield* HttpServerResponse.json(details);
}),
};
}).pipe(Effect.provide(Cloudflare.Stream.StreamBinding)),
);
export const Worker = Cloudflare.Worker("Worker", {
main: "./src/worker.ts",
env: { STREAM: Cloudflare.Stream.Stream() },
});
export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
// { STREAM: StreamBinding }
// Default: videos land in the local video store under `alchemy dev`.
// Alchemy.remote() opts the binding into the real Stream service
// instead — in an Effect-native Worker:
const stream = yield* Cloudflare.Stream.Stream("STREAM").pipe(Alchemy.remote());
// or declared on an async Worker's env:
env: { STREAM: Cloudflare.Stream.Stream("STREAM").pipe(Alchemy.remote()) }