Skip to main content

django-supabase-storage

PyPI Downloads

A Django storage backend for Supabase buckets. Store your media and static files directly in Supabase Storage with a simple, drop-in replacement for Django's default storage backend.

Features

  • Direct Supabase Storage Integration: Upload files directly to Supabase buckets
  • No Local Storage: All files are stored in Supabase, not locally
  • Media & Static Storage: Separate storage backends for media and static files
  • Manifest Hashing by Default: Static files use Django manifest hashing for cache busting
  • Django Compatible: Works as a drop-in replacement for Django's default storage
  • Easy Configuration: Simple settings-based configuration
  • Production Ready: Comprehensive error handling and logging

Quick Start

1. Installation

pip install django-supabase-storage

2. Configuration

Add to your Django settings:

import os

# Supabase Configuration
SUPABASE_URL = os.getenv('SUPABASE_URL', 'https://your-project-id.supabase.co')
SUPABASE_KEY = os.getenv('SUPABASE_KEY', 'your-anon-public-key')
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET_NAME", 'your-supabase-bucket-name')

# Storage Configuration (Django 4.2+)
STORAGES = {
    'default': {
        'BACKEND': 'django_supabase_storage.SupabaseMediaStorage',
    },
    'staticfiles': {
        'BACKEND': 'django_supabase_storage.SupabaseStaticStorage',
    },
}

# Optional: disable static manifest hashing (enabled by default)
# SUPABASE_STATIC_MANIFEST = False

# For Django < 4.2, use:
# DEFAULT_FILE_STORAGE = 'django_supabase_storage.SupabaseMediaStorage'
# STATICFILES_STORAGE = 'django_supabase_storage.SupabaseStaticStorage'

3. Set Environment Variables

Create a .env file:

SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_KEY=your-anon-public-key
SUPABASE_BUCKET_NAME=your-supabase-bucket-name

Using Storage Classes

Media Files Storage

For user uploads (images, documents, etc.):

from django.core.files.storage import default_storage

# Save a file to the media bucket
file_path = default_storage.save('uploads/myfile.pdf', file_content)

# Get the public URL
file_url = default_storage.url(file_path)

# Delete a file
default_storage.delete('uploads/myfile.pdf')

# Check if file exists
if default_storage.exists('uploads/myfile.pdf'):
    content = default_storage.open('uploads/myfile.pdf').read()

In Django Models:

from django.db import models

class UserProfile(models.Model):
    avatar = models.ImageField(upload_to='avatars/')
    resume = models.FileField(upload_to='resumes/')
    bio = models.TextField()

In Views:

from django.shortcuts import render
from django.core.files.storage import default_storage

def upload_avatar(request):
    if request.method == 'POST':
        file = request.FILES['avatar']
        # Save automatically goes to Supabase
        file_path = default_storage.save(f'avatars/{request.user.id}/{file.name}', file)
        file_url = default_storage.url(file_path)
        return render(request, 'success.html', {'url': file_url})
    return render(request, 'upload.html')

Static Files Storage

For CSS, JavaScript, and static images:

from django.templatetags.static import static

# In your Django templates
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<script src="{% static 'js/main.js' %}"></script>

Collecting Static Files:

# Django automatically collects static files to Supabase
python manage.py collectstatic

This uploads all your static files to the 'static' bucket in Supabase. By default, static filenames are manifest-hashed for cache busting.

Storage Backends

SupabaseStorage (Base Class)

The base storage class for custom implementations:

from django_supabase_storage import SupabaseStorage

class CustomStorage(SupabaseStorage):
    folder_path = 'custom/'  # All files saved under this folder

SupabaseMediaStorage

Handles user-uploaded media files:

  • Bucket: 'media' (by default)
  • Folder: 'media'
  • Use Case: Profile pictures, documents, user uploads
from django_supabase_storage import SupabaseMediaStorage

# Direct usage
storage = SupabaseMediaStorage()
path = storage.save('documents/myfile.pdf', content)
url = storage.url(path)

SupabaseStaticStorage

Handles static application files:

  • Bucket: 'static' (by default)
  • Folder: 'static'
  • Manifest hashing: enabled by default
  • Use Case: CSS, JavaScript, application images
from django_supabase_storage import SupabaseStaticStorage

# Used automatically with collectstatic
# Or directly:
storage = SupabaseStaticStorage()
path = storage.save('js/app.js', content)
url = storage.url(path)

To opt out of manifest hashing:

SUPABASE_STATIC_MANIFEST = False

Complete Setup Example

Step 1: Create Supabase Buckets

  1. Go to supabase.com and create a project
  2. Navigate to Storage → Buckets
  3. Create bucket named media
  4. Create bucket named static
  5. For each bucket, go to Policies → New Policy → Allow public read access

Step 2: Get Credentials

  1. Go to Settings → API
  2. Copy Project URL (SUPABASE_URL)
  3. Copy anon public key (SUPABASE_KEY)

