Skip to main content

Overview

ThinkEx provides flexible file upload capabilities with support for both Supabase Storage and local filesystem storage. The system includes:
  • Direct-to-storage uploads (bypassing serverless limits)
  • Signed URL generation for large files
  • Automatic fallback for small files
  • Support for files up to 200MB

Upload Methods

For large files, use the two-step direct upload flow to bypass the 4.5MB serverless function body limit.

Step 1: Request Signed URL

POST /api/upload-url
Content-Type: application/json

{
  "filename": "document.pdf",
  "contentType": "application/pdf"
}
filename
string
required
Original filename (will be sanitized and made unique)
contentType
string
MIME type of the file (e.g., application/pdf, image/png)
mode
string
Storage mode: supabase or local
signedUrl
string
Signed upload URL (Supabase mode only, valid for 5 minutes)
publicUrl
string
Public URL for accessing the file after upload
path
string
Storage path/filename (unique)

Step 2: Upload to Signed URL

PUT <signedUrl>
Content-Type: application/pdf

<binary file data>
The client uploads the file directly to Supabase Storage using the signed URL. No response body is needed - a 200 status indicates success.

Simple Upload

For files under 50MB, you can use the simple upload endpoint:
POST /api/upload-file
Content-Type: multipart/form-data

file=<binary data>
file
file
required
File to upload (max 50MB)
success
boolean
Whether the upload succeeded
url
string
Public URL of the uploaded file
filename
string
Generated unique filename

Client SDK

The recommended way to upload files from the client is using the uploadFileDirect utility:
import { uploadFileDirect } from '@/lib/uploads/client-upload';

const file = document.querySelector('input[type="file"]').files[0];

try {
  const result = await uploadFileDirect(file, { log: true });
  console.log('File uploaded:', result.url);
  console.log('Filename:', result.filename);
} catch (error) {
  console.error('Upload failed:', error.message);
}
The SDK automatically:
  • Validates file size (200MB limit)
  • Requests a signed URL
  • Falls back to simple upload for local storage
  • Falls back to /api/upload-file if direct upload fails for small files

Storage Configuration

Configure storage backend via environment variables:

Supabase Storage (Default)

STORAGE_TYPE=supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
  • Files stored in file-upload bucket
  • Requires Supabase project with storage enabled
  • Bucket must allow public reads or use signed URLs

Local Storage

STORAGE_TYPE=local
UPLOADS_DIR=/path/to/uploads  # Optional, defaults to ./uploads
NEXT_PUBLIC_APP_URL=http://localhost:3000
  • Files stored in local filesystem
  • Directory created automatically if it doesn’t exist
  • Files accessible via /api/files/:filename

File Naming

All uploaded files are automatically renamed with a unique identifier:
<timestamp>-<random>-<sanitized-original-name>
Example: 1709123456789-abc123def45-my_document.pdf
  • Special characters replaced with underscores
  • Timestamp prevents conflicts
  • Random string adds extra uniqueness

Error Responses

error
string
Error message describing what went wrong
details
string
Additional error details (when available)

Common Errors

StatusErrorSolution
401UnauthorizedUser must be authenticated
400No file providedInclude file in form data
400File size exceeds limitReduce file size or split into multiple uploads
500Server configuration errorCheck environment variables
500Failed to upload fileCheck storage backend connectivity

Rate Limits

The upload endpoints have the following limits:
  • Max file size: 50MB (simple upload), 200MB (direct upload)
  • Max duration: 30 seconds (simple upload), 10 seconds (signed URL generation)
  • Concurrent uploads: Limited by client

Security

  • All endpoints require authentication via Better Auth session
  • File URLs are validated to prevent SSRF attacks
  • Filenames are sanitized to prevent path traversal
  • Supabase service role key never exposed to client
  • Signed URLs expire after 5 minutes

See Also

Build docs developers (and LLMs) love