Docs
Features

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

BackendBest for
LocalDevelopment, self-hosted deployments
ImgBBServerless / cloud deployments (Vercel, etc.)

Configuration

# .env
UPLOAD_PROVIDER="local"   # local | imgbb

Local 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.jpg

Local storage does not persist across serverless deployments (e.g. Vercel). Use ImgBB or another cloud provider in those environments.

ImgBB

  1. Create a free account at imgbb.com
  2. Generate an API key from https://api.imgbb.com
  3. 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

RuleValue
Accepted typesimage/jpeg, image/png, image/gif, image/webp
Max file size5 MB
Enforced atServer (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

  1. Create src/lib/upload/your-provider.ts implementing the IUploadProvider interface:
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()
  1. 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
  }
}
  1. Set UPLOAD_PROVIDER="yours" in .env.

Next Steps

File Upload | Tikship