Skip to main content

Upload API

The upload API (/api/upload) handles image uploads with automatic CDN routing.

Endpoint

POST /api/upload
Content-Type: multipart/form-data

Request

const formData = new FormData();
formData.append("file", imageBlob);
formData.append("filename", "my-design.png"); // optional

const response = await fetch("/api/upload", {
  method: "POST",
  body: formData,
});

const { url } = await response.json();

Response

{
  "url": "https://ucarecdn.com/abc123/my-design.png"
}

Supported Formats

  • PNG (recommended for designs)
  • JPEG
  • WebP
  • GIF (static)

Unified Image Library

The upload API uses lib/image.ts, which supports multiple CDN providers:

Cloudinary

Full-featured CDN with transformations

Uploadcare

Simple, fast uploads

ImageKit

Real-time transformations

Vercel Blob

Integrated with Vercel hosting

Provider Detection

The library auto-detects available providers:
// lib/image.ts
function resolveProvider() {
  // Check for forced provider
  const forced = process.env.IMAGE_PROVIDER?.toLowerCase();
  if (forced === "cloudinary" && hasCloudinary) return "cloudinary";
  if (forced === "uploadcare" && hasUploadcare) return "uploadcare";
  // ...
  
  // Auto-detect based on credentials
  if (hasCloudinary) return "cloudinary";
  if (hasUploadcare) return "uploadcare";
  if (hasImageKit) return "imagekit";
  if (hasVercelBlob) return "vercel-blob";
  
  return "local"; // Fallback to public/uploads
}

Configuration

Cloudinary

CLOUDINARY_CLOUD_NAME=yourcloud
CLOUDINARY_API_KEY=123456789
CLOUDINARY_API_SECRET=abcdefgh

Uploadcare

UPLOADCARE_PUBLIC_KEY=demopublickey

ImageKit

IMAGEKIT_PUBLIC_KEY=public_xxx
IMAGEKIT_PRIVATE_KEY=private_xxx
IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/yourcompany

Vercel Blob

BLOB_READ_WRITE_TOKEN=vercel_blob_xxx

Force Provider

Override auto-detection:
IMAGE_PROVIDER=uploadcare

Image Processing

Alpha Flattening

Transparent PNGs are flattened to a dark matte:
// lib/image.ts
const MATTE_COLOR = { r: 5, g: 5, b: 5 };

async function ensureOpaque(buffer: Buffer): Promise<Buffer> {
  const image = sharp(buffer);
  const metadata = await image.metadata();
  
  if (metadata.hasAlpha) {
    return image
      .flatten({ background: MATTE_COLOR })
      .toBuffer();
  }
  
  return buffer;
}

Preserve Alpha

For print assets, preserve transparency:
await uploadImage(buffer, "print-asset.png", { 
  preserveAlpha: true 
});

URL Transformations

The library provides URL transformation helpers:
import { transformUrl } from "@/lib/image";

// Resize to max 1600px
const optimized = transformUrl(originalUrl, 1600);

// Provider-specific transformations are handled automatically

Cloudinary Transforms

// Input
"https://res.cloudinary.com/demo/image/upload/v123/design.png"

// Output (with width 800)
"https://res.cloudinary.com/demo/image/upload/w_800,c_limit,q_auto/v123/design.png"

Uploadcare Transforms

// Input
"https://ucarecdn.com/abc123/design.png"

// Output (with width 800)
"https://ucarecdn.com/abc123/-/resize/800x/design.png"

Local Fallback

Without CDN credentials, files save to public/uploads/:
async function writeLocalFile(buffer: Buffer, filename: string) {
  const uploadDir = path.join(process.cwd(), "public/uploads");
  await fs.mkdir(uploadDir, { recursive: true });
  
  const filepath = path.join(uploadDir, filename);
  await fs.writeFile(filepath, buffer);
  
  return `/uploads/${filename}`;
}
Local storage is for development only. Use a CDN in production for performance and reliability.

Client-Side Upload

// React component
const [uploading, setUploading] = useState(false);

const handleUpload = async (file: File) => {
  setUploading(true);
  
  const formData = new FormData();
  formData.append("file", file);
  
  const res = await fetch("/api/upload", {
    method: "POST",
    body: formData,
  });
  
  const { url } = await res.json();
  setUploading(false);
  
  return url;
};

Error Handling

StatusErrorDescription
400”No image provided”Missing file in form data
413”File too large”Exceeds size limit (10MB)
415”Unsupported format”Invalid image type
500”Upload failed”CDN provider error

Size Limits

  • Max file size: 10MB
  • Recommended max dimensions: 4096x4096
  • Optimal for generation: 1024x1024

Usage in Generation

Uploaded images can be used as references:
// 1. Upload reference image
const { url: referenceUrl } = await uploadImage(file);

// 2. Use in generation
const response = await fetch("/api/chat", {
  method: "POST",
  body: JSON.stringify({
    prompt: "similar style with blue colors",
    designUrls: [referenceUrl],
  }),
});