2.0 beta — new releases can include breaking changes. Stable 1.x docs
dimah-s3v2.0.0-beta.0

Quickstart

A working S3 upload in minutes — new app or existing project.

Create a new project

Scaffold a full-stack project with preconfigured S3 routes and UI. create also writes a generated DIMAH_S3_SECRET to .env.

npx @dimah-s3/cli@beta create

Manual installation

Install dependencies

npm i @dimah-s3/server@beta @dimah-s3/core@beta @dimah-s3/react@beta @fuma-translate/react @aws-sdk/client-s3

@dimah-s3/react needs React 19.2 or later.

Configure S3 and routes

.env
S3_ENDPOINT="https://your-endpoint.example.com"
S3_REGION="auto"
S3_ACCESS_KEY_ID="your-access-key-id"
S3_SECRET_ACCESS_KEY="your-secret-access-key"
S3_BUCKET="your-bucket-name"

# Signs upload tickets. Generate one with: openssl rand -base64 32
DIMAH_S3_SECRET="your-generated-secret"
lib/s3.ts
import { S3Client } from "@aws-sdk/client-s3";
import { dimahS3, route } from "@dimah-s3/server";

export const awsS3 = new S3Client({
  region: process.env.S3_REGION,
  endpoint: process.env.S3_ENDPOINT,
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID!,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
  },
  // The SDK default (WHEN_SUPPORTED) breaks presigned uploads.
  requestChecksumCalculation: "WHEN_REQUIRED",
});

export const s3 = dimahS3({
  client: awsS3,
  bucket: process.env.S3_BUCKET!,
  routes: {
    avatar: route({
      upload: {
        fileTypes: ["image/*"],
        maxFileSize: 2 * 1024 * 1024, // 2MB
      },
      download: true,
    }),
  },
});

Set bucket CORS for your origin — Amazon S3, Cloudflare R2, or MinIO.

Upload secret

dimahS3() reads DIMAH_S3_SECRET to sign upload tickets. Use at least 32 characters and the same value on every server instance, or pass secret yourself. When it is missing, development and test fall back to a random secret with a warning; anywhere else, uploads return 500.

Mount the route handler

app/api/s3/[...s3]/route.ts
import { toNextJsHandler } from "@dimah-s3/server/next";
import { s3 } from "@/lib/s3";

export const { GET, POST, PUT, PATCH, DELETE } = toNextJsHandler(s3);

Cache Components

Next.js 16.4 enables Cache Components on new apps. This handler reads the request, so it runs on each request. Keep the handler as written. Server Setup explains why it must stay uncached.

Other runtimes: Server Setup.

Create the client and provider

lib/s3-client.ts
"use client";

import { createS3Client } from "@dimah-s3/react";

export const s3Client = createS3Client();
export const S3Provider = s3Client.Provider;

Wrap your application

npm i @dimah-s3/ui@beta shadcn
app/globals.css
@import "shadcn/tailwind.css";
@import "@dimah-s3/ui/styles.css"; 

/* shadcn theme variables (`--primary`, `--muted`, …) */
app/layout.tsx
import { Toaster } from "@dimah-s3/ui"; 
import { S3Provider } from "@/lib/s3-client"; 

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <S3Provider>{children}</S3Provider>
        <Toaster />
      </body>
    </html>
  );
}

Add an upload button

app/page.tsx
"use client";

import { useUpload } from "@dimah-s3/react";
import { Upload, UploadToasts, UploadTrigger } from "@dimah-s3/ui";

export default function Page() {
  const upload = useUpload({ route: "avatar" });

  return (
    <>
      <Upload upload={upload}>
        <UploadTrigger />
      </Upload>
      <UploadToasts upload={upload} />
    </>
  );
}

Selecting a file presigns a secure URL and uploads directly to your bucket. UploadToasts reports progress, success, and errors for this hook. See Upload for the dropzone and status parts.

Frequently asked questions

On this page