Uploading and Saving Files with SvelteKit
Table of contents
Recently, while working on a project, I needed to add a feature to allow users to upload a file and save it on the server. I did quite a bit of searching for a working solution. However, many of the answers I found were outdated, used features I didn't have available, or just flat-out didn't work. I eventually figured out a solution that worked for me which I'll document below.
Setup
You can skip this section if you already have an existing SvelteKit project. I'm including this section so people can get a fully reproducible project from start to finish.
First, make sure you have Node.js installed. I'm using Node.js 18.16.0 and npm 9.5.1.
Then, open up a terminal. I'm on Windows 11, so I just press the Windows key, type terminal, and press Enter.
Navigate to wherever you want to place your project.
cd C:\Users\Travis\Development
Create a new SvelteKit project.
npm create svelte@latest skupload
I named the project skupload, but feel free to choose any name to your liking.
Answer the questions given to create the project. Here's what I chose:
Which Svelte app template? Skeleton project
Add type checking with TypeScript? Yes, using JavaScript with JSDoc comments
Select additional options (none)
Change into the directory.
cd skupload
Install dependencies.
npm install
Optionally, initialize version control with git.
git init && git add -A && git commit -m "Initial commit"
Run the development server.
npm run dev -- --open
The code
Before we write the first line of code, we'll need a directory to store the uploads. Create a directory called uploads
in the root of your project.
Now, edit src/routes/+page.svelte
. Replace the default sample code with the following:
<form method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<button>Upload</button>
</form>
This is a basic form in HTML, but it's all we need. Note:
The form
method
must bepost
The form must have an
enctype
ofmultipart/form-data
The
input
of typefile
will render a "file chooser" form control. Here, the input's name is file, but feel free to choose any name of your liking. Just keep in mind that we will be referencing it later.There is a
button
at the end of the form that submits the form.
Now, create a new file called src/routes/+page.server.js
with the following contents:
import { writeFile } from 'node:fs/promises';
import { extname } from 'path';
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const uploadedFile = formData?.get('file');
const filename = `uploads/${crypto.randomUUID()}${extname(uploadedFile?.name)}`;
await writeFile(filename, Buffer.from(await uploadedFile?.arrayBuffer()));
return { success: true };
}
};
At the top of the file, we import writeFile
and extname
from some built-in Node.js modules. We use these functions further down in the code.
Then, we use a JSDoc comment to hint that the actions
object is a SvelteKit Actions
type.
Inside the actions
object, we provide a single property called default
, which contains an async function that will run when the form is submitted.
Inside the default
function, we set the formData
constant to the form data that the user submitted.
The uploadedFile
constant is set to the file that was submitted in the form data. The string 'file'
here refers to the name of the input on the form.
The filename
could be anything you want. I prefer to use a random UUID so that no files get overwritten. Notice we're using extname
to keep the file extension of the uploaded file. Also, notice all uploaded files will be placed in the uploads
directory we created earlier.
Next, we write the file to disk using writeFile
. Using Buffer.from
and arrayBuffer()
in this way is the secret to this solution.
Finally, we return { success: true }
. You don't have to return this exact thing, so feel free to change it depending on your needs.
Trying it out
Make sure the development server is running, then (if it isn't already open) navigate to http://localhost:5173/
You should see the simple form we wrote earlier. On Chrome, it looks like this:
Click on Choose File. A file chooser will appear. Choose any file you want to upload.
Once you're back on the form, click Upload.
It may not look like anything happened, because the page simply reloads after a successful upload. Later, you'll probably want to do some redirection to a "success" page or similar.
If you check the uploads
directory inside your project, though, you should see a new file. This is the file that was uploaded.
That's it! We successfully created a form in SvelteKit that handles file uploads and writes them to disk.
Further improvements
The style of the form could be significantly improved. You'll probably want to style it to match the rest of your app.
Some sort of confirmation that the file upload was successful is necessary. I recommend redirecting to a page with a success message.
Writing the file to disk is great, but you'll probably want to reference it later somewhere in your app. For that, I recommend saving the filename to a database alongside some relevant information; perhaps the original filename, the user account who uploaded it, the date and time, etc.
All of the code in this article is available in a working example on GitHub:
Subscribe to my newsletter
Read articles from Travis Horn directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Travis Horn
Travis Horn
I have a passion for discovering and working with cutting-edge technology. I am a constant and quick learner. I enjoy collaborating with others to solve problems. I believe helping people achieve their goals helps me achieve mine.