SupaLaunch logo
SUPALAUNCH
ShowcaseDocs
SupaLaunch
HomeDemoDashboardDocumentationBlogShowcaseServices and FrameworksTerms of ServicePrivacy policy
Tools
DB Schema GeneratorStartup Idea Generator
Other Projects
Syntha AICreateChatsCron Expression GeneratorAI Prompts Generator
Contacts
Created with ❤️ by Denisdenis@supalaunch.com
  • Showcase
  • Docs
Blog

Complete Guide to File Uploads with Next.js and Supabase Storage

14 Apr 2025
nextjssupabasestoragefile-upload

Launch your startup with our Supabase + NextJS Starter Kit

SupaLaunch is a SaaS boilerplate built using Supabase and Next.js. It includes authentication, Stripe payments, Postgres database, 20+ TailwindCSS themes, emails, OpenAI API streaming, file storage and more.

  • Save weeks of your time: No need to setup authentication, payments, emails, file storage, etc.
  • Focus on what matters: Spend your time building your product, not boilerplate code.
Get started with SupaLaunch

File uploads with Next.js and Supabase

Building File Upload Functionality with Next.js and Supabase

File uploads are a crucial feature for many web applications, from profile pictures to document management systems. In this guide, we'll explore how to implement secure file uploads in a Next.js application using Supabase Storage.

What You'll Learn

  • Setting up Supabase Storage for your Next.js project
  • Implementing file upload functionality
  • Managing upload permissions with Row Level Security (RLS)
  • Retrieving and displaying uploaded files
  • Handling common upload issues

Prerequisites

Before diving in, make sure you have:

  • A Next.js project set up
  • A Supabase account
  • Basic knowledge of React and JavaScript

Setting Up Supabase Storage

Step 1: Create a Supabase Project

First, create a new project in Supabase from your dashboard if you haven't already.

Step 2: Create a Storage Bucket

Navigate to the Storage section in your Supabase dashboard and create a new bucket. Let's call it documents.

By default, all buckets in Supabase are private. You'll need to set up RLS policies to control access.

Step 3: Configure Storage Permissions

To allow uploads to your bucket, add an RLS policy:

CREATE POLICY "Allow uploads" ON storage.objects 
FOR INSERT WITH CHECK (
  bucket_id = 'documents' AND auth.role() = 'anon'
);

For authenticated users only:

CREATE POLICY "Allow authenticated uploads" ON storage.objects 
FOR INSERT WITH CHECK (
  bucket_id = 'documents' AND auth.role() = 'authenticated'
);

Step 4: Install Required Packages

Add the Supabase client to your Next.js project:

npm install @supabase/supabase-js
# or
yarn add @supabase/supabase-js

For apps using Next.js with authentication:

npm install @supabase/auth-helpers-nextjs
# or
yarn add @supabase/auth-helpers-nextjs

Step 5: Configure Environment Variables

Create a .env.local file in your project root and add:

NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

Implementing File Upload Functionality

Creating a Supabase Client

Create a utility file to initialize your Supabase client:

// lib/supabaseClient.js
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseKey)

Basic File Upload Component

Here's a simple component that handles file uploads:

// components/FileUpload.jsx
import { useState } from 'react'
import { supabase } from '../lib/supabaseClient'

export default function FileUpload() {
  const [uploading, setUploading] = useState(false)
  const [filePath, setFilePath] = useState(null)
  const [error, setError] = useState(null)

  const handleFileChange = async (event) => {
    try {
      setUploading(true)
      setError(null)
      
      const file = event.target.files[0]
      
      if (!file) return
      
      // Upload file to Supabase
      const { data, error } = await supabase.storage
        .from('documents')
        .upload(`public/${Date.now()}-${file.name}`, file, {
          cacheControl: '3600',
          upsert: false
        })
        
      if (error) throw error
      
      setFilePath(data.path)
      alert('File uploaded successfully!')
      
    } catch (error) {
      setError(error.message)
      alert('Error uploading file: ' + error.message)
    } finally {
      setUploading(false)
    }
  }

  return (
    <div>
      <h2>Upload File</h2>
      <input
        type="file"
        disabled={uploading}
        onChange={handleFileChange}
      />
      
      {uploading && <p>Uploading...</p>}
      {error && <p className="error">Error: {error}</p>}
      {filePath && <p>File uploaded to: {filePath}</p>}
    </div>
  )
}

Using the Component in a Page

// pages/upload.js
import FileUpload from '../components/FileUpload'

export default function UploadPage() {
  return (
    <div className="container">
      <h1>File Upload Example</h1>
      <FileUpload />
    </div>
  )
}

Advanced Upload Features

File Type Validation

Add file type validation to ensure only desired file types are uploaded:

const handleFileChange = async (event) => {
  try {
    const file = event.target.files[0]
    
    // Validate file type
    const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
    if (!allowedTypes.includes(file.type)) {
      throw new Error('File type not supported. Please upload JPEG, PNG, or PDF.')
    }
    
    // Continue with upload...
  } catch (error) {
    setError(error.message)
  }
}

File Size Validation

Prevent uploading large files:

// Check file size (limit to 5MB)
if (file.size > 5 * 1024 * 1024) {
  throw new Error('File size exceeds 5MB limit.')
}

Progress Indicator

For a better user experience, add a progress indicator:

import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabaseClient'

