šŸ“ Sign Up | šŸ” Log In

← Root | ↑ Up

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” │ šŸ“„ shadcn/directory/nic13gamer/better-upload/quickstart │ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

╔══════════════════════════════════════════════════════════════════════════════════════════════╗
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘

title: Quickstart icon: Rocket

You can have file uploads in your React app in a few minutes with Better Upload. This guide will walk you through the steps to set it up with any React framework.

Before you start, make sure you have an S3-compatible bucket ready. You can use AWS S3, Cloudflare R2, or any other S3-compatible service.

<PageSelect pages={[ { href: '/docs/quickstart', title: 'Multiple files', description: 'Upload more than one file at once.', }, { href: '/docs/quickstart-single', title: 'Single files', description: 'Upload only a single file at once.', }, ]} />

Uploading images

<Steps> <Step>

Install

Install the better-upload package, as well as the AWS S3 Client.

npm i better-upload @aws-sdk/client-s3
</Step> <Step>

Set up server

Your server will create pre-signed URLs, which the client uses to upload files directly to the S3 bucket.

Change my-bucket to your bucket name, and configure the S3 client as needed.

import { S3Client } from '@aws-sdk/client-s3';
import {
  createUploadRouteHandler,
  route,
  type Router,
} from 'better-upload/server';

const s3 = new S3Client(); // [!code highlight]

const router: Router = {
  client: s3,
  bucketName: 'my-bucket', // [!code highlight]
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
    }),
  },
};

export const { POST } = createUploadRouteHandler(router);
import { createFileRoute } from '@tanstack/react-router';
import { handleRequest, route, type Router } from 'better-upload/server';

const s3 = new S3Client(); // [!code highlight]

const router: Router = {
  client: s3,
  bucketName: 'my-bucket', // [!code highlight]
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
    }),
  },
};

export const Route = createFileRoute('/api/upload')({
  server: {
    handlers: {
      POST: async ({ request }) => {
        return handleRequest(request, router);
      },
    },
  },
});
import { ActionFunctionArgs } from '@remix-run/node';
import { handleRequest, route, type Router } from 'better-upload/server';

const s3 = new S3Client(); // [!code highlight]

const router: Router = {
  client: s3,
  bucketName: 'my-bucket', // [!code highlight]
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
    }),
  },
};

export async function action({ request }: ActionFunctionArgs) {
  return handleRequest(request, router);
}
// When using a separate backend server, make sure to update the `api` option on the client hooks.

import { Hono } from 'hono';
import { handleRequest } from 'better-upload/server';

const app = new Hono();

const s3 = new S3Client(); // [!code highlight]

const router: Router = {
  client: s3,
  bucketName: 'my-bucket', // [!code highlight]
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
    }),
  },
};

app.post('/upload', (c) => {
  return handleRequest(c.req.raw, router);
});

In the example above, we create the upload route demo. Learn more about upload routes here.

<Accordions> <Accordion title="Adding authentication">

You can run code before uploads in the server. Use the onBeforeUpload callback:

import { RejectUpload, route, type Router } from 'better-upload/server';

const auth = (req: Request) => ({ id: 'fake-user-id' }); // [!code highlight]

const router: Router = {
  client: s3,
  bucketName: 'my-bucket',
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
      // [!code ++:7]
      onBeforeUpload: async ({ req, files, clientMetadata }) => {
        const user = await auth(req);

        if (!user) {
          throw new RejectUpload('Not logged in!');
        }
      },
    }),
  },
};
</Accordion> <Accordion title="Changing S3 object key">

You can change the S3 object key to any value you want. Use the onBeforeUpload callback:

const router: Router = {
  client: s3,
  bucketName: 'my-bucket',
  routes: {
    demo: route({
      fileTypes: ['image/*'],
      multipleFiles: true,
      maxFiles: 4,
      // [!code ++:7]
      onBeforeUpload: async ({ req, files, clientMetadata }) => {
        return {
          generateObjectInfo: ({ file }) => ({
            key: `files/${file.name}`,
          }),
        };
      },
    }),
  },
};
</Accordion> </Accordions> </Step> <Step>

Create <Uploader /> component

We will now build our UI using pre-built components. We'll use <UploadDropzone /> for multiple file uploads.

Install it via the shadcn CLI:

npx shadcn@latest add @better-upload/upload-dropzone

We'll also use the useUploadFiles hook. The complete code looks like this:

'use client'; // For Next.js

import { useUploadFiles } from 'better-upload/client';
import { UploadDropzone } from '@/components/ui/upload-dropzone';

export function Uploader() {
  const { control } = useUploadFiles({
    route: 'demo',
  });

  return (
    <UploadDropzone
      control={control}
      accept="image/*"
      description={{
        maxFiles: 4,
        maxFileSize: '5MB',
        fileTypes: 'JPEG, PNG, GIF',
      }}
    />
  );
}

Learn more about the hooks here.

</Step> <Step>

Place the component

Now place the <Uploader /> component in your app.

import { Uploader } from '@/components/uploader';

export default function Page() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center">
      <Uploader />
    </main>
  );
}
</Step> <Step>

You're done! šŸŽ‰

You can now run your app and upload images directly to any S3-compatible service!

If you plan on uploading files larger than 5GB, take a look at multipart uploads.

<Accordions> <Accordion title="CORS Configuration">

Make sure to also correctly configure CORS on your bucket. Here is an example:

[
  {
    "AllowedOrigins": [
      "http://localhost:3000",
      "https://example.com" // Add your domain here
    ],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"]
  }
]

Learn more about CORS here.

</Accordion> </Accordions> </Step> </Steps>

Learn more

Concepts

<Cards> <Card href="/docs/routes-multiple" title="Upload routes" description="Configure upload routes for different behaviors." /> <Card href="/docs/hooks-multiple" title="Client hooks" description="Use client-side hooks to easily upload files." /> </Cards>

Guides

<Cards> <Card href="/docs/guides/form" title="Upload in forms" description="Integrate with shadcn/ui forms and React Hook Form." /> <Card href="/docs/guides/tanstack-query" title="With TanStack Query" description="Use TanStack Query to manage the upload process." /> </Cards>

Components

<Cards> <Card href="/docs/components/upload-button" title="Upload button" description="A button that uploads a single file." /> <Card href="/docs/components/upload-dropzone" title="Upload dropzone" description="A dropzone that uploads multiple files." /> <Card href="/docs/components/upload-dropzone-progress" title="Upload dropzone with progress" description="A dropzone that uploads multiple files, showing the progress of each upload." /> </Cards>
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•‘
ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•

← Root | ↑ Up