File Upload
Flexible image upload supporting local filesystem and ImgBB cloud storage — switchable via environment variable, with avatar upload and rich-text editor drag-and-drop support.
Storage Backends
| Backend | Best for |
|---|---|
| Local | Development, self-hosted deployments |
| ImgBB | Serverless / cloud deployments (Vercel, etc.) |
Configuration
# .env
UPLOAD_PROVIDER="local" # local | imgbbLocal Storage
Files are saved to the server filesystem under public/upload/. No additional configuration is required.
UPLOAD_PROVIDER="local"
# Files accessible at: https://yourdomain.com/upload/YYYY-MM-DD/filename.jpgLocal storage does not persist across serverless deployments (e.g. Vercel). Use ImgBB or another cloud provider in those environments.
ImgBB
- Create a free account at imgbb.com
- Generate an API key from https://api.imgbb.com
- Add it to your environment:
UPLOAD_PROVIDER="imgbb"
IMGBB_API_KEY="your-api-key"Uploaded images are hosted on ImgBB's CDN and returned as permanent URLs.
Upload API
All uploads go through POST /api/upload. The endpoint:
- Validates file type (JPEG, PNG, GIF, WebP)
- Validates file size (5 MB limit)
- Routes to the configured storage backend
- Returns a
{ url }response
// Client-side upload helper
async function uploadImage(file: File): Promise<string> {
const form = new FormData()
form.append('file', file)
const res = await fetch('/api/upload', { method: 'POST', body: form })
const { data } = await res.json()
return data.url
}Avatar Upload
Users can upload a profile picture from their account settings.
- Accepted types: JPEG, PNG, GIF, WebP
- Max size: 5 MB
- Validation: enforced server-side before storage
import { AvatarUpload } from '@/components/auth/register/AvatarUpload'
<AvatarUpload
currentUrl={user.avatar}
onUpload={(url) => updateProfile({ avatar: url })}
/>The component shows a preview of the current avatar, opens a file picker on click, uploads on selection, and calls onUpload with the resulting URL.
Editor Image Upload
The TipTap rich-text editor supports image insertion via:
- Drag and drop — drop an image file anywhere onto the editor
- File picker — click the image toolbar button and select a file
- URL — paste or type an external image URL
When a file is dropped or selected, the editor calls the upload API automatically and inserts the returned URL as an <img> node.
// Editor configuration (already set up in src/components/editor/RichTextEditor.tsx)
const editor = useEditor({
extensions: [
// ...
ImageUpload.configure({
uploadFn: uploadImage, // reuses the same upload helper
}),
],
})Validation Rules
| Rule | Value |
|---|---|
| Accepted types | image/jpeg, image/png, image/gif, image/webp |
| Max file size | 5 MB |
| Enforced at | Server (API route) + Client (pre-flight check) |
Server-side validation is performed inside the upload API route (src/app/api/upload/).
Adding a New Storage Backend
- Create
src/lib/upload/your-provider.tsimplementing theIUploadProviderinterface:
import { IUploadProvider, UploadResult } from '@/types/upload'
export class YourProvider implements IUploadProvider {
async uploadImage(file: File | Blob): Promise<UploadResult> {
// your implementation
return { success: true, url: 'https://...' }
}
}
export const yourProvider = new YourProvider()- Register it in
src/lib/upload/service.ts:
import { yourProvider } from '@/lib/upload/your-provider'
function getDefaultProvider(): IUploadProvider {
const uploadType = process.env.UPLOAD_PROVIDER || 'local'
switch (uploadType.toLowerCase()) {
case 'yours': return yourProvider
case 'imgbb': return imgBBProvider
default: return localProvider
}
}- Set
UPLOAD_PROVIDER="yours"in.env.
Next Steps
- Content Management — editor image upload in context
- Admin Panel — product and avatar image upload