ūüĒ• Join our¬†free ¬†SvelteKit Workshop on June 28 -¬†only 9 seats remaining ! ūüí®¬†Save Your Spot!

How to upload files to Cloudflare R2 in SvelteKit

Justin's profile pic

Justin Ahinon

Table of Contents

    How to upload files to Cloudflare R2 in SvelteKit

    SvelteKit version: 1.5.0

    @aws-sdk/client-s3 version: 3.332.0

    @aws-sdk/s3-request-presigner version: 3.332.0

    GitHub repository: https://github.com/JustinyAhin/okupter-repos/tree/main/apps/cloudflare-r2-sveltekit

    Demo website: https://cloudflare-r2-sveltekit.vercel.app/

    I've been willing to try out Cloudflare R2 for a while now. The product looks amazing, the pricing is appealing, and on top of that, it's being built by a company I trust and use for many of my projects.

    In the last couple of weeks, I finally had the occasion to use it on two different projects.

    This blog post is a quick guide on how to upload files to Cloudflare R2 in SvelteKit, based on my experience.

    What is Cloudflare R2?

    R2 is a global object storage service. You can see it as a competitor for popular services in the same category, such as Amazon S3, Google Cloud Storage, or DigitalOcean Spaces.

    Here are a few of the features that make R2 stand out:

    • Globally distributed storage : R2 is built on top of Cloudflare's global network, which means that your files are stored in multiple locations around the world. This allows for faster uploads and downloads and also makes your files more resilient to failures.

    • S3 compatible API : R2 is compatible with the S3 API, which means that you can use any S3 client to interact with it. This is great because it means that you can use tools and libraries you're already familiar with.

    • Zero egress fees : R2 doesn't charge for data transfer, which means that you can upload and download as much data as you want without worrying about the cost. Cloudflare pricing is based on the volume of data stored and the number of requests made to the API.

    • R2 is very easy to use: If you've used some S3 in the past, you probably know that it can be a bit tricky to set up. From unknown or not well-documented errors to hard debuggable fails, it can be a bit of a pain. R2 abstracts a lot of the complexity away and makes it very easy to get started.

    On top of all this, Cloudflare also offers a generous free tier, with 10GB of storage, 1.000.000 write operations, and 10.000.000 read operations per month.

    Now that we know what R2 is, let's see how to use it in SvelteKit.

    Installing and setting up a new SvelteKit project

    The first step is of course to create a new SvelteKit project, with TypeScript, preferably.


    If you have read my previous article about implementing file upload in SvelteKit, you can reuse some of the code we wrote there.

    Styling the upload form

    For this post, I'm going to use some components from Flowbit Svelte to style the upload form. You can of course use any other CSS framework or write your own styles.

    You can find here the steps to install Flowbit Svelte in your project.

    Here is what the upload form looks like in the browser:

    SvelteKit upload form UI

    Installing the S3 dependencies

    R2 provides a few ways to interact with the API. You could either use the Cloudflare workers API or a compatible S3 client. For this post, I'm going to use the Modularize AWS SDK for JavaScript.

    We will also make use of pre-signed URLs to share access to our files without revealing our API credentials.

    Let's install the dependencies:


    Upload flow

    Here is a quick overview of the upload flow we're going to implement:

    1. The user selects a file to upload (client)

    2. We make a request to a server endpoint to get a pre-signed URL (server)

    3. When the endpoint returns the URL, we upload the file to R2 (client)

    4. The user can now access the file using the URL

    New Bucket setup in R2

    Let's start by creating a new bucket in R2. You can do this from your Cloudflare dashboard, in the R2 section.

    Cloudflare R2 dashboard

    Once the bucket is created, you can click on it to access the settings. We will need to update the CORS settings to allow uploads from our domain.

    For that, go to the settings page of the bucket, click on "Edit CORS policy"; and update the policy to allow uploads from your domain:


    Here, we are allowing uploads from our domain and from localhost, which is useful for development.

    Creating a new S3 client

    To create a new S3 client, we need to get our API tokens from the R2 dashboard. You can create new API tokens by clicking on "Manage R2 API Tokens" in the R2 section of the dashboard.

    Create API tokens for Cloudflare R2

    Unfortunately, at the moment, R2 doesn't support per-bucket API tokens at the moment. That means that the token we're going to create will have access to all the buckets in our account.

    This is not ideal, but from what I've been seeing in Cloudflare's community forums, this is something the team is aware of, and they plan to add support for per-bucket tokens in the future.

    Check this thread for more information.

    Once you have created the token, you can copy the key and secret and use them to create a new S3 client in a  src/lib/s3.ts  file:


    We are using the  R2_ACCOUNT_ID  environment variable to build the endpoint URL. This is the same ID you can find in the URL of your R2 dashboard.

    Generating a pre-signed URL

    On the client side, here is what a rough implementation of the upload flow looks like at this point:


    We are getting the file data from the  change  event of the  Fileupload  component. We can then use this data to make a request to our server endpoint to get a pre-signed URL.

    Presigned URLs  are used to grant temporary access to a file. They are useful when you want to share a file without revealing your API credentials. You can read more about them here.

    Because we are using secret keys to generate the URLs, we need to do this on the server.

    Let's create a new endpoint in  src/routes/api/upload/+server.ts :


    They are a lot of things going on here, so let's break it down.

    • First, we are getting the¬† fileName ¬†and¬† fileType ¬†from the request body (we will send them from the client).

    • Then we are doing some validation to make sure the parameters are not empty or undefined.

    • To avoid any weird characters in the file name, we are using a¬† slugifyString ¬†function to transform the file name into a URL-friendly string. We are also adding a timestamp to the file name to make sure we don't have any conflicts.

    • We are then using the¬† getSignedUrl ¬†function from the¬† @aws-sdk/s3-request-presigner ¬†package to generate a pre-signed URL. We are using the¬† PutObjectCommand ¬†to create a new object in our bucket. We are also setting the¬† ACL ¬†to¬† public-read ¬†to make sure the file is publicly accessible.

    Notices that we've set the  expiresIn  option to 5 minutes. This means that the URL will expire after 5 minutes. This is useful to avoid having a URL that can be used by anyone to upload files to our bucket. Since the URL will be used right after by the client, we could even have set a much shorter expiration time.

    Finally, we are returning the pre-signed URL and the object key to the client.

    Now, let's update our client-side code to use this endpoint:


    Here, we are sending a  POST  request to our  /api/upload  endpoint with the file name and type in the request body, and we are getting the pre-signed URL and object key from the response.

    Uploading the file

    Here is the structure of the  presignedUrl  we get from the server:  https://<bucket-name>.<region>.r2.cloudflarestorage.com/<object-key>?<query-params> . We now basically need to send a  PUT  request to this URL with the file data in the request body.


    On a successful response, you can now go to your bucket dashboard on Cloudfare R2 and see your file.

    Accessing the uploaded file

    Per default, all buckets created on Cloudflare R2 are private. To access your bucket's files, you can either connect a custom domain you own to the bucket or use  .r2.dev  subdomain provided by Cloudflare.

    Using a custom domain

    If your domain is already set up on Cloudflare, you just need to go to your bucket settings, and then in the "Public access" section, add your domain in the "Domain" field and click "Continue". This will give you a preview of the DNS records Cloudflare is going to add to your domain. If everything looks good, click "Connect domain".

    It will take a few seconds before the DNS records are propagated, but once it's done, you should be able to access your files at  https://<your-domain>/<object-key> .

    Connect domain to Cloudflare R2 Connect domain to Cloudflare R2 (CNAME)

    In case your domain is not set up on Cloudflare, transfer your DNS management (not the domain) to Cloudflare, and then follow the steps above.

    Using the  .r2.dev  subdomain

    In addition to the custom domain option, Cloudflare also provides a  .r2.dev  subdomain for each bucket. Per default, this subdomain is not accessible publicly. To make it public, go to your bucket settings, and then in the "R2.dev subdomain" section, click "Allow access".

    Keep in mind that this is not recommended for production environments, since, these subdomains are rate limited, and don't take advantage of features like bot management, caching, and more that are available for custom domains.

    Wrapping up

    I've come to enjoy using Cloudflare R2 for my personal projects, as well as for some clients' projects. It can be a great alternative to AWS S3, especially if you are already using Cloudflare a lot in your projects. I hope this article helped you get started with Cloudflare R2.

    You might also like these blog posts

    How to implement file upload with SvelteKit

    How to implement file upload with SvelteKit

    Looking to implement file uploading in your Svelte project? Check out this tutorial for an easy-to-follow guide and start uploading files like a pro.

    Mastering Client-Side Authentication with Firebase and SvelteKit

    Mastering Client-Side Authentication with Firebase and SvelteKit

    Mastering Client-Side Authentication with Firebase and SvelteKit