Upload SDK

Upload Assets

Define reusable rules and destinations for each type of upload.

An upload asset is a named configuration for a type of upload.

It defines:

  • Where the upload should go
  • How the storage key should be organized
  • Which reported file types are accepted
  • The maximum reported file size
  • How long the prepared upload remains valid
  • Metadata included with the upload
const assets = defineAssets(storageProfiles, {
  avatar: {
    keyPrefix: "avatars",
  },

  document: {
    keyPrefix: "documents",
  },
});

The asset name is passed to prepareUpload():

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

Asset names

Asset names are chosen by your application.

Use names that describe the upload's purpose:

const assets = defineAssets(storageProfiles, {
  avatar: {
    keyPrefix: "avatars",
  },

  resume: {
    keyPrefix: "resumes",
  },

  productImage: {
    keyPrefix: "products/images",
  },
});

This keeps server code easy to understand:

await uploader.prepareUpload("productImage", input);

Complete example

const assets = defineAssets(storageProfiles, {
  avatar: {
    storageProfile: "optimizedImages",

    keyPrefix: "users/avatars",

    accept: {
      mimeTypes: ["image/png", "image/jpeg"],
      extensions: [".png", ".jpg", ".jpeg"],
    },

    limits: {
      maxFileSize: {
        value: 2,
        unit: "MB",
      },
    },

    expiresIn: {
      value: 5,
      unit: "minutes",
    },

    metadata: {
      purpose: "avatar",
      temporary: true,
    },
  },
});

Asset properties

PropertyPurpose
storageProfileSelects the storage profile
keyPrefixSets the path before the generated filename
accept.mimeTypesAllows declared MIME types
accept.extensionsAllows filename extensions
limits.maxFileSizeSets the maximum reported file size
expiresInSets how long the prepared upload is valid
metadataAdds static provider metadata

storageProfile

storageProfile selects the named provider configuration used by the asset.

avatar: {
  storageProfile: "optimizedImages",
  keyPrefix: "avatars",
}

The name must exist in storageProfiles.

const storageProfiles = defineStorageProfiles({
  optimizedImages: imageKit(/* ... */),
});

When storageProfile is omitted, the asset uses the uploader's default profile.

attachment: {
  keyPrefix: "attachments",
}

See Storage Profiles for profile selection and multiple destinations.

keyPrefix

keyPrefix controls the path before the generated filename.

avatar: {
  keyPrefix: "users/avatars",
}

A generated key may look like:

users/avatars/profile-550e8400-e29b-41d4-a716-446655440000.png

Use prefixes that describe how files are organized:

avatar: {
  keyPrefix: "users/avatars",
}

invoice: {
  keyPrefix: "billing/invoices",
}

productImage: {
  keyPrefix: "catalog/products/images",
}

Upload SDK sanitizes the prefix before using it.

accept.mimeTypes

accept.mimeTypes defines which declared content types are allowed.

accept: {
  mimeTypes: ["image/png", "image/jpeg"],
}

A MIME wildcard can allow a whole group:

accept: {
  mimeTypes: ["image/*"],
}

The SDK checks the value reported by the client.

It does not inspect the file bytes to confirm the actual type.

accept.extensions

accept.extensions defines which filename extensions are allowed.

accept: {
  extensions: [".png", ".jpg", ".jpeg"],
}

The SDK extracts the extension from the reported filename.

{
  filename: "profile.png",
}

A filename such as profile.exe is rejected when .exe is not allowed.

The declared content type does not override the extension rule.

Using MIME types and extensions together

You can require both values to be allowed:

accept: {
  mimeTypes: ["image/png", "image/jpeg"],
  extensions: [".png", ".jpg", ".jpeg"],
}
FilenameDeclared content typeResult
avatar.pngimage/pngAccepted
avatar.exeimage/pngRejected
avatar.pngapplication/pdfRejected

These checks use client-reported information. They do not prove what the file actually contains.

See Upload Safety for the full trust model.

limits.maxFileSize

limits.maxFileSize sets the largest reported file size that the asset accepts.

limits: {
  maxFileSize: {
    value: 2,
    unit: "MB",
  },
}

Supported units are:

"KB" | "MB" | "GB" | "TB"

When prepareUpload() is called, the reported size is compared with this limit.

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

If the reported size is too large, the request is rejected before signing.

Providers that support upload-size conditions can also include the resolved limit in the signed upload.

maxFiles and concurrency

The asset type currently includes maxFiles and concurrency, but prepareUpload() does not enforce them.

Do not rely on these fields to control batch size or simultaneous uploads.

Handle those limits in your application.

expiresIn

expiresIn controls how long the prepared upload remains valid.

expiresIn: {
  value: 5,
  unit: "minutes",
}

Supported units are:

"seconds" | "minutes" | "hours";

Upload SDK converts the duration and passes it to the selected provider.

Provider-specific minimum and maximum values may differ.

expiresIn: {
  value: 30,
  unit: "seconds",
}
expiresIn: {
  value: 10,
  unit: "minutes",
}

Once the target expires, the client must request a new prepared upload.

metadata

metadata adds static information to the prepared upload.

metadata: {
  purpose: "avatar",
  temporary: true,
  version: 1,
}

Metadata values can be strings, numbers, or booleans.

Providers handle metadata differently:

  • AWS S3 uses object metadata.
  • ImageKit uses custom metadata.

Metadata is defined on the asset. The current prepareUpload() input does not accept per-upload metadata overrides.

Sharing profiles between assets

Several assets can use the same profile while keeping different paths and rules.

const assets = defineAssets(storageProfiles, {
  avatar: {
    keyPrefix: "avatars",
    accept: {
      mimeTypes: ["image/png", "image/jpeg"],
      extensions: [".png", ".jpg", ".jpeg"],
    },
  },

  document: {
    keyPrefix: "documents",
    accept: {
      mimeTypes: ["application/pdf"],
      extensions: [".pdf"],
    },
  },

  attachment: {
    keyPrefix: "attachments",
  },
});

When these assets use the same default profile, they share provider configuration without sharing upload rules.

Routing assets to different profiles

Assets can also use different storage destinations:

const assets = defineAssets(storageProfiles, {
  avatar: {
    storageProfile: "optimizedImages",
    keyPrefix: "avatars",
  },

  legalDocument: {
    storageProfile: "privateDocuments",
    keyPrefix: "legal",
  },
});

The selected asset decides where the upload goes.

The client upload flow remains the same.

Choosing assets safely

Your server should choose the asset after authenticating and authorizing the request.

await uploader.prepareUpload("avatar", input);

Avoid passing an unrestricted client value directly to prepareUpload():

await uploader.prepareUpload(body.assetName, input);

Different assets may provide access to different profiles, size limits, expiration times, or storage paths.

Only allow assets that the current user is authorized to use.

Type-safe asset names

Asset names are preserved by TypeScript.

const assets = defineAssets(storageProfiles, {
  avatar: {
    keyPrefix: "avatars",
  },

  document: {
    keyPrefix: "documents",
  },
});

Valid names can be passed to the uploader:

await uploader.prepareUpload("avatar", input);
await uploader.prepareUpload("document", input);

An unknown name should produce a TypeScript error:

await uploader.prepareUpload("missingAsset", input);

Next steps

On this page