Direct creator uploads
Direct creator uploads let your end users upload videos directly to Cloudflare Stream without exposing your API token to clients. You can implement direct creator uploads using either a basic POST request or the tus protocol. Use this chart to decide which method to use:
flowchart LR
accTitle: Direct creator uploads decision flow
accDescr: Decision flow for choosing between basic POST uploads and tus protocol based on file size and connection reliability
A{Is the video over 200 MB?}
A -->|Yes| B[You must use the tus protocol]:::link
A -->|No| C{Does the end user have a reliable connection?}
C -->|Yes| D[Basic POST is recommended]:::link
C -->|No| E[The tus protocol is optional, but recommended]:::link
classDef link text-decoration:underline,color:#F38020
click B "#direct-creator-uploads-with-tus-protocol" "Learn about tus protocol"
click D "#basic-post-request" "See basic POST instructions"
click E "#direct-creator-uploads-with-tus-protocol" "Learn about tus protocol"
Whether you use basic POST or tus protocol, you must specify a maximum duration to reserve for the user's upload to ensure it can be accommodated within your available storage. This duration will be deducted from your account's available storage until the user's upload is received. Once the upload is processed, its actual duration will be counted and the remaining reservation will be relased. If the video errors or is not received before the link expires, the entire reservation will be released.
If your end user's video is under 200 MB and their connection is reliable, we recommend using this method. If your end user's connection is unreliable, we recommend using the tus protocol instead.
To enable direct creator uploads with a POST request:
- Generate a unique, one-time upload URL using the Direct upload API.
curl https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/direct_upload \--header 'Authorization: Bearer <API_TOKEN>' \ --data '{ "maxDurationSeconds": 3600 }'{ "result": { "uploadURL": "https://upload.videodelivery.net/f65014bc6ff5419ea86e7972a047ba22", "uid": "f65014bc6ff5419ea86e7972a047ba22" }, "success": true, "errors": [], "messages": []}- With the
uploadURLfrom the previous step, users can upload video files that are limited to 200 MB in size. Refer to the example request below.
curl --request POST \ --form file=@/Users/mickie/Downloads/example_video.mp4 \ https://upload.videodelivery.net/f65014bc6ff5419ea86e7972a047ba22A successful upload will receive a 200 HTTP status code response. If the upload does not meet
the upload constraints defined at time of creation or is larger than 200 MB in size, you will receive a 4xx HTTP status code response.
If your end user's video is over 200 MB, you must use the tus protocol. Even if the file is under 200 MB, if the end user's connection is potentially unreliable, Cloudflare recommends using the tus protocol because it is resumable. For detailed information about tus protocol requirements, additional client examples, and upload options, refer to Resumable and large files (tus).
To enable direct creator uploads with the tus protocol:
- Create your own API endpoint that returns an upload URL.
The example below shows how to build a Worker to get a URL your end users can use to upload their video. The one-time upload URL is returned in the Location header of the response, not in the response body.
export async function onRequest(context) { const { request, env } = context; const { CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN } = env; const endpoint = `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/stream?direct_user=true`;
const response = await fetch(endpoint, { method: "POST", headers: { Authorization: `bearer ${CLOUDFLARE_API_TOKEN}`, "Tus-Resumable": "1.0.0", "Upload-Length": request.headers.get("Upload-Length"), "Upload-Metadata": request.headers.get("Upload-Metadata"), }, });
const destination = response.headers.get("Location");
return new Response(null, { headers: { "Access-Control-Expose-Headers": "Location", "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Origin": "*", Location: destination, }, });}- Use this API endpoint directly in your tus client. A common mistake is to extract the upload URL from your new API endpoint, and use this directly. See below for a complete example of how to use the API from Step 1 with the uppy tus client.
<html> <head> <link href="https://releases.transloadit.com/uppy/v3.0.1/uppy.min.css" rel="stylesheet" /> </head> <body> <div id="drag-drop-area" style="height: 300px"></div> <div class="for-ProgressBar"></div> <button class="upload-button" style="font-size: 30px; margin: 20px"> Upload </button> <div class="uploaded-files" style="margin-top: 50px"> <ol></ol> </div> <script type="module"> import { Uppy, Tus, DragDrop, ProgressBar, } from "https://releases.transloadit.com/uppy/v3.0.1/uppy.min.mjs";
const uppy = new Uppy({ debug: true, autoProceed: true });
const onUploadSuccess = (el) => (file, response) => { const li = document.createElement("li"); const a = document.createElement("a"); a.href = response.uploadURL; a.target = "_blank"; a.appendChild(document.createTextNode(file.name)); li.appendChild(a);
document.querySelector(el).appendChild(li); };
uppy .use(DragDrop, { target: "#drag-drop-area" }) .use(Tus, { endpoint: "/api/get-upload-url", chunkSize: 150 * 1024 * 1024, }) .use(ProgressBar, { target: ".for-ProgressBar", hideAfterFinish: false, }) .on("upload-success", onUploadSuccess(".uploaded-files ol"));
const uploadBtn = document.querySelector("button.upload-button"); uploadBtn.addEventListener("click", () => uppy.upload()); </script> </body></html>For more details on using tus and example client code, refer to Resumable and large files (tus).
You can apply the same constraints as Direct Creator Upload via basic upload when using tus. To do so, you must pass the expiry and maxDurationSeconds as part of the Upload-Metadata request header as part of the first request (made by the Worker in the example above.) The Upload-Metadata values are ignored from subsequent requests that do the actual file upload.
The Upload-Metadata header should contain key-value pairs. The keys are text and the values should be encoded in base64. Separate the key and values by a space, not an equal sign. To join multiple key-value pairs, include a comma with no additional spaces.
In the example below, the Upload-Metadata header is instructing Stream to only accept uploads with max video duration of 10 minutes, uploaded prior to the expiry timestamp, and to make this video private:
'Upload-Metadata: maxDurationSeconds NjAw,requiresignedurls,expiry MjAyNC0wMi0yN1QwNzoyMDo1MFo='
NjAw is the base64 encoded value for "600" (or 10 minutes).
MjAyNC0wMi0yN1QwNzoyMDo1MFo= is the base64 encoded value for "2024-02-27T07:20:50Z" (an RFC3339 format timestamp)
After the creation of a unique one-time upload URL, you may wish to retain the unique identifier (uid) returned in the response to track the progress of a user's upload.
You can do that two ways:
-
Search for a video with the UID to check the status.
-
Create a webhook subscription to receive notifications about the video status. These notifications include the video's UID.
Direct Creator Upload links count towards your storage limit even if your users have not yet uploaded video to this URL. If the link expires before it is used or the upload cannot be processed, the storage reservation will be released. Otherwise, once the upload is encoded, its true duration will be counted toward storage and the reservation will be released.
For a detailed breakdown of pricing and example scenarios, refer to Pricing.