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

Routes

Named file routes — server-owned keys, constraints, and object policies.

A route is a named storage policy on your server. Each route owns its constraints, object keys, and enabled operations (upload, download, delete, and upload.multipart).

The browser sends a route name with each request. The server enforces file constraints, validates permissions, and generates object keys.

lib/s3.ts
import { dimahS3, route } from "@dimah-s3/server";
import { awsS3 } from "./aws-s3";

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

awsS3 is the S3Client from Server Setup.


Combining operations

Enable operations on the same route when they manage the same objects. For example, if users upload, view, and remove their avatars:

avatar: route({
  upload: {
    fileTypes: ["image/*"],
    maxFileSize: 2 * 1024 * 1024,
  },
  download: true, 
  delete: true, 
}),

With the default keyPrefix (the route name), download and delete must target keys within that route's namespace. That is route isolation, not per-user ownership — see Security.


Key generation and object identity

By default, the server generates object keys formatted as:

{keyPrefix}/{uuid}/{sanitizedName}

keyPrefix defaults to the route name (avatar → avatar/…, document → document/…). The same prefix is used twice:

  1. Upload and multipart init — the server generates the key. The client sends a route name, not an object key. The key travels inside the upload ticket, so confirm and the later multipart calls never send it.
  2. Download and delete — the client sends a key. The server accepts it only if it sits under that route's prefix.

So DELETE ?route=avatar&key=avatar/{uuid}/photo.jpg is valid, and DELETE ?route=avatar&key=document/{uuid}/cv.pdf returns KEY_INVALID. Two routes cannot share or nest prefixes (files and files/images); dimahS3() throws at startup.

Disabling namespace isolation

keyPrefix: false allows download and delete to target any safe key in the bucket. Use it only when strict route or operation guards enforce the complete access policy.

To customize key generation or organize files by user or tenant, return keyFolder or key from upload.object:

avatar: route({
  upload: {
    fileTypes: ["image/*"],
    maxFileSize: 2 * 1024 * 1024,
    object: async ({ request }) => {
      const session = await getSession(request);
      return {
        keyFolder: `users/${session.userId}`, 
        // Results in: avatar/users/{userId}/{uuid}/{name}
      };
    },
  },
}),

object runs after the instance and route guards, so it can also read locals instead of loading the session again.

Keys are limited to 1024 UTF-8 bytes. A generated key turns ${ in the file name into _{. On a method: "POST" route, a key from object that contains ${filename} returns KEY_INVALID, so the presigned form always pins the exact key.

Route lifetimes and sizes (upload.expiresIn, upload.confirmWindow, upload.maxFileSize, upload.multipart.partSize, upload.multipart.ticketExpiresIn, and download.maxExpiresIn) must be positive integers. Any other value throws at dimahS3().


Type reference

import type {
  DimahS3RouteConfig,
  UploadConfig,
  DownloadConfig,
  DeleteConfig,
  MultipartConfig,
  UploadObjectInfo,
} from "@dimah-s3/server";

DimahS3RouteConfig

Prop

Type


UploadConfig

Prop

Type


UploadObjectInfo

What upload.object returns.

Prop

Type


DownloadConfig

Prop

Type


DeleteConfig

Prop

Type

Frequently asked questions

On this page