Loading...
Loading...
> Upload and store files securely with automatic provider detection.
Cloud storage lets you save files (images, documents, videos) on remote servers instead of your own. This is essential when users need to upload profile pictures, documents, or any other files. Think of it like Google Drive or Dropbox for your app - files are stored securely in the cloud and can be accessed from anywhere.
DESC: R2 is the recommended provider due to zero egress fees
1$# .env.local23$# Cloudflare R2 Configuration4$CLOUDFLARE_R2_ACCESS_KEY_ID="your-access-key-id"5$CLOUDFLARE_R2_SECRET_ACCESS_KEY="your-secret-access-key"6$CLOUDFLARE_R2_BUCKET="my-saas-uploads"7$CLOUDFLARE_R2_ENDPOINT="https://your-account-id.r2.cloudflarestorage.com"89$# Optional: Public URL for the bucket10$CLOUDFLARE_R2_PUBLIC_URL="https://uploads.yourdomain.com"
DESC: If you prefer S3 or already use AWS
1$# .env.local23$# AWS S3 Configuration4$AWS_S3_ACCESS_KEY_ID="your-access-key-id"5$AWS_S3_SECRET_ACCESS_KEY="your-secret-access-key"6$AWS_S3_BUCKET="my-saas-uploads"7$AWS_S3_REGION="us-east-1"
Use the upload utility to store files
1import { uploadFile, getStorageProvider } from "@/lib/storage/uploads";23// Check which provider is being used4const provider = getStorageProvider();567// Upload a file8const result = await uploadFile(file, {9 folder: "avatars", // Optional: organize by folder10 organizationId: org.id, // Optional: for access control11 allowedTypes: ["image/jpeg", "image/png"], // Optional: restrict types12 maxSize: 5 * 1024 * 1024, // Optional: 5MB limit13});1415if (result.success) {161718} else {1920}
Validate files before uploading
1import { validateFile } from "@/lib/storage/uploads";23// Validate file size and type4const validation = validateFile(file, {5 maxSize: 10 * 1024 * 1024, // 10MB6 allowedTypes: [7 "image/jpeg",8 "image/png",9 "image/webp",10 "application/pdf",11 ],12});1314if (!validation.valid) {15 // Handle validation errors16 console.error(validation.error);17 // "File too large. Maximum size is 10MB"18 // "File type not allowed. Allowed: image/jpeg, image/png..."19}
Handle file uploads in your API
1// src/app/api/upload/route.ts23import { auth } from "@/lib/auth";4import { uploadFile } from "@/lib/storage/uploads";5import { NextResponse } from "next/server";67export async function POST(request: Request) {8 const session = await auth();9 if (!session?.user) {10 return NextResponse.json({ error: "Unauthorized" }, { status: 401 });11 }1213 // Get file from form data14 const formData = await request.formData();15 const file = formData.get("file") as File;1617 if (!file) {18 return NextResponse.json({ error: "No file provided" }, { status: 400 });19 }2021 // Upload to storage22 const result = await uploadFile(file, {23 folder: `users/${session.user.id}`,24 allowedTypes: ["image/jpeg", "image/png"],25 maxSize: 5 * 1024 * 1024, // 5MB26 });2728 if (!result.success) {29 return NextResponse.json({ error: result.error }, { status: 400 });30 }3132 // Save URL to database if needed33 await prisma.user.update({34 where: { id: session.user.id },35 data: { avatarUrl: result.url },36 });3738 return NextResponse.json({39 url: result.url,40 message: "File uploaded successfully",41 });42}
React component for file uploads
1"use client";23import { useState } from "react";4import { Button } from "@/components/ui/button";56export function FileUploader() {7 const [uploading, setUploading] = useState(false);89 const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {10 const file = e.target.files?.[0];11 if (!file) return;1213 setUploading(true);1415 const formData = new FormData();16 formData.append("file", file);1718 try {19 const response = await fetch("/api/upload", {20 method: "POST",21 body: formData,22 });2324 const data = await response.json();2526 if (!response.ok) {27 alert(data.error);28 return;29 }3031 alert("Uploaded! URL: " + data.url);32 } catch (_) {33 alert("Upload failed");34 } finally {35 setUploading(false);36 }37 };3839 return (40 <div>41 <input42 type="file"43 onChange={handleUpload}44 disabled={uploading}45 accept="image/jpeg,image/png"46 />47 {uploading && <p>Uploading...</p>}48 </div>49 );50}
Fabrk automatically detects which storage provider you have configured and uses it. This means you can start with local storage during development and switch to cloud in production without changing your code.
Cloudflare R2
Used if R2 environment variables are set
AWS S3
Used if only S3 environment variables are set
Local Storage
Fallback when no cloud provider is configured
Recommended
Industry Standard
Development Only
Cloudflare R2: $0.015/GB/month for storage, zero egress fees. First 10GB free.
AWS S3: ~$0.023/GB/month storage + $0.09/GB egress. Egress fees can add up quickly.
By default, Fabrk validates files up to 10MB. You can change this in your upload options. For larger files (videos, etc.), consider using direct-to-storage uploads with presigned URLs.
Fabrk uses one provider at a time based on which env vars are set. R2 takes priority if both are configured. If you need multi-provider support, you'd need to customize the storage module.
Use the deleteFile(key) function from the storage module. The key is returned when you upload a file. Make sure to also remove the file reference from your database.