Skip to content

Repository files navigation

Upload SDK

version follow

Prepare direct browser uploads from your server without tying your application to one storage provider.

const preparedUpload = await uploader.prepareUpload("avatar", {
  filename: "profile.png",
  contentType: "image/png",
  size: 120_000,
});

@marinedotsh/upload-sdk validates client-reported file information, creates safe storage keys, and returns short-lived multipart upload targets for AWS S3 and ImageKit.

  • Typed storage profiles and upload assets
  • Provider-agnostic prepareUpload() flow
  • Filename, MIME type, extension, and size validation
  • Collision-resistant storage keys
  • Signed S3 POST policies and ImageKit V2 upload tokens
  • Consistent SDK errors for validation and provider failures

Installation

pnpm add @marinedotsh/upload-sdk

Other package managers:

npm install @marinedotsh/upload-sdk
yarn add @marinedotsh/upload-sdk
bun add @marinedotsh/upload-sdk

Setup

Create your uploader in server-only code:

import {
  createUploader,
  defineAssets,
  defineStorageProfiles,
} from "@marinedotsh/upload-sdk";
import { awsS3 } from "@marinedotsh/upload-sdk/providers";

const storageProfiles = defineStorageProfiles({
  userUploads: awsS3({
    bucket: process.env.AWS_BUCKET!,
    region: process.env.AWS_REGION!,
    credentials: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    },
  }),
});

const assets = defineAssets(storageProfiles, {
  avatar: {
    keyPrefix: "avatars",
    accept: {
      mimeTypes: ["image/png", "image/jpeg"],
      extensions: [".png", ".jpg", ".jpeg"],
    },
    limits: {
      maxFileSize: {
        value: 2,
        unit: "MB",
      },
    },
    expiresIn: {
      value: 5,
      unit: "minutes",
    },
  },
});

export const uploader = createUploader({
  storageProfiles,
  defaultStorageProfile: "userUploads",
  assets,
});

After authenticating and authorizing a request, prepare the upload:

const preparedUpload = await uploader.prepareUpload("avatar", {
  filename: "profile.png",
  contentType: "image/png",
  size: 120_000,
});

Return preparedUpload to the browser. The SDK response contains a multipart target:

type PrepareUploadOutput = {
  strategy: "multipart";
  url: string;
  method: "POST";
  headers: Record<string, string>;
  fields: Record<string, string>;
  key: string;
  expiresAt: string;
};

Browser upload

Send the file directly to the provider with the returned form fields:

import type { PrepareUploadOutput } from "@marinedotsh/upload-sdk";

async function uploadToProvider(
  file: File,
  preparedUpload: PrepareUploadOutput,
): Promise<string> {
  const formData = new FormData();

  for (const [name, value] of Object.entries(preparedUpload.fields)) {
    formData.append(name, value);
  }

  formData.append("file", file);

  const response = await fetch(preparedUpload.url, {
    method: preparedUpload.method,
    headers: preparedUpload.headers,
    body: formData,
  });

  if (!response.ok) {
    throw new Error("The file could not be uploaded.");
  }

  return preparedUpload.key;
}

Do not manually set the multipart Content-Type header. The browser adds the boundary.

Providers

AWS S3

import { awsS3 } from "@marinedotsh/upload-sdk/providers";

awsS3({
  bucket: process.env.AWS_BUCKET!,
  region: process.env.AWS_REGION!,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
    sessionToken: process.env.AWS_SESSION_TOKEN,
  },
});

S3 uploads use a signed POST policy. The generated policy includes the key, content type, expiration, metadata fields, and the configured size limit when present.

ImageKit

import { imageKit } from "@marinedotsh/upload-sdk/providers";

imageKit({
  publicKey: process.env.IMAGEKIT_PUBLIC_KEY!,
  privateKey: process.env.IMAGEKIT_PRIVATE_KEY!,
});

ImageKit uploads use a V2 upload JWT. The SDK maps asset rules into ImageKit upload checks for file size and MIME type where supported.

Validation model

Upload SDK validates information reported by the client before it asks a provider to sign anything. This keeps obvious invalid uploads out of the signing path, but it does not read or inspect the actual file bytes.

Your application is still responsible for authentication, authorization, upload completion checks, trusted database records, content inspection when required, and cleanup of abandoned uploads.

API reference

import {
  createUploader,
  defineAssets,
  defineStorageProfiles,
  defineUploadConfig,
  isUploadSDKError,
  UploadSDKError,
} from "@marinedotsh/upload-sdk";
import { awsS3, imageKit } from "@marinedotsh/upload-sdk/providers";

Common types:

import type {
  AssetUploadConfig,
  Duration,
  FileSizeLimit,
  PrepareUploadOutput,
  StorageProvider,
  UploadMetadata,
  UploadSDKErrorCode,
} from "@marinedotsh/upload-sdk";

Repository

This repo is a pnpm workspace managed with Turbo.

Path Description
packages/upload-sdk Published SDK package
packages/upload-sdk/src/providers AWS S3 and ImageKit provider implementations
packages/upload-sdk/src/validation Input, key, filename, duration, and size validation
apps/demo Next.js demo application
apps/docs Fumadocs documentation site
docs Repository-level process docs

Development

pnpm install
pnpm dev
pnpm typecheck
pnpm test
pnpm build
pnpm check

Package publish checks:

pnpm --filter @marinedotsh/upload-sdk publish:check

Documentation

License

MIT

About

A TypeScript SDK for validating files and preparing secure direct uploads to AWS S3, ImageKit, and other storage providers.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages