The Best Object Storage Experience.
Built for the Modern Web.

Object uploads, downloads, distribution, and management.

Documentation

You don't want to set this all up yourself.

...trust us

Public + private uploads
Public uploads are accessible to anyone on the internet. Private uploads are only accessible to via a signed URL.
CDN distribution
Uploads are distributed via CDN for fast delivery to your users.
File management
Explore uploads in a dashboard. Know who is viewing uploaded files in realtime. Understand your usage.
React + Next.js
Best-in-class developer experience for implementing file uploads in React + Next.js using our open-source libraries.
API integration
Use TypeScript SDKs to integrate our API into your backend to manage uploads and files in your project.
Free tier
10GB of storage, 100GB of bandwidth per month, on us.

Choose your fighter.

We aim to support as many modern web frameworks as possible. Don't see yours? We're probably have it planned! If not, let us know!

app/_uploadjoy/index.ts
server
docs

import { createUploadjoy, type FileRouter } from "@uploadjoy/core/next";

const f = createUploadjoy();

export const uploadRouter = {
  imageRoute: f({
    image: {
      maxFileSize: "8MB",
      acceptedFiles: ["image/png", "image/jpeg"],
      maxFileCount: 1,
    },
  })
    .access("private")
    .middleware(async (req, ctx) => {
      const filesToUpload = ctx.files;
      return { metadata: { message: "hello world!" } };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      // metadata type is inferred from above!
      console.log("Upload complete", metadata, file);
    }),
} satisfies FileRouter;

export type OurFileRouter = typeof uploadRouter;
app/api/uploadjoy/route.ts
server
docs

import { uploadRouter } from "../../_uploadjoy";
import { createNextRouteHandler } from "@uploadjoy/core/next";

export const runtime = "edge";

export const { GET, POST } = createNextRouteHandler({
  router: uploadRouter,
  config: {
    debug: true,
  },
});
app/_components/FileInput.tsx
client
docs

"use client";

import { useInput } from "@uploadjoy/react/hooks";
import { OurFileRouter } from "../_uploadjoy";

export const BasicInput = () => {
  const { 
    getInputProps,
    openFileDialog,
    upload,
    isUploading
  } = useInput<OurFileRouter>({
    endpoint: "imageRoute", // <-- inferred from OurFileRouter!
    clientCallbacks: {
      onUploadSuccess: (ctx) => {
        console.log("onUploadSuccess", ctx);
      },
    },
  });

  return (
    <>
      <input {...getInputProps()} />
      <div className="flex gap-4">
        <button
          onClick={openFileDialog}
          className="p-2 bg-indigo-700 w-fit"
        >
          Pick Files
        </button>
        <button
          onClick={() => upload()}
          className="p-2 bg-slate-700 w-fit disabled:bg-slate-400"
          disabled={isUploading}
        >
          {isUploading ? "Uploading..." : "Upload"}
        </button>
      </div>
    </>
  );
};