Supabase Storage
Store files in Supabase with public or private bucket support and automatic signed URL generation.
Strapi Provider Upload Supabase
Upload files to Supabase Storage from Strapi v5 with support for both public and private buckets.
Features
- ✅ Strapi v5 compatible
- ✅ Node.js 20 and 22 support
- ✅ TypeScript with full type definitions
- ✅ CommonJS and ESM support
- ✅ Public buckets (permanent URLs)
- ✅ Private buckets (time-limited signed URLs)
Installation
npm install strapi-provider-upload-supabase-bucketRequirements
- Node.js >= 20.0.0 and <= 22.x.x
- Strapi >= 5.0.0
- Supabase project with a storage bucket
Quick Start
1. Environment Variables
Add to your .env file:
1SUPABASE_API_URL=https://your-project.supabase.co
2SUPABASE_API_KEY=your-service-role-key
3SUPABASE_BUCKET=your-bucket-name
4SUPABASE_DIRECTORY=uploads
5SUPABASE_PUBLIC_FILES=true
6SUPABASE_SIGNED_URL_EXPIRES=36002. Plugin Configuration
Create or update config/plugins.ts (or config/plugins.js for JavaScript):
TypeScript:
1export default ({ env }) => ({
2 upload: {
3 config: {
4 provider: 'strapi-provider-upload-supabase-bucket',
5 providerOptions: {
6 apiUrl: env('SUPABASE_API_URL'),
7 apiKey: env('SUPABASE_API_KEY'),
8 bucket: env('SUPABASE_BUCKET'),
9 directory: env('SUPABASE_DIRECTORY', ''),
10 publicFiles: env.bool('SUPABASE_PUBLIC_FILES', true),
11 signedUrlExpires: env.int('SUPABASE_SIGNED_URL_EXPIRES', 3600),
12 },
13 sizeLimit: 250 * 1024 * 1024, // 250MB
14 },
15 },
16});JavaScript:
1module.exports = ({ env }) => ({
2 upload: {
3 config: {
4 provider: 'strapi-provider-upload-supabase-bucket',
5 providerOptions: {
6 apiUrl: env('SUPABASE_API_URL'),
7 apiKey: env('SUPABASE_API_KEY'),
8 bucket: env('SUPABASE_BUCKET'),
9 directory: env('SUPABASE_DIRECTORY', ''),
10 publicFiles: env.bool('SUPABASE_PUBLIC_FILES', true),
11 signedUrlExpires: env.int('SUPABASE_SIGNED_URL_EXPIRES', 3600),
12 },
13 sizeLimit: 250 * 1024 * 1024, // 250MB
14 },
15 },
16});3. Security Configuration
Update config/middlewares.ts (or config/middlewares.js) to allow Supabase URLs:
1module.exports = [
2 'strapi::logger',
3 'strapi::errors',
4 {
5 name: 'strapi::security',
6 config: {
7 contentSecurityPolicy: {
8 useDefaults: true,
9 directives: {
10 'connect-src': ["'self'", 'https:'],
11 'img-src': [
12 "'self'",
13 'data:',
14 'blob:',
15 'market-assets.strapi.io',
16 'https://your-project.supabase.co', // Replace with your project URL
17 ],
18 'media-src': [
19 "'self'",
20 'data:',
21 'blob:',
22 'market-assets.strapi.io',
23 'https://your-project.supabase.co', // Replace with your project URL
24 ],
25 upgradeInsecureRequests: null,
26 },
27 },
28 },
29 },
30 'strapi::cors',
31 'strapi::poweredBy',
32 'strapi::query',
33 'strapi::body',
34 'strapi::session',
35 'strapi::favicon',
36 'strapi::public',
37];Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiUrl | string | Yes | - | Supabase project URL |
apiKey | string | Yes | - | Supabase service role key |
bucket | string | Yes | - | Storage bucket name |
directory | string | No | '' | Subdirectory for file organization |
publicFiles | boolean | No | true | Public (true) or private (false) bucket |
signedUrlExpires | number | No | 3600 | Signed URL expiration (seconds, private only) |
Public vs Private Buckets
Public Buckets (publicFiles: true)
Files are accessible via permanent public URLs without authentication.
1https://your-project.supabase.co/storage/v1/object/public/bucket/uploads/file.jpgBest for: Public assets, images, documents
Private Buckets (publicFiles: false)
Files require time-limited signed URLs for access.
1https://your-project.supabase.co/storage/v1/object/sign/bucket/uploads/file.jpg?token=...Best for: User documents, sensitive files, access-controlled content
Accessing Private Files
1// Get file from Strapi
2const file = await strapi.plugins.upload.services.upload.findOne(fileId);
3
4// Generate signed URL
5const provider = strapi.plugins.upload.provider;
6const { url } = await provider.getSignedUrl(file);
7
8// Use the temporary URL (expires after signedUrlExpires seconds)
9console.log(url);Supabase Setup
- Go to your Supabase dashboard
- Navigate to Storage → New bucket
- Create a bucket (choose Public or Private)
- Get credentials from Settings → API:
- Project URL →
SUPABASE_API_URL - service_role key →
SUPABASE_API_KEY
- Project URL →
Troubleshooting
Files not displaying
Check config/middlewares.js includes your Supabase URL in CSP directives.
Upload fails
- Verify
apiUrl,apiKey, andbucketare correct - Ensure you're using the
service_rolekey (notanonkey) - Check bucket exists in Supabase
Private bucket signed URLs not working
- Verify bucket is set to Private in Supabase
- Check
publicFiles: falsein configuration - Ensure signed URLs are regenerated before expiration
Testing
Tested and verified on:
| Bucket Type | Node 20 | Node 22 |
|---|---|---|
| Public | ✅ | ✅ |
| Private | ✅ | ✅ |
Privacy
This provider does not collect, track, or transmit any usage data. All operations are performed directly between your Strapi instance and your Supabase project.
Security
- Never commit your
service_rolekey to version control - Use environment variables for all credentials
- Set appropriate
signedUrlExpiresduration for private buckets - Implement proper authentication before generating signed URLs
License
MIT
Links
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Made with ❤️ for the Strapi community
Install now
npm install strapi-provider-upload-supabase-bucket
Create your own plugin
Check out the available plugin resources that will help you to develop your plugin or provider and get it listed on the marketplace.