Get startedFramework guidesAll docs

documentation / get started

Framework guides

Every server entry point exports the same createUploadfile builder, so one router works everywhere. Only the route handler and the client helper change.

Entry points

Set UPLOADFILE_TOKEN in the server environment for every framework. The token selects staging or production. Write the router once, importing createUploadfile from the entry point for your server. The Next.js quickstart shows a complete router.

ServerImport from
Next.js App Router@uploadfile/core/next
Next.js Pages Router@uploadfile/core/next-legacy
Express@uploadfile/core/express
Fastify@uploadfile/core/fastify
H3, Nitro, and Nuxt@uploadfile/core/h3
Remix and React Router@uploadfile/core/remix
SvelteKit, SolidStart, Hono, Bun, Deno, and other Fetch servers@uploadfile/core/server

Next.js Pages Router

pages/api/uploadfile.ts
import { createRouteHandler } from "@uploadfile/core/next-legacy";
import { fileRouter } from "../../server/file-router";

export default createRouteHandler({ router: fileRouter });

Express

src/file-router.ts
import { createUploadfile, type FileRouter } from "@uploadfile/core/express";

const f = createUploadfile();

export const fileRouter = {
  imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } })
    .middleware(async () => {
      // Look up the signed-in user here and throw to reject the upload.
      const user = { id: "demo-user" };
      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      console.log("Uploaded by", metadata.userId, file.ufsUrl);
      return { url: file.ufsUrl };
    }),
} satisfies FileRouter;

export type AppFileRouter = typeof fileRouter;
src/server.ts
import express from "express";
import { createRouteHandler } from "@uploadfile/core/express";
import { fileRouter } from "./file-router";

const app = express();
app.use("/api/uploadfile", createRouteHandler({ router: fileRouter }));
app.listen(3000);

If your frontend runs on a different origin, pass the full route URL to the client helper, for example url: "http://localhost:3000/api/uploadfile".

Fastify

src/server.ts
import Fastify from "fastify";
import { createRouteHandler } from "@uploadfile/core/fastify";
import { fileRouter } from "./file-router";

const app = Fastify();
await app.register(createRouteHandler, { router: fileRouter });
await app.listen({ port: 3000 });

The plugin serves /api/uploadfile itself, so register it without a prefix.

H3 and Nitro

src/app.ts
import { createApp, createRouter } from "h3";
import { createRouteHandler } from "@uploadfile/core/h3";
import { fileRouter } from "./file-router";

export const app = createApp();
const router = createRouter();
router.use("/api/uploadfile", createRouteHandler({ router: fileRouter }));
app.use(router);

SvelteKit, SolidStart, and Fetch servers

The handler from @uploadfile/core/server takes a Request, or an event object with a request property, and returns a Response.

src/routes/api/uploadfile/+server.ts (SvelteKit)
import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "$lib/server/file-router";

const handler = createRouteHandler({ router: fileRouter });
export { handler as GET, handler as POST };
src/routes/api/uploadfile.ts (SolidStart)
import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "~/server/file-router";

const handler = createRouteHandler({ router: fileRouter });
export { handler as GET, handler as POST };
src/index.ts (Hono)
import { Hono } from "hono";
import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "./file-router";

const handler = createRouteHandler({ router: fileRouter });
const app = new Hono();
app.all("/api/uploadfile", (c) => handler(c.req.raw));

export default app;

Remix and React Router

app/routes/api.uploadfile.ts
import { createRouteHandler } from "@uploadfile/core/remix";
import { fileRouter } from "~/server/file-router";

export const { loader, action } = createRouteHandler({ router: fileRouter });

React

@uploadfile/react works in any React app. Generate typed components or the useUploadfile hook from your router type. Upload components covers styling and building your own picker.

src/uploadfile.ts
import { generateReactHelpers, generateUploadButton, generateUploadDropzone } from "@uploadfile/react";
import type { AppFileRouter } from "../server/file-router";

export const UploadButton = generateUploadButton<AppFileRouter>();
export const UploadDropzone = generateUploadDropzone<AppFileRouter>();
export const { useUploadfile, uploadFiles } = generateReactHelpers<AppFileRouter>();

Vue

src/uploadfile.ts
import { generateUploadButton, generateUploadDropzone } from "@uploadfile/vue";
import type { AppFileRouter } from "../server/file-router";

export const UploadButton = generateUploadButton<AppFileRouter>();
export const UploadDropzone = generateUploadDropzone<AppFileRouter>();
src/components/ImageUpload.vue
<script setup lang="ts">
import "@uploadfile/vue/styles.css";
import { UploadButton } from "../uploadfile";

