Better Upload

Upload Routes

Upload routes are where you define how the files are uploaded. To create a route, use the route function. You can create multiple routes for different purposes (e.g. images, videos). A basic example is below.

import { route } from 'better-upload/server';
 
route({
  fileTypes: ['image/*'], // Accepts all image types
  maxFileSize: 1024 * 1024 * 4, // 4MB
});

All routes have the following options:

  • fileTypes: An array of file types to accept. Use any valid MIME type. Like application/pdf to accept PDF files. You can also use wildcards like image/* to accept all image types. By default, all file types are accepted.
  • maxFileSize: The maximum file size in bytes. Default is 5MB.
  • signedUrlExpiresIn: The time in seconds the upload signed URL is valid. Default is 120 seconds (2 minutes).

Callbacks

When defining a route, you may want to run code before or after the upload. You can do this by using the callbacks.

Before upload

The onBeforeUpload callback is called before the pre-signed URL is generated. Use this to run custom logic before uploading a file, such as auth and rate-limiting. You can also customize the S3 object key here.

Throw an UploadFileError to reject the file upload. This will also send the error message to the client.

import { route, UploadFileError } from 'better-upload/server';
 
route({
  onBeforeUpload: async ({ req, file, clientMetadata }) => {
    const user = await auth();
 
    if (!user) {
      throw new UploadFileError('Not logged in!');
    }
 
    return {
      objectKey: user.id,
    };
  },
});

You can return an object with the following properties:

  • objectKey: The S3 object key. If not provided, a random key will be generated. For multiple files, return generateObjectKey instead.
  • metadata: Metadata to be passed to the onAfterSignedUrl callback.

The request and metadata sent from the client are also available.

After generating signed URL

The onAfterSignedUrl callback is called after the pre-signed URL is generated. Use this to run custom logic after the URL is generated, such as logging and saving data.

Metadata from the onBeforeUpload callback is available, as well as metadata sent from the client.

import { route } from 'better-upload/server';
 
route({
  onAfterSignedUrl: async ({ req, file, metadata, clientMetadata }) => {
    console.log('After signed URL:', file.objectKey);
 
    return {
      metadata: {
        example: '123',
      },
    };
  },
});

You can return an object with the following properties:

  • metadata: Metadata to be sent back to the client. Needs to be JSON serializable.

Multiple files

By default, an upload route only accepts a single file. If you want to upload multiple files, set multipleFiles to true.

import { route } from 'better-upload/server';
 
route({
  maxFileSize: 1024 * 1024 * 4, // For each file
  multipleFiles: true, 
  maxFiles: 4, 
});

The callbacks are also a bit different when uploading multiple files. See the example below.

import { route } from 'better-upload/server';
 
route({
  multipleFiles: true,
  maxFiles: 4,
  onBeforeUpload: async ({ req, files, clientMetadata }) => {
    // files is an array
 
    return {
      generateObjectKey: ({ file }) => `files/${file.name}`,
    };
  },
  onAfterSignedUrl: async ({ req, files, metadata, clientMetadata }) => {
    // files is an array
  },
});

Both now get an array of files (files), instead of a single file (file).

onBeforeUpload now needs to return generateObjectKey, instead of just returning a string in objectKey.

Multipart uploads

If you want to upload files larger than 5GB, you need to use multipart uploads. To enable it, set multipart to true. It works both for single and multiple files. No change is needed in the client.

import { route } from 'better-upload/server';
 
route({
  multipart: true, 
  partSize: 1024 * 1024 * 20, // 20MB, default is 50MB
});

You can also modify the following options:

  • partSize: The size of each part in bytes. Default is 50MB.
  • partSignedUrlExpiresIn: The time in seconds the part signed URL is valid. Default is 1500 seconds (25 minutes).
  • completeSignedUrlExpiresIn: The time in seconds the complete signed URL is valid. Default is 1800 seconds (30 minutes).

Router

The router is where you define all of your upload routes. To define a router you can use the Router type, then just create an object.

import { route, type Router } from 'better-upload/server';
 
export const router: Router = {
  client: s3,
  bucketName: 'your-bucket-name',
  routes: {
    demo: route({
      fileTypes: ['image/*'],
    }),
  },
};

You can also just define it in the upload route handler, which is simpler.

On this page