export default function FileUploadWithProgress() {
  const [uploading, setUploading] = useState(false)
  const [progress, setProgress] = useState(0)
  
  const handleFileChange = async (event) => {
    try {
      const file = event.target.files[0]
      setUploading(true)
      setProgress(0)
      
      // Simulate progress (Supabase doesn't provide upload progress)
      const interval = setInterval(() => {
        setProgress((prevProgress) => {
          if (prevProgress >= 95) {
            clearInterval(interval)
            return prevProgress
          }
          return prevProgress + 5
        })
      }, 100)
      
      const { data, error } = await supabase.storage
        .from('documents')
        .upload(`public/${Date.now()}-${file.name}`, file)
      
      clearInterval(interval)
      
      if (error) throw error
      
      setProgress(100)
      // Handle success
      
    } catch (error) {
      // Handle error
    } finally {
      setUploading(false)
    }
  }
  
  return (
    <div>
      <input type="file" onChange={handleFileChange} disabled={uploading} />
      {uploading && (
        <div className="progress-bar">
          <div className="progress" style={{ width: `${progress}%` }}></div>
          <span>{progress}%</span>
        </div>
      )}
    </div>
  )
}

Retrieving and Displaying Files

Getting a Public URL

import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabaseClient'

export default function FileDisplay({ filePath }) {
  const [fileUrl, setFileUrl] = useState(null)
  
  useEffect(() => {
    if (filePath) {
      const { data } = supabase.storage
        .from('documents')
        .getPublicUrl(filePath)
      
      setFileUrl(data.publicUrl)
    }
  }, [filePath])
  
  return fileUrl ? (
    <div>
      <h3>Uploaded File</h3>
      {fileUrl.match(/\.(jpeg|jpg|gif|png)$/) ? (
        <img src={fileUrl} alt="Uploaded file" style={{ maxWidth: '100%' }} />
      ) : (
        <a href={fileUrl} target="_blank" rel="noopener noreferrer">
          View File
        </a>
      )}
    </div>
  ) : null
}

Creating a Signed URL for Secure Access

For private files, create temporary signed URLs:

const getSignedUrl = async (filePath) => {
  const { data, error } = await supabase.storage
    .from('documents')
    .createSignedUrl(filePath, 60) // URL valid for 60 seconds
    
  if (error) {
    console.error('Error creating signed URL:', error)
    return null
  }
  
  return data.signedUrl
}

Using with Next.js App Router

For Next.js 13+ with the App Router, create a client component:

// app/components/FileUploader.jsx
'use client'

import { useState } from 'react'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export default function FileUploader() {
  const [uploading, setUploading] = useState(false)
  const supabase = createClientComponentClient()
  
  const handleUpload = async (event) => {
    try {
      setUploading(true)
      const file = event.target.files[0]
      
      const { data, error } = await supabase.storage
        .from('documents')
        .upload(`public/${Date.now()}-${file.name}`, file)
        
      if (error) throw error
      
      // Handle success
      
    } catch (error) {
      // Handle error
    } finally {
      setUploading(false)
    }
  }
  
  return (
    <input type="file" onChange={handleUpload} disabled={uploading} />
  )
}

Multiple File Uploads

Handle multiple file uploads with this example:

import { useState } from 'react'
import { supabase } from '../lib/supabaseClient'

export default function MultiFileUpload() {
  const [uploading, setUploading] = useState(false)
  const [uploadedFiles, setUploadedFiles] = useState([])
  
  const handleMultipleFiles = async (event) => {
    try {
      setUploading(true)
      const files = Array.from(event.target.files)
      
      const uploads = files.map(async (file) => {
        const { data, error } = await supabase.storage
          .from('documents')
          .upload(`public/${Date.now()}-${file.name}`, file)
          
        if (error) throw error
        return data.path
      })
      
      const results = await Promise.all(uploads)
      setUploadedFiles(results)
      
    } catch (error) {
      console.error('Error uploading files:', error)
    } finally {
      setUploading(false)
    }
  }
  
  return (
    <div>
      <input 
        type="file" 
        multiple 
        onChange={handleMultipleFiles} 
        disabled={uploading} 
      />
      
      {uploading && <p>Uploading multiple files...</p>}
      
      {uploadedFiles.length > 0 && (
        <div>
          <h3>Uploaded Files:</h3>
          <ul>
            {uploadedFiles.map((path, index) => (
              <li key={index}>{path}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  )
}

Common Issues and Solutions

CORS Issues

If you encounter CORS errors, ensure your Supabase project has the correct CORS configuration. Go to the Auth settings in your Supabase dashboard and add your domain to the list of allowed domains.

File Size Limits

Supabase has a default file size limit of 50MB. For larger files, consider splitting them into chunks or using a different solution.

File Type Restrictions

Always validate file types on both client and server sides for security:

// Client-side validation
const isValidFileType = (file) => {
  const validTypes = ['image/jpeg', 'image/png', 'application/pdf']
  return validTypes.includes(file.type)
}

Conclusion

Implementing file uploads with Next.js and Supabase Storage is straightforward and secure. With the examples provided in this guide, you can quickly add file upload functionality to your applications while maintaining control over access permissions.

For more complex scenarios, consider:

  • Adding a drag-and-drop interface
  • Implementing file compression before upload
  • Adding file previews
  • Building a file management system with delete and update capabilities

The combination of Next.js and Supabase provides a powerful foundation for building robust file upload solutions for your web applications.

Happy coding!