Step 3: Configure Django

# settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# Supabase Configuration
SUPABASE_URL = os.getenv('SUPABASE_URL')
SUPABASE_KEY = os.getenv('SUPABASE_KEY')
SUPABASE_MEDIA_BUCKET = os.getenv('SUPABASE_MEDIA_BUCKET', 'media')
SUPABASE_STATIC_BUCKET = os.getenv('SUPABASE_STATIC_BUCKET', 'static')

# Storage Configuration
STORAGES = {
    'default': {
        'BACKEND': 'django_supabase_storage.SupabaseMediaStorage',
    },
    'staticfiles': {
        'BACKEND': 'django_supabase_storage.SupabaseStaticStorage',
    },
}

# Optional: disable static manifest hashing (enabled by default)
# SUPABASE_STATIC_MANIFEST = False

# Static files URL for Supabase
STATIC_URL = f'{SUPABASE_URL}/storage/v1/object/public/{SUPABASE_STATIC_BUCKET}/'

# Media files URL for Supabase
MEDIA_URL = f'{SUPABASE_URL}/storage/v1/object/public/{SUPABASE_MEDIA_BUCKET}/'

Step 4: Use in Your Application

# models.py
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    cover_image = models.ImageField(upload_to='articles/')
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    avatar = models.ImageField(upload_to='avatars/')
    bio = models.TextField()

API Reference

SupabaseStorage Methods

# Save a file
path = storage.save('folder/filename.ext', content)

# Open/download a file
file_obj = storage.open('folder/filename.ext', mode='rb')
content = file_obj.read()

# Delete a file
storage.delete('folder/filename.ext')

# Check if file exists
exists = storage.exists('folder/filename.ext')

# List directory
dirs, files = storage.listdir('folder/')

# Get file size in bytes
size = storage.size('folder/filename.ext')

# Get public URL
url = storage.url('folder/filename.ext')

# Get file timestamps
created = storage.get_created_time('folder/filename.ext')
modified = storage.get_modified_time('folder/filename.ext')
accessed = storage.get_accessed_time('folder/filename.ext')

Troubleshooting

SUPABASE_URL or SUPABASE_KEY Not Configured

Error: ValueError: SUPABASE_URL is not configured!

Solution: Set environment variables or add to settings:

SUPABASE_URL = 'https://your-project-id.supabase.co'
SUPABASE_KEY = 'your-anon-public-key'
SUPABASE_BUCKET_NAME = 'your-supabase-bucket-name'

Files Not Uploading

Error: IOError: UPLOAD TO SUPABASE FAILED!

Solutions:

  1. Verify API credentials are correct
  2. Check bucket exists in Supabase
  3. Ensure bucket policies allow uploads
  4. Check network connectivity

Files Not Found

Error: FileNotFoundError: Failed to download file_name

Solutions:

  1. Verify file path is correct
  2. Check bucket name configuration
  3. Ensure file was successfully uploaded

Permission Denied

Error: IOError: Permission denied

Solutions:

  1. Go to Supabase Dashboard → Storage → Buckets
  2. Click the bucket → Policies
  3. Ensure public read access is enabled

Best Practices

  1. Use environment variables for sensitive credentials
  2. Set appropriate bucket policies (public read for media/static)
  3. Organize files with folders (avatars/, uploads/, etc.)
  4. Cache static files in CDN for better performance
  5. Monitor storage usage in Supabase dashboard
  6. Use Django's get_absolute_url() for model file URLs
  7. Use separate buckets for both static and media files

Support

For issues and questions:

License

MIT License - see LICENSE file for details.

Metadata

Release files for django-supabase-storage 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-supabase-storage 1.1.0
File Size Uploaded
django_supabase_storage-1.1.0.tar.gz 27.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-supabase-storage 1.1.0
File Interpreter ABI Platform
django_supabase_storage-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 37.0 kB

Release files / django_supabase_storage-1.1.0.tar.gz

Download URL django_supabase_storage-1.1.0.tar.gz
Size 27.3 kB
Tags Source
SHA-256 checksum
How to use checksums
153a93e9f59830d03421c37e7364c0d67d5404e40053238c24677f9915c1410a
BLAKE2b-256 checksum
How to use checksums
f6c76b4a2d3bca6547909ba8da94fe52b801f5a8a566d8e695cfe8ed9f1ef72c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / django_supabase_storage-1.1.0-py3-none-any.whl

Download URL django_supabase_storage-1.1.0-py3-none-any.whl
Size 9.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6bc51fd5528691d0e48996960b6cab9888a0889349fa3176d6ae53bf64dff6ed
BLAKE2b-256 checksum
How to use checksums
69ec46c23f39b301b1173de34b5c96e0e122f84ae8694344e84353e2eeb7b45c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release history Release notifications | RSS feed

1.1.2

2 release files

1.1.1

2 release files

This release

1.1.0 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page