Skip to main content

Upload API

Base path: /api/upload
File: ayts-api/src/routes/upload.ts
Auth required: Yes
Storage: Cloudflare R2 bucket ayts

POST /api/upload/image

Upload a single image to R2.

Request: multipart/form-data

file: {image file}

Accepted MIME types:

  • image/jpeg
  • image/png
  • image/webp
  • image/gif

Max file size: 5 MB

Response 200:

{
"success": true,
"url": "https://pub-cfc3656f8fe64c31862884b05a8159ad.r2.dev/images/uuid-filename.jpg",
"key": "images/uuid-filename.jpg"
}

Response 400: Invalid file type or size exceeded.


POST /api/upload/bulk

Upload multiple images at once (product gallery).

Request: multipart/form-data

files: {image1, image2, image3}

Response 200:

{
"success": true,
"urls": [
"https://pub-cfc3656f8fe64c31862884b05a8159ad.r2.dev/...",
"https://pub-cfc3656f8fe64c31862884b05a8159ad.r2.dev/..."
]
}

R2 Configuration

# wrangler.toml
[[r2_buckets]]
binding = "AYTS_STORAGE_BUCKET"
bucket_name = "ayts"

The public R2 URL prefix: https://pub-cfc3656f8fe64c31862884b05a8159ad.r2.dev

note

The R2 bucket must exist in your Cloudflare dashboard. Verify at: Cloudflare Dashboard → R2 → Buckets → ayts


Image Processing

Uploaded images are processed by Sharp before storage:

  • Resize to max 1200px wide (maintaining aspect ratio)
  • Convert to WebP for smaller file size
  • Strip EXIF metadata for privacy

Frontend Upload Component

The SingleMediaUpload component in ayts-admin/components/single-media-upload.tsx handles the upload flow:

  1. User drags/drops or selects image
  2. Client validates file type + size
  3. POST /api/upload/image
  4. On success, the returned URL is saved to the form field
  5. Form submits with the R2 URL
// Example usage
<SingleMediaUpload
value={imageUrl}
onChange={(url) => setValue('imageUrl', url)}
label="Product Image"
/>