Documentation
supastarter for Nuxtsupastarter for NuxtStorage

Uploading files

Learn how to upload files in your supastarter application.

Before you start uploading files, make sure to setup your storage.

You also want to make sure that you have the bucket created that you want to upload files to. For this example we are going to upload a PDF file to a bucket called documents, that we assume you have already created in your storage provider.

Make sure to disable public access to all your buckets as we will care about access control in the API layer of the application.

Add the bucket name to the config

For easy reusability, we recommend adding the bucket name to the config.

packages/storage/config.ts
import type { StorageConfig } from "./types";

export const config = {
	bucketNames: {
		avatars: process.env.NUXT_PUBLIC_AVATARS_BUCKET_NAME ?? "avatars",
		documents: process.env.NUXT_PUBLIC_DOCUMENTS_BUCKET_NAME ?? "documents",
	},
} as const satisfies StorageConfig;

Prepare upload endpoint

As explained on the overview page, supastarter uses presigned URLs to upload files to your storage provider. The API is built with oRPC, so we create a new procedure that returns a signed upload URL for the documents bucket.

supastarter already ships with the createAvatarUploadUrl procedure in packages/api/modules/users/procedures/create-avatar-upload-url.ts, which you can use as a reference. Let's create a new procedure for the documents bucket:

packages/api/modules/uploads/procedures/create-document-upload-url.ts
import { getSignedUploadUrl } from "@repo/storage";
import { config } from "@repo/storage/config";
import { z } from "zod";

// the protectedProcedure makes sure the user is authenticated
import { protectedProcedure } from "../../../orpc/procedures";

export const createDocumentUploadUrl = protectedProcedure
	.route({
		method: "POST",
		path: "/uploads/document-upload-url",
		tags: ["Uploads"],
		summary: "Create document upload URL",
		description: "Create a signed upload URL to upload a document to the storage bucket",
	})
	.input(
		z.object({
			path: z.string().min(1),
		}),
	)
	.handler(async ({ input: { path } }) => {
		const signedUploadUrl = await getSignedUploadUrl(path, {
			bucket: config.bucketNames.documents,
		});

		return { signedUploadUrl };
	});

Then register the procedure in a router and mount it in the main API router (see the define an endpoint guide for more details):

packages/api/modules/uploads/router.ts
import { createDocumentUploadUrl } from "./procedures/create-document-upload-url";

export const uploadsRouter = {
	documentUploadUrl: createDocumentUploadUrl,
};
packages/api/orpc/router.ts
// ...
import { uploadsRouter } from "../modules/uploads/router";

export const router = publicProcedure.router({
	// ...
	uploads: uploadsRouter,
});

This procedure will now allow authenticated users to get a signed upload URL for the documents bucket.

If you want to only allow uploading to specific paths or check for specific file types, you can add additional validation inside the handler and throw an ORPCError (e.g. BAD_REQUEST or FORBIDDEN) when the path or file type is not allowed.

Upload files from the UI

In order to upload files from the UI, use your preferred method to select a file (like a file input or a dropzone component). Then you need to execute the following steps:

  1. Get a signed upload URL for the file you want to upload.
  2. Upload the file to the signed URL.
  3. Store the file url to your database to be able to use the file later.
<script setup lang="ts">
const uploading = ref(false);
const { user } = useSession();
const { orpc } = useORPC();

const createDocumentUploadUrlMutation = useMutation(
  orpc.uploads.documentUploadUrl.mutationOptions(),
);

const uploadFile = async (file: File) => {
  if (!user.value) return;

  uploading.value = true;

  try {
    const path = `${user.value.id}/${crypto.randomUUID()}.pdf`;
    const { signedUploadUrl } = await createDocumentUploadUrlMutation.mutateAsync({
      path,
    });

    const response = await $fetch(signedUploadUrl, {
      method: "PUT",
      body: file,
      headers: {
        "Content-Type": file.type,
      },
    });

    // TODO: store the file path to the database
  } catch (e) {
    // TODO: handle error
  } finally {
    uploading.value = false;
  }
};
</script>

Now your users can upload files to the documents bucket. In the next step you can learn how to access the uploaded files.