django-supabase-storage
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
- Go to supabase.com and create a project
- Navigate to Storage → Buckets
- Create bucket named
media - Create bucket named
static - For each bucket, go to Policies → New Policy → Allow public read access
Step 2: Get Credentials
- Go to Settings → API
- Copy Project URL (SUPABASE_URL)
- 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:
- Verify API credentials are correct
- Check bucket exists in Supabase
- Ensure bucket policies allow uploads
- Check network connectivity
Files Not Found
Error: FileNotFoundError: Failed to download file_name
Solutions:
- Verify file path is correct
- Check bucket name configuration
- Ensure file was successfully uploaded
Permission Denied
Error: IOError: Permission denied
Solutions:
- Go to Supabase Dashboard → Storage → Buckets
- Click the bucket → Policies
- Ensure public read access is enabled
Best Practices
- Use environment variables for sensitive credentials
- Set appropriate bucket policies (public read for media/static)
- Organize files with folders (avatars/, uploads/, etc.)
- Cache static files in CDN for better performance
- Monitor storage usage in Supabase dashboard
- Use Django's
get_absolute_url()for model file URLs - Use separate buckets for both static and media files
Support
For issues and questions:
- Open an issue on GitHub
- See INSTALLATION.md for detailed setup instructions
- Check CHANGELOG.md for version history
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)
| File | Size | Uploaded | |
|---|---|---|---|
| django_supabase_storage-1.1.0.tar.gz | 27.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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