GuidesFile Upload

Setup

Arkos supports two approaches to file uploads: standalone routes (/api/uploads/:fileType) that handle files independently, and router uploads that attach files directly to a route in a single request.

// Standalone — upload first, reference the URL later
const { urls } = await fetch("/api/uploads/images", { method: "POST", body: formData }).then(r => r.json());

See File Upload Routes for the full standalone API.

Router uploads

Use experimental.uploads on any route definition to handle file uploads inline — the file path is automatically attached to req.body before your handler runs.

src/routers/reports.router.ts
reportsRouter.post(
  {
    path: "/api/reports/upload",
    experimental: {
      uploads: {
        type: "single",
        field: "reportFile",
        uploadDir: "reports",
        deleteOnError: true,
      },
    },
  },
  reportsController.processUploadedReport
);
src/modules/user/user.router.ts
import { ArkosRouter, RouteHook } from "arkos";

export const hook: RouteHook = {
  createOne: {
    experimental: {
      uploads: {
        type: "single",
        field: "profilePhoto",
        uploadDir: "user-profiles",
        deleteOnError: true,
      },
    },
  },
};

const userRouter = ArkosRouter();

export default userRouter;

RouteHook is the new name for export const config: RouterConfig. Existing code using the old name still works but will log a deprecation warning. See the Route Hook guide for details.

For all upload types (single, array, fields), nested field notation, and RouteHook examples, see Router Upload.

Global configuration

All fields are optional and deep-merged with defaults.

KeyTypeDescriptionDefault
baseUploadDirstringRoot-relative path where files are saved/uploads
baseRoutestringBase route for standalone upload endpoints/api/uploads
expressStaticobjectOptions passed to express.static() — deep-merged with defaultsSee below
restrictions.imagesobjectmaxCount, maxSize, supportedFilesRegex for images-
restrictions.videosobjectSame shape, for videos
restrictions.documentsobjectSame shape, for documents
restrictions.filesobjectSame shape, for all other types

expressStatic defaults:

{
  maxAge: "1y",
  etag: true,
  lastModified: true,
  dotfiles: "ignore",
  fallthrough: true,
  index: false,
  cacheControl: true,
}

baseRoute and baseUploadDir are independent — changing one does not affect the other.

arkos.config.ts
import { defineConfig } from "arkos";

export default defineConfig({
  fileUpload: {
    baseUploadDir: "/uploads",
    baseRoute: "/api/uploads",
    expressStatic: {
      maxAge: "1d",
    },
    restrictions: {
      images: {
        maxCount: 10,
        maxSize: 5 * 1024 * 1024,
        supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
      },
    },
  },
});
arkos.config.ts
import { ArkosConfig } from "arkos";

const arkosConfig: ArkosConfig = {
  fileUpload: {
    baseUploadDir: "/uploads",
    baseRoute: "/api/uploads",
    expressStatic: {
      maxAge: "1d",
    },
    restrictions: {
      images: {
        maxCount: 10,
        maxSize: 5 * 1024 * 1024,
        supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
      },
    },
  },
};

export default arkosConfig;
src/app.ts
import arkos from "arkos";

arkos.init({
  fileUpload: {
    baseUploadDir: "/uploads",
    baseRoute: "/api/uploads",
    restrictions: {
      images: {
        maxCount: 10,
        maxSize: 5 * 1024 * 1024,
        supportedFilesRegex: /\.(jpg|jpeg|png|webp)$/,
      },
    },
  },
});