npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uploadbox/nextjs

v0.7.0

Published

Next.js route handler for Uploadbox — presigned URL uploads with your own S3 bucket

Readme

@uploadbox/nextjs

Next.js route handler for Uploadbox — presigned URL uploads with your own S3 bucket.

npm license

Installation

npm i @uploadbox/nextjs @uploadbox/core

Peer dependency: Next.js 14, 15, or 16.

Quick Start

1. Define your file router

// src/lib/uploadbox.ts
import { f } from "@uploadbox/core";

export const router = {
  imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 5 } })
    .middleware(async ({ auth }) => {
      return { userId: auth?.userId ?? auth?.apiKeyId };
    })
    .onUploadComplete(async ({ file, metadata }) => {
      console.log("Upload complete:", file.url);
      return { fileId: file.key };
    }),
};

export type AppRouter = typeof router;

2. Create the route handler

// src/app/api/uploadbox/route.ts
import { createRouteHandler } from "@uploadbox/nextjs";
import { router } from "@/lib/uploadbox";

export const { GET, POST } = createRouteHandler({ router });

S3 credentials are read from environment variables by default (UPLOADBOX_S3_BUCKET, UPLOADBOX_AWS_REGION, UPLOADBOX_AWS_ACCESS_KEY_ID, UPLOADBOX_AWS_SECRET_ACCESS_KEY), or pass a config object explicitly.

3. Add the React components

See @uploadbox/react for the client-side integration.

Lifecycle Hooks

Lifecycle hooks let you integrate authentication, quotas, and database tracking without coupling your upload logic to a specific database.

export const { GET, POST } = createRouteHandler({
  router,
  hooks: {
    // API key auth (hosted mode):
    onAuthenticate: async (req) => {
      const token = req.headers.get("authorization");
      const user = await verifyToken(token);
      return { apiKeyId: user.id, apiKeyName: user.name };
    },
    // Session auth (self-hosted mode):
    // onAuthenticate: async (req) => {
    //   const session = await getSession(req);
    //   if (!session) return undefined;
    //   return { userId: session.id, userName: session.name };
    // },

    onQuotaCheck: async ({ auth, totalSize, fileCount }) => {
      const usage = await getStorageUsage(auth.apiKeyId ?? auth.userId);
      if (usage + totalSize > MAX_STORAGE) {
        throw UploadboxError.quotaExceeded();
      }
    },

    onUploadStarted: async (events) => {
      await db.insert(uploads).values(events.map(toRecord));
    },

    onUploadCompleted: async (event) => {
      await db.update(uploads)
        .set({ status: "completed" })
        .where(eq(uploads.key, event.file.key));
    },

    onUploadFailed: async (event) => {
      await db.update(uploads)
        .set({ status: "failed", error: event.error })
        .where(eq(uploads.key, event.fileKey));
    },

    onFileVerified: async (fileKey) => {
      // Fallback lookup when in-memory state is lost (e.g., multi-instance deploys)
      return db.query.uploads.findFirst({ where: eq(uploads.key, fileKey) });
    },
  },
});

Available Hooks

| Hook | When | Purpose | |------|------|---------| | onAuthenticate | Every request | Return AuthContext or undefined for anonymous. Use apiKeyId/apiKeyName for hosted/API-key auth; use userId/userName for self-hosted session auth | | onQuotaCheck | Before presigning | Throw UploadboxError.quotaExceeded() to deny | | onUploadStarted | After presigned URLs generated | Track pending uploads | | onFileVerified | On complete, if in-memory miss | Fallback lookup for file data | | onUploadCompleted | After onUploadComplete succeeds | Update DB, trigger webhooks | | onUploadFailed | On upload failure | Log errors, clean up | | onMultipartStarted | Multipart upload created | Track large uploads | | onMultipartCompleted | Multipart upload finished | Update status | | onMultipartAborted | Multipart upload cancelled | Clean up | | onResolveConfig | Every request | Per-tenant S3 config (BYOB) |

Rate Limiting

Built-in sliding window rate limiter, no external dependencies:

export const { GET, POST } = createRouteHandler({
  router,
  rateLimit: {
    requestsPerMinute: 60,
    uploadsPerHour: 100,
  },
});

Limits are applied per API key or client IP.

Processing Pipeline

Run post-upload processing hooks (image resize, virus scan, etc.):

import { createImageResizeHook } from "@uploadbox/core/hooks/image-resize";
import { createVirusScanHook } from "@uploadbox/core/hooks/virus-scan";

export const { GET, POST } = createRouteHandler({
  router,
  processing: {
    hooks: [
      createImageResizeHook({ maxWidth: 1920, format: "webp" }),
      createVirusScanHook({ clamavUrl: "http://localhost:3310" }),
    ],
    mode: "sequential", // or "parallel"
    continueOnError: true,
  },
});

Or use runProcessingPipeline directly:

import { runProcessingPipeline } from "@uploadbox/nextjs";

const results = await runProcessingPipeline(pipelineConfig, file, metadata, s3Client, config);

Hosted Mode

For the Uploadbox hosted platform, use createHostedHandler which adds platform-integrated auth, quotas, and event tracking:

import { createHostedHandler } from "@uploadbox/nextjs";

export const { GET, POST } = createHostedHandler({
  router,
  platform: {
    platformUrl: "https://uploadbox.dev",
    projectId: "proj_abc123",
  },
});

Configuration

Full createRouteHandler options:

createRouteHandler({
  router,                    // FileRouter (required)
  config: {                  // S3 config (or use env vars)
    bucket: "my-bucket",
    region: "us-east-1",
    accessKeyId: "...",
    secretAccessKey: "...",
    endpoint: "...",         // MinIO, R2, Wasabi
    forcePathStyle: true,
    cdnBaseUrl: "https://cdn.example.com",
    presignedUrlExpiry: 3600,
  },
  hooks: { ... },            // LifecycleHooks
  rateLimit: { ... },        // RateLimitConfig
  processing: { ... },       // ProcessingPipelineConfig
  s3ClientFactory: (cfg) => createCustomClient(cfg),
});

Related Packages

License

MIT