{
  "$type": "site.standard.document",
  "content": {
    "$type": "site.standard.content.markdown",
    "text": "I am currently building [magiedit](https://github.com/magitools/magiedit), a markdown editor that also allows publishing to different platforms (like [Hashnode](https://hashnode.com) and [Dev.to](https://dev.to)) and decided to add some shortcuts/commands to make my life simpler (since I am building this tool primarily for myself xD), including adding images from unsplash and gifs and giphy because\n![Old Man What GIF by Amazon Prime Video](https://media0.giphy.com/media/FEBDBbLFT9px3da0vT/giphy.gif?cid=bcfb6944bd46uy7laheqck7cr13jjhkkjlsv9lkz8j64mnz6&ep=v1_gifs_search&rid=giphy.gif&ct=g)\n\nFor one of the features I'm working on that will allow users to use dall-e to generate images (and cover images based on their article's content as soon I can get that to working) I needed to store the selected images (since openais urls only last for 1 hour and storing 4mb of base64 data in an article makes editing it awful. For this, I turned to [cloudflare's r2 storage](https://www.cloudflare.com/fr-fr/developer-platform/r2/), because I already use their solution for my blog's images and my dns management (plus, their free tier is pretty generous).\n\n![Jimmy Fallon Free Stuff GIF by The Tonight Show Starring Jimmy Fallon](https://media3.giphy.com/media/3tpzkqpbVdshXX1By7/giphy.gif?cid=bcfb69442ar464da1u8i5rc10qqbj90qmbbtmr2g269feyn6&ep=v1_gifs_search&rid=giphy.gif&ct=g)\n\n## Using Cloudflare R2 Storage\n\nCloudflare's R2 Storage is an S3 (the one from Amazon, exactly) compatible storage solution, meaning you can interact with it in \"almost\" the say way you would with an S3 bucket (there are some methods that are not supported and that you can find [here](https://developers.cloudflare.com/r2/).\n\nSo, for my use case, I created a bucket and paired it with a domain name and then generated an access key to that specific bucket (so that I could upload things from my sveltekit api function). Again, the link above should provide all instructions on how to get these informations and what they all mean. Now that I had a bucket, let's get to the heart of this article, which is how to interact with a bucket using sveltekit (try saying that sentence out of context).\n\n## Sveltekit integration\n\nNow for the fun part; using all this in sveltekit. Let me preface this section by saying that I had specific needs in this application that required something of an unorthodox architecture. Since the application is storing user articles, I wanted those to be offline first, as well as other potential informations required for using the application (like tokens for publishing and stuff); this means that I had to create api routes instead of handling all of this in form actions. Having said all this, let's see how it all works.\n\n![Lets Go Start GIF](https://media3.giphy.com/media/3aGZA6WLI9Jde/giphy.gif?cid=bcfb69440en0a56he59l99bvpxc33352js92uu1b4j031q3u&ep=v1_gifs_search&rid=giphy.gif&ct=g)\n\n### Getting the images\n\nFor my specific use case, I needed to store images generated by dall-e (openai), so that meant calling that service; said service returns either a base64 string containing the image or an url (that expires after 1 hour). Passing around a 4mb string didn't sound like a good idea (trust me, I tried), so I was left with the url.\n\nThis is the (hopefully) final code I came up with:\n\n```js\nexport const POST: RequestHandler = async ({ locals, request }) => {\n\tconst session = await locals.auth.validate();\n\tif (!session) {\n\t\tthrow error(401, { message: 'not authorized' });\n\t}\n\tconst formData = await request.formData();\n\tconst { content, description } = Object.fromEntries(formData);\n\tif (!content) {\n\t\tthrow error(500, { message: 'invalid input' });\n\t}\n\tconst arrayBuffer = await (await fetch(content.toString())).arrayBuffer();\n\tconst data = Buffer.from(new Uint8Array(arrayBuffer));\n\tconst key = `${session?.user?.userId}_${uuidv4()}`;\n\tconst url = await saveToBucket(data, key);\n\t// get data from function\n\tawait db.insert(userImages).values({\n\t\turl,\n\t\tuserId: session?.user.userId,\n\t\tdescription: description?.toString()\n\t});\n\treturn json({ message: 'ok', url });\n};\n```\n\nThese two lines are what took me a long time to figure out because I kept running into heap problems (my program was using too much memory on this single call, which is not normal)\n\n```js\nconst arrayBuffer = await (await fetch(content.toString())).arrayBuffer()\nconst data = Buffer.from(new Uint8Array(arrayBuffer))\n```\n\nThis allows me to download the image from the url openai provides, store it as a buffer and then do whatever I want with it (in this case, store it in a bucket). Then I generate a key using the user's id a random uuid (I will always have access to the user, because this is one of the few commands requiring authentication) which will become the file name, and then call the `saveToBucket` function.\n\n### Saving the images (or any other file, really)\n\nYou may have noticed I had a call to a functionn `saveToBucket` in the previous code sample; well, as the name suggests, this is where the buffer we got earlier gets saved into the bucket and we get a url back:\n\n```js\nexport async function saveToBucket(data: Buffer, key: string) {\n\tconst S3 = new S3Client({\n\t\tregion: 'auto',\n\t\tendpoint: `https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,\n\t\tcredentials: {\n\t\t\taccessKeyId: CLOUDFLARE_ACCESS_KEY_ID,\n\t\t\tsecretAccessKey: CLOUDFLARE_SECRET_ACCESS_KEY\n\t\t}\n\t});\n\tawait S3.send(\n\t\tnew PutObjectCommand({\n\t\t\tACL: 'public-read',\n\t\t\tKey: key,\n\t\t\tBody: data,\n\t\t\tBucket: CLOUDFLARE_BUCKET_NAME\n\t\t})\n\t);\n\treturn `${CLOUDFLARE_BUCKET_URL}/${key}`;\n}\n```\n\nAgain, most of this stuff comes from the cloudflare documentation I linked above, like how to setup the S3Client. This function basically puts together a bunch of environment variables and call a single function with the command needed to upload a file to a specific bucket. And just like that, we have image upload !\n\n![Art Drawing GIF by GEICO](https://media3.giphy.com/media/MXM5QQ3jY7WmcmPwTI/giphy.gif?cid=bcfb6944pnewgj5qvmk7rqd9rizxnherrb1klviruuvl5jfc&ep=v1_gifs_search&rid=giphy.gif&ct=g)\n\n## What's left after uploading\n\nThe one thing I wanted was for users to be able to retrieve all the images they had generated (and be able to download them at some point), so I simply stored the url for the new image with the user's in my database, and retrieve all those belonging to a specific user id when necessary.",
    "version": "1.0"
  },
  "path": "/articles/file-uploads-with-sveltekit-and-cloudflare-cover",
  "publishedAt": "2023-09-12T00:00:00.000Z",
  "site": "at://did:plc:dgtaz4vldacvqhvvmdvoc4ad/site.standard.publication/3mfbydibiwc7f",
  "tags": [
    "s3",
    "cloudflare",
    "sveltekit"
  ],
  "textContent": "I am currently building magiedit, a markdown editor that also allows publishing to different platforms (like Hashnode and Dev.to) and decided to add some shortcuts/commands to make my life simpler (since I am building this tool primarily for myself xD), including adding images from unsplash and gifs and giphy because\n\nFor one of the features I'm working on that will allow users to use dall-e to generate images (and cover images based on their article's content as soon I can get that to working) I needed to store the selected images (since openais urls only last for 1 hour and storing 4mb of base64 data in an article makes editing it awful. For this, I turned to cloudflare's r2 storage, because I already use their solution for my blog's images and my dns management (plus, their free tier is pretty generous).\n\nUsing Cloudflare R2 Storage\n\nCloudflare's R2 Storage is an S3 (the one from Amazon, exactly) compatible storage solution, meaning you can interact with it in \"almost\" the say way you would with an S3 bucket (there are some methods that are not supported and that you can find here.\n\nSo, for my use case, I created a bucket and paired it with a domain name and then generated an access key to that specific bucket (so that I could upload things from my sveltekit api function). Again, the link above should provide all instructions on how to get these informations and what they all mean. Now that I had a bucket, let's get to the heart of this article, which is how to interact with a bucket using sveltekit (try saying that sentence out of context).\n\nSveltekit integration\n\nNow for the fun part; using all this in sveltekit. Let me preface this section by saying that I had specific needs in this application that required something of an unorthodox architecture. Since the application is storing user articles, I wanted those to be offline first, as well as other potential informations required for using the application (like tokens for publishing and stuff); this means that I had to create api routes instead of handling all of this in form actions. Having said all this, let's see how it all works.\n\nGetting the images\n\nFor my specific use case, I needed to store images generated by dall-e (openai), so that meant calling that service; said service returns either a base64 string containing the image or an url (that expires after 1 hour). Passing around a 4mb string didn't sound like a good idea (trust me, I tried), so I was left with the url.\n\nThis is the (hopefully) final code I came up with:\n\nThese two lines are what took me a long time to figure out because I kept running into heap problems (my program was using too much memory on this single call, which is not normal)\n\nThis allows me to download the image from the url openai provides, store it as a buffer and then do whatever I want with it (in this case, store it in a bucket). Then I generate a key using the user's id a random uuid (I will always have access to the user, because this is one of the few commands requiring authentication) which will become the file name, and then call the  function.\n\nSaving the images (or any other file, really)\n\nYou may have noticed I had a call to a functionn  in the previous code sample; well, as the name suggests, this is where the buffer we got earlier gets saved into the bucket and we get a url back:\n\nAgain, most of this stuff comes from the cloudflare documentation I linked above, like how to setup the S3Client. This function basically puts together a bunch of environment variables and call a single function with the command needed to upload a file to a specific bucket. And just like that, we have image upload !\n\nWhat's left after uploading\n\nThe one thing I wanted was for users to be able to retrieve all the images they had generated (and be able to download them at some point), so I simply stored the url for the new image with the user's in my database, and retrieve all those belonging to a specific user id when necessary.",
  "title": "File Uploads with Sveltekit and Cloudflare",
  "updatedAt": "2026-05-17T13:09:10.918Z"
}