Video apps without the plumbing

Cloudflare Stream takes care of the hard parts of video — encoding, storage, and playback — so you can focus on the application layer. Combined with Cloudflare Access, Pages, and Workers, you can put together a full video platform (think personal YouTube or Twitch) with authenticated uploads and admin controls, without running any video infrastructure yourself.

Listing and playing videos

The main page of the demo application fetches a list of all videos using Stream's search API and an empty query string. You could easily adapt the same call to return trending videos or a curated selection for individual users. For each result, the app filters out private videos and extracts the metadata it needs — thumbnail URL, video ID, and creation date.

Build your next video application on Cloudflare

Playback comes down to one decision: are your videos public or private? Public videos simplify playback, but if you want to require login before viewing or restrict access in any way, mark them as private and sign the delivery URLs. For low-traffic testing (under 10,000 requests per day), the /token endpoint is fine. At higher scale, sign your own tokens using JSON Web Tokens, as the demo does.

Handling uploads

Uploading works through direct creator uploads. The application issues a one-time, unique upload URL via the Stream API, using an API token with Stream:Edit permission and your account ID. The app hooks into every POST request from the frontend and returns that generated URL.

The admin page presents a drag-and-drop upload form. When an authenticated user submits the form, the app fetches the unique URL and POSTs the FormData directly to it — a pattern that works for any app accepting user-generated video content.

export const cfTeamsAccessAuthMiddleware = async ({request, data, env, next}) => {
    try {
        const userEmail = request.headers.get("cf-access-authenticated-user-email")

        if (!userEmail) {
            throw new Error("User not found, make sure application is behind Cloudflare Access")
        }
  
        // Pass user info to next handlers
        data.user = {
            email: userEmail
        }
  
        return next()
    } catch (e) {
        return new Response(e.toString(), {status: 401})
    }
}

export const onRequest = [
    cfTeamsAccessAuthMiddleware
]

Live video slots into the same architecture. With Stream Live, authenticated users can start broadcasts that other logged-in users (or the public) watch in real time. Streams are automatically archived to the account, so they appear in the main video list the moment the broadcast ends.

Locking down the admin section

Authenticated routes sit behind a middleware function that inspects the request headers to confirm the user's email is valid. Cloudflare Access handles the actual log-in flow: the main page stays public, while the admin page is gated behind an email prompt. Entering an allowed Cloudflare email triggers an access code; anyone else is stopped at the door. The same Access setup could protect the whole application if you were building an internal training library or a private beta.

export const cfTeamsAccessAuthMiddleware = async ({request, data, env, next}) => {
    try {
        const userEmail = request.headers.get("cf-access-authenticated-user-email")

        if (!userEmail) {
            throw new Error("User not found, make sure application is behind Cloudflare Access")
        }
  
        // Pass user info to next handlers
        data.user = {
            email: userEmail
        }
  
        return next()
    } catch (e) {
        return new Response(e.toString(), {status: 401})
    }
}

export const onRequest = [
    cfTeamsAccessAuthMiddleware
]

One repo, full stack

Cloudflare Pages deploys the static frontend and now integrates directly with the Workers runtime, so the entire application ships from a single, readable repository. Feed requests and Stream API calls run in Workers, uploads and playback are managed by Stream, authentication is handled by Access, and Pages serves it all without a dedicated video server in sight.