C3 (create-cloudflare-cli) is a command-line tool designed to help you set up and deploy Workers & Pages applications to Cloudflare as fast as possible.
To get started, open a terminal window and run:
npmcreatecloudflare@latest--r2-workeryarncreatecloudflarer2-workerpnpmcreatecloudflare@latestr2-workerFor setup, select the following options:
- ForWhat would you like to start with?, choose
Hello World example. - ForWhich template would you like to use?, choose
Worker only. - ForWhich language do you want to use?, choose
JavaScript. - ForDo you want to use git for version control?, choose
Yes. - ForDo you want to deploy your application?, choose
No(we will be making some changes before deploying).
Then, move into your newly created directory:
cdr2-workerCreate your bucket by running:
npxwranglerr2bucketcreate<YOUR_BUCKET_NAME>To check that your bucket was created, run:
npxwranglerr2bucketlistAfter running thelist command, you will see all bucket names, including the one you have just created.
You will need to bind your bucket to a Worker.
A binding is how your Worker interacts with external resources such asKV Namespaces,Durable Objects, orR2 Buckets. A binding is a runtime variable that the Workers runtime provides to your code. You can declare a variable name in your Wrangler file that will be bound to these resources at runtime, and interact with them through this variable. Every binding's variable name and behavior is determined by you when deploying the Worker. Refer to theEnvironment Variables documentation for more information.
A binding is defined in the Wrangler file of your Worker project's directory.
To bind your R2 bucket to your Worker, add the following to your Wrangler file. Update thebinding property to a valid JavaScript variable identifier andbucket_name to the<YOUR_BUCKET_NAME> you used to create your bucket instep 2:
{"r2_buckets":[{"binding":"MY_BUCKET",// <~ valid JavaScript variable name"bucket_name":"<YOUR_BUCKET_NAME>"}]}[[r2_buckets]]binding="MY_BUCKET"bucket_name="<YOUR_BUCKET_NAME>"For more detailed information on configuring your Worker (for example, if you are usingjurisdictions), refer to theWrangler Configuration documentation.
Within your Worker code, your bucket is now available under theMY_BUCKET variable and you can begin interacting with it.
By defaultwrangler dev runs in local development mode. In this mode, all operations performed by your local worker will operate against local storage on your machine.
If you want the R2 operations that are performed during development to be performed against a real R2 bucket, you can set"remote" : true in the R2 binding configuration. Refer toremote bindings documentation for more information.
An R2 bucket is able to READ, LIST, WRITE, and DELETE objects. You can see an example of all operations below using the Module Worker syntax. Add the following snippet into your project'sindex.js file:
import{WorkerEntrypoint} from"cloudflare:workers";exportdefaultclassextendsWorkerEntrypoint<Env>{asyncfetch(request:Request){consturl=newURL(request.url);constkey=url.pathname.slice(1);switch (request.method){case"PUT":{awaitthis.env.R2.put(key,request.body,{onlyIf:request.headers,httpMetadata:request.headers,});returnnewResponse(`Put${key} successfully!`);}case"GET":{constobject=awaitthis.env.R2.get(key,{onlyIf:request.headers,range:request.headers,});if (object===null){returnnewResponse("Object Not Found",{ status:404});}constheaders=newHeaders();object.writeHttpMetadata(headers);headers.set("etag",object.httpEtag);// When no body is present, preconditions have failedreturnnewResponse("body"inobject?object.body:undefined,{status:"body"inobject?200:412,headers,});}case"DELETE":{awaitthis.env.R2.delete(key);returnnewResponse("Deleted!");}default:returnnewResponse("Method Not Allowed",{status:405,headers:{Allow:"PUT, GET, DELETE",},});}}};exportdefault{asyncfetch(request,env){consturl=newURL(request.url);constkey=url.pathname.slice(1);switch (request.method){case"PUT":{awaitthis.env.R2.put(key,request.body,{onlyIf:request.headers,httpMetadata:request.headers,});returnnewResponse(`Put${key} successfully!`);}case"GET":{constobject=awaitthis.env.R2.get(key,{onlyIf:request.headers,range:request.headers,});if (object===null){returnnewResponse("Object Not Found",{ status:404});}constheaders=newHeaders();object.writeHttpMetadata(headers);headers.set("etag",object.httpEtag);// When no body is present, preconditions have failedreturnnewResponse("body"inobject?object.body:undefined,{status:"body"inobject?200:412,headers,});}case"DELETE":{awaitthis.env.R2.delete(key);returnnewResponse("Deleted!");}default:returnnewResponse("Method Not Allowed",{status:405,headers:{Allow:"PUT, GET, DELETE",},});}}}from workersimport WorkerEntrypoint, Responsefrom urllib.parseimport urlparseclassDefault(WorkerEntrypoint):asyncdeffetch(self,request):url=urlparse(request.url)key= url.path[1:]if request.method=="PUT":awaitself.env.R2.put(key,request.body,onlyIf=request.headers,httpMetadata=request.headers,)returnResponse(f"Put{key} successfully!")elif request.method=="GET":obj=awaitself.env.R2.get(key,onlyIf=request.headers,range=request.headers,)if objisNone:returnResponse("Object Not Found",status=404)# When no body is present, preconditions have failedbody= obj.bodyifhasattr(obj,"body")elseNonestatus=200ifhasattr(obj,"body")else412headers={"etag": obj.httpEtag}returnResponse(body,status=status,headers=headers)elif request.method=="DELETE":awaitself.env.R2.delete(key)returnResponse("Deleted!")else:returnResponse("Method Not Allowed",status=405,headers={"Allow":"PUT, GET, DELETE"},)The body of aRequest ↗ can only be accessed once. If you previously usedrequest.formData() in the same request, you may encounter a TypeError when attempting to accessrequest.body.
To avoid errors, create a clone of the Request object withrequest.clone() for each subsequent attempt to access a Request's body.Keep in mind that Workers have amemory limit of 128 MB per Worker and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider usingStreams.
With the above code added to your Worker, every incoming request has the ability to interact with your bucket. This means your bucket is publicly exposed and its contents can be accessed and modified by undesired actors.
You must now define authorization logic to determine who can perform what actions to your bucket. This logic lives within your Worker's code, as it is your application's job to determine user privileges. The following is a short list of resources related to access and authorization practices:
- Basic Authentication: Shows how to restrict access using the HTTP Basic schema.
- Using Custom Headers: Allow or deny a request based on a known pre-shared key in a header.
Continuing with your newly created bucket and Worker, you will need to protect all bucket operations.
ForPUT andDELETE requests, you will make use of a newAUTH_KEY_SECRET environment variable, which you will define later as a Wrangler secret.
ForGET requests, you will ensure that only a specific file can be requested. All of this custom logic occurs inside of anauthorizeRequest function, with thehasValidHeader function handling the custom header logic. If all validation passes, then the operation is allowed.
constALLOW_LIST= ["cat-pic.jpg"];// Check requests for a pre-shared secretconsthasValidHeader=(request,env)=>{returnrequest.headers.get("X-Custom-Auth-Key")===env.AUTH_KEY_SECRET;};functionauthorizeRequest(request,env,key){switch (request.method){case"PUT":case"DELETE":returnhasValidHeader(request,env);case"GET":returnALLOW_LIST.includes(key);default:returnfalse;}}exportdefault{asyncfetch(request,env,ctx){consturl=newURL(request.url);constkey=url.pathname.slice(1);if (!authorizeRequest(request,env,key)){returnnewResponse("Forbidden",{ status:403});}// ...},};from workersimport WorkerEntrypoint, Responsefrom urllib.parseimport urlparseALLOW_LIST=["cat-pic.jpg"]# Check requests for a pre-shared secretdefhas_valid_header(request,env):return request.headers.get("X-Custom-Auth-Key")== env.AUTH_KEY_SECRETdefauthorize_request(request,env,key):if request.methodin["PUT","DELETE"]:returnhas_valid_header(request, env)elif request.method=="GET":return keyin ALLOW_LISTelse:returnFalseclassDefault(WorkerEntrypoint):asyncdeffetch(self,request):url=urlparse(request.url)key= url.path[1:]ifnotauthorize_request(request,self.env, key):returnResponse("Forbidden",status=403)# ...For this to work, you need to create a secret via Wrangler:
npxwranglersecretputAUTH_KEY_SECRETThis command will prompt you to enter a secret in your terminal:
npxwranglersecretputAUTH_KEY_SECRETEnterthesecrettextyou'd like assigned to the variable AUTH_KEY_SECRET on the script named <YOUR_WORKER_NAME>:*********🌀 Creating the secret for script name <YOUR_WORKER_NAME>✨ Success! Uploaded secret AUTH_KEY_SECRET.This secret is now available asAUTH_KEY_SECRET on theenv parameter in your Worker.
With your Worker and bucket set up, run thenpx wrangler deploycommand to deploy to Cloudflare's global network:
npxwranglerdeployYou can verify your authorization logic is working through the following commands, using your deployed Worker endpoint:
When uploading files to R2 viacurl, ensure you use--data-binary ↗ instead of--data or-d. Files will otherwise be truncated.
# Attempt to write an object without providing the "X-Custom-Auth-Key" headercurlhttps://your-worker.dev/cat-pic.jpg-XPUT--data-binary'test'#=> Forbidden# Expected because header was missing# Attempt to write an object with the wrong "X-Custom-Auth-Key" header valuecurlhttps://your-worker.dev/cat-pic.jpg-XPUT--header"X-Custom-Auth-Key: hotdog"--data-binary'test'#=> Forbidden# Expected because header value did not match the AUTH_KEY_SECRET value# Attempt to write an object with the correct "X-Custom-Auth-Key" header value# Note: Assume that "*********" is the value of your AUTH_KEY_SECRET Wrangler secretcurlhttps://your-worker.dev/cat-pic.jpg-XPUT--header"X-Custom-Auth-Key: *********"--data-binary'test'#=> Put cat-pic.jpg successfully!# Attempt to read object called "foo"curlhttps://your-worker.dev/foo#=> Forbidden# Expected because "foo" is not in the ALLOW_LIST# Attempt to read an object called "cat-pic.jpg"curlhttps://your-worker.dev/cat-pic.jpg#=> test# Note: This is the value that was successfully PUT aboveBy completing this guide, you have successfully installed Wrangler and deployed your R2 bucket to Cloudflare.