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"
}
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
});
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
// 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"
// 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
Status Error Description 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 ],
}),
});