const onDone = (files: { ufsUrl: string }[]) => {
  console.log(files.map((file) => file.ufsUrl));
};
</script>

<template>
  <UploadButton :config="{ endpoint: 'imageUploader', onClientUploadComplete: onDone }" />
</template>

Vue components take one config prop that holds the endpoint and callbacks.

Nuxt

The Nuxt module serves /api/uploadfile, registers UploadButton and UploadDropzone, and auto-imports useUploadfile. It loads the router exported as uploadRouter from server/uploadfile.ts.

nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@uploadfile/nuxt"],
});
server/uploadfile.ts
import { createUploadfile, type FileRouter } from "@uploadfile/core/h3";

const f = createUploadfile();

export const uploadRouter = {
  imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } })
    .middleware(async () => {
      // Look up the signed-in user here and throw to reject the upload.
      const user = { id: "demo-user" };
      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      console.log("Uploaded by", metadata.userId, file.ufsUrl);
      return { url: file.ufsUrl };
    }),
} satisfies FileRouter;

export type UploadRouter = typeof uploadRouter;
app.vue
<template>
  <UploadButton :config="{ endpoint: 'imageUploader' }" />
</template>

Install @uploadfile/nuxt and @uploadfile/core. The module brings in @uploadfile/vue, and without Tailwind it adds the component stylesheet for you.

Svelte

src/lib/server/file-router.ts
import { createUploadfile, type FileRouter } from "@uploadfile/core/server";

const f = createUploadfile();

export const fileRouter = {
  imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } })
    .middleware(async () => {
      // Look up the signed-in user here and throw to reject the upload.
      const user = { id: "demo-user" };
      return { userId: user.id };
    })
    .onUploadComplete(async ({ metadata, file }) => {
      console.log("Uploaded by", metadata.userId, file.ufsUrl);
      return { url: file.ufsUrl };
    }),
} satisfies FileRouter;

export type AppFileRouter = typeof fileRouter;
src/lib/uploadfile.ts
import { generateSvelteHelpers } from "@uploadfile/svelte";
import type { AppFileRouter } from "$lib/server/file-router";

export const { createUploader, createUploadfile } = generateSvelteHelpers<AppFileRouter>();
src/routes/+page.svelte
<script lang="ts">
  import "@uploadfile/svelte/styles.css";
  import { UploadButton } from "@uploadfile/svelte";
  import { createUploader } from "$lib/uploadfile";

  const uploader = createUploader("imageUploader", {
    onClientUploadComplete: (files) => console.log(files.map((file) => file.ufsUrl)),
    onUploadError: (error) => alert(error.message),
  });
</script>

<UploadButton {uploader} />

Solid

src/components/ImageUpload.tsx
import "@uploadfile/solid/styles.css";
import { generateUploadButton } from "@uploadfile/solid";
import type { AppFileRouter } from "~/server/file-router";

const UploadButton = generateUploadButton<AppFileRouter>();

export default function ImageUpload() {
  return (
    <UploadButton
      endpoint="imageUploader"
      onClientUploadComplete={(files) => console.log(files.map((file) => file.ufsUrl))}
    />
  );
}

Expo

Serve the route from an Expo Router API route, or from any server above. Expo API routes need web.output set to server in your app config. The Expo helpers find the route through EXPO_PUBLIC_SERVER_ORIGIN, or through the development server's address while you develop.

app/api/uploadfile+api.ts
import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "../../server/file-router";

const handler = createRouteHandler({ router: fileRouter });
export { handler as GET, handler as POST };
app/index.tsx
import { Alert, Pressable, Text } from "react-native";
import { generateReactNativeHelpers } from "@uploadfile/expo";
import type { AppFileRouter } from "../server/file-router";

const { useImageUploader } = generateReactNativeHelpers<AppFileRouter>();

export default function Home() {
  const { openImagePicker, isUploading } = useImageUploader("imageUploader", {
    onClientUploadComplete: () => Alert.alert("Upload complete"),
    onUploadError: (error) => Alert.alert("Upload failed", error.message),
  });
  return (
    <Pressable disabled={isUploading} onPress={() => openImagePicker({ source: "library" })}>
      <Text>{isUploading ? "Uploading..." : "Choose an image"}</Text>
    </Pressable>
  );
}
Up nextBuild with AI agentsGive a coding agent the Uploadfile skill and it can add uploads to your project with the right packages, environment, and code.