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.
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.
| Server | Import 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 |
import { createRouteHandler } from "@uploadfile/core/next-legacy";
import { fileRouter } from "../../server/file-router";
export default createRouteHandler({ router: fileRouter });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;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".
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.
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);The handler from @uploadfile/core/server takes a Request, or an event object with a request property, and returns a Response.
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 };import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "~/server/file-router";
const handler = createRouteHandler({ router: fileRouter });
export { handler as GET, handler as POST };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;import { createRouteHandler } from "@uploadfile/core/remix";
import { fileRouter } from "~/server/file-router";
export const { loader, action } = createRouteHandler({ router: fileRouter });@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.
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>();import { generateUploadButton, generateUploadDropzone } from "@uploadfile/vue";
import type { AppFileRouter } from "../server/file-router";
export const UploadButton = generateUploadButton<AppFileRouter>();
export const UploadDropzone = generateUploadDropzone<AppFileRouter>();<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.
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.
export default defineNuxtConfig({
modules: ["@uploadfile/nuxt"],
});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;<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.
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;import { generateSvelteHelpers } from "@uploadfile/svelte";
import type { AppFileRouter } from "$lib/server/file-router";
export const { createUploader, createUploadfile } = generateSvelteHelpers<AppFileRouter>();<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} />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))}
/>
);
}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.
import { createRouteHandler } from "@uploadfile/core/server";
import { fileRouter } from "../../server/file-router";
const handler = createRouteHandler({ router: fileRouter });
export { handler as GET, handler as POST };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>
);
}