Someone recently asked me how to use with , to which I responded I'll create a small document for this (I'll create a pr to add this to the a little later). FormData Astro Astro docs Getting started I won't go into detail on what is, but here is a short summary FormData [is an] interface [which] provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the or method. It uses the same format a form would use if the encoding type were set to . Source: FormData fetch() XMLHttpRequest.send() "multipart/form-data" MDN - FormData Basically instead of using JSON to send data to and from your server, you'd use , except unlike JSON it supports files natively. FormData For example, // 1. Create or Get a File /** Creating a File */ const fileContent = `Text content...Lorem Ipsium`; const buffer = new TextEncoder().encode(fileContent); const blob = new Blob([buffer]); const file = new File([blob], "text-file.txt", { type: "text/plain" }); /** OR */ /** Getting a File */ const fileInput = document.querySelector("#files"); // <input id="files" type="file" multiple /> const file = fileInput.files.item(0); // 2. Create FormData const formData = new FormData(); // 3. Add File to FormData through the `file` field formData.append("file", file); // FormData keys are called fields const file = fileInput.files.item(0); is a , which is similar but an array, to work around this you can convert the to an array of 's using fileInput.files FileList not FileList File Array.from For our use case, since we're only trying to upload one file, it'd be easier to select the first in the File FileList Learn more on and MDN - HTMLInputElement MDN - File you can also just directly use instead of using an element Note: FileReader <input /> Usage There are 2 ways to support in ; the easy and the hard way, I'll show you both. FormData Astro : both the easy and hard way require to be configured in mode Note Astro server (SSR) import { defineConfig } from 'astro/config'; // https://astro.build/config export default defineConfig({ output: 'server', }); Easy Way The requires you to create a new file that will act as your endpoint, for example, if you wanted a endpoint, you would create a file in . easy way .ts /upload .ts src/pages Read 's official docs on to learn more Astro File Routes Your basic file tree should look like this after creating your endpoint src/ pages/ upload.ts index.astro Inside your file follow the example I gave above in , on getting up and running. index.astro #getting-started FormData Once you've created an instance of and populated it with the files you'd like to upload, you then just setup a POST request to that endpoint. FormData // ... const res = await fetch('/upload', { method: 'POST', body: formData, }); const result = await res.json(); console.log(JSON.stringify(result)); From the endpoint side you'd then need to export a post method to handle the POST request being sent, Here is where things get complex. I recommend going through Astro's File Routes Docs import type { APIContext } from 'astro'; // File routes export a get() function, which gets called to generate the file. // Return an object with `body` to save the file contents in your final build. // If you export a post() function, you can catch post requests, and respond accordingly export async function post({ request }: APIContext) { const formData = await request.formData(); return { body: JSON.stringify({ fileNames: await Promise.all( formData.getAll('files').map(async (file: File) => { return { webkitRelativePath: file.webkitRelativePath, lastModified: file.lastModified, name: file.name, size: file.size, type: file.type, buffer: { type: 'Buffer', value: Array.from( new Int8Array(await file.arrayBuffer()).values() ), }, }; }) ), }), }; } The basics of what's happening here are fairly simple, but the code all put together seems rather complex, so let's break it down. First, the exported post function handles POST requests as its name suggests, meaning if you send a get request and don't export a get function an error will occur. what?! Yeah, I too recently learned that supports this out of the box, which is awesome. export async function post() { ... } Astro W3Schools cover fairly well, take a look at their article if you're not familiar with POST and GET requests POST and GET Let's first talk about the parameter. As it's name suggests is an instance of the class which includes all the methods that supports, including a method for transforming said request into you can work with. request request Request Request FormData // ... export async function post({ request }: APIContext) { const formData = await request.formData(); // ... } Using you can get all the instances of a specific field ( keys are called fields), for example, get all 's in the field. formData FormData File file // ... export async function post({ request }: APIContext) { const formData = await request.formData(); return { body: JSON.stringify({ // getAll('file') will return an array of File classes fileNames: formData.getAll('file'), }), }; } The problem with this solution is that it will return due to being unable to convert classes to a string {"fileNames":[{}]} JSON.stringify File To deal with this formatting issue we need to format the 's array properly, File // ... export async function post({ request }: APIContext) { const formData = await request.formData(); return { body: JSON.stringify({ // getAll('files') will return an array of File classes fileNames: formData.getAll('files').map(async (file: File) => { return { webkitRelativePath: file.webkitRelativePath, lastModified: file.lastModified, name: file.name, size: file.size, type: file.type, buffer: { /* ... */ } }; }), }), }; } The last part is converting into data that is easy to work with, for this case using arrays to represent buffers works rather well, so we just do some conversion, ArrayBuffers // ... export async function post({ request }: APIContext) { const formData = await request.formData(); return { body: JSON.stringify({ // getAll('file') will return an array of File classes fileNames: formData.getAll('file').map(async (file: File) => { return { // ... buffer: { type: 'Buffer', value: Array.from( new Int8Array( await file.arrayBuffer() ).values() ), }, }; }), }), }; } That's the . Using baked in to act as an endpoint for your . easy way Astro's file routes FormData To actually run with the endpoint all you need is Astro /upload npm run dev You can view a demo of the on , and easy way Stackblitz CodeSandbox GitHub Hard Way The requires you to use the middleware together with , in order to make the integration support requests. hard way multer expressjs @astrojs/node FormData The mostly builds on the , except instead of a file, you would instead use a file in the root directory to define your endpoints, so, your file structure would look more like this, hard way #easy-way src/pages/upload.ts server.mjs src/ pages/ index.astro server.mjs The core of the occurs inside . should look like this by the end of this blog post hard way server.mjs server.mjs import express from 'express'; import { handler as ssrHandler } from './dist/server/entry.mjs'; import multer from 'multer'; const app = express(); app.use(express.static('dist/client/')); app.use(ssrHandler); const upload = multer(); app.post('/upload', upload.array('file'), function (req, res, next) { // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files // // e.g. // req.files['avatar'][0] -> File // req.files['gallery'] -> Array // // req.body will contain the text fields, if there were any console.log(req.files); res.json({ fileNames: req.files }); }); app.listen(8080); When you build an project in mode (e.g. ), will automatically generate a file, it's this file that allows us to build our own custom nodejs server and then run off this server. Astro server (SSR) npm run build Astro dist/server/entry.mjs Astro For this specific use case we are using for the server, and to enable support in we need the middleware, so if you're familiar with at all this should look familiar, express FormData express multer express import express from 'express'; import { handler as ssrHandler } from './dist/server/entry.mjs'; const app = express(); app.use(express.static('dist/client/')); app.use(ssrHandler); // ... app.listen(8080); The enables to run on the server, for the most part it can be treated like any other middleware and ignored. ssrHandler Astro express express If you're not familiar with the code snippet above, please go through , it'll make the rest of the explanation easier to understand Note: express' documentation The real interesting part is where and meet. multer express By using a POST request handler we are able to recieve POST requests made to the endpoint and respond back with the parsed results, but unlike in the , is able to handle all the formatting allowing responses to be as expected. /upload FormData #easy-way express File // ... import multer from 'multer'; const app = express(); // ... const upload = multer(); app.post('/upload', upload.array('files'), function (req, res, next) { // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files // // e.g. // req.files['avatar'][0] -> File // req.files['gallery'] -> Array // // req.body will contain the text fields, if there were any console.log(req.files); res.json({ fileNames: req.files }); }); app.listen(8080); Response to POST request express That's the . Using mode together with and to create the endpoint which supports . hard way Astro's SSR express multer /upload formData To actually run you need to do a bit more than you'd need for the Astro #easy-way Install and -> express multer npm install express multer Build handler -> Astro npm run build Run -> server.mjs node server.mjs The may seem easier, but that is due to having done alot of the prep work in the , it is actually more overall work than the easy way. hard way #easy-way You can view a demo of the on , and hard way Stackblitz CodeSandbox GitHub Conclusion There are 2 ways of using with , either the or the . FormData Astro easy way hard way The is to use baked in to act as an endpoint for your POST requests. easy way Astro's File Routes FormData The is to use mode together with and to create a endpoint which supports . hard way Astro's SSR express multer /upload FormData There is no right way, but I will recommend the as it is easier and less confusing to work with overall. easy way by on . Photo Caleb Jack (@hitthetrailjack) Unsplash Originally published on but also on blog.okikio.dev dev.to