Storage
NaijaBase Storage lets you store and serve files — images, videos, documents, and any other binary data — directly from your NaijaBase project. Files are stored in buckets and served via a CDN-friendly URL.
Concepts
- Bucket — a named container for files (like an S3 bucket or Google Cloud Storage bucket)
- Object — a file stored in a bucket, identified by its path (e.g.
avatars/user-123.jpg) - Public bucket — files are accessible via a public URL without authentication
- Private bucket — files require a signed URL or authenticated request
Create a bucket
import { createClient } from '@naijabase/js'
const nb = createClient(
process.env.NEXT_PUBLIC_NAIJABASE_URL,
process.env.NEXT_PUBLIC_NAIJABASE_ANON_KEY
)
// Public bucket (files accessible to anyone)
const { data, error } = await nb.storage.createBucket('avatars', { public: true })
// Private bucket (requires signed URL)
const { data, error } = await nb.storage.createBucket('documents', { public: false })
Upload a file
// From a File object (browser)
const file = event.target.files[0]
const { data, error } = await nb.storage
.from('avatars')
.upload(`public/${userId}.jpg`, file, {
cacheControl: '3600',
upsert: true, // overwrite if exists
contentType: 'image/jpeg',
})
// From a Buffer (Node.js)
const fs = require('fs')
const buffer = fs.readFileSync('./photo.jpg')
const { data, error } = await nb.storage
.from('avatars')
.upload('photos/ada.jpg', buffer, { contentType: 'image/jpeg' })
Get a public URL
const { data } = nb.storage
.from('avatars')
.getPublicUrl('public/user-123.jpg')
console.log(data.publicUrl)
// https://api.naijabase.dev/storage/v1/object/public/avatars/public/user-123.jpg
Get a signed URL (private buckets)
// URL expires in 60 seconds
const { data, error } = await nb.storage
.from('documents')
.createSignedUrl('invoice-456.pdf', 60)
console.log(data.signedUrl)
Download a file
const { data, error } = await nb.storage
.from('avatars')
.download('public/user-123.jpg')
// data is a Blob
const url = URL.createObjectURL(data)
List files in a bucket
const { data, error } = await nb.storage
.from('avatars')
.list('public', {
limit: 100,
offset: 0,
sortBy: { column: 'created_at', order: 'desc' },
})
// data is an array of file metadata objects
Remove files
// Remove a single file
const { data, error } = await nb.storage
.from('avatars')
.remove(['public/user-123.jpg'])
// Remove multiple files
const { data, error } = await nb.storage
.from('documents')
.remove(['invoice-1.pdf', 'invoice-2.pdf'])
Move or copy files
// Move (rename)
await nb.storage.from('avatars').move('old-name.jpg', 'new-name.jpg')
// Copy
await nb.storage.from('avatars').copy('original.jpg', 'backup.jpg')
Profile picture example
A complete flow for uploading and displaying a user avatar:
import { useState } from 'react'
import { createClient } from '@naijabase/js'
const nb = createClient(process.env.NEXT_PUBLIC_NAIJABASE_URL, process.env.NEXT_PUBLIC_NAIJABASE_ANON_KEY)
export default function AvatarUpload({ userId }) {
const [avatarUrl, setAvatarUrl] = useState(null)
const [uploading, setUploading] = useState(false)
async function uploadAvatar(event) {
const file = event.target.files[0]
if (!file) return
setUploading(true)
const filePath = `${userId}/avatar.${file.name.split('.').pop()}`
const { error } = await nb.storage.from('avatars').upload(filePath, file, { upsert: true })
if (error) {
alert('Upload failed: ' + error.message)
} else {
const { data } = nb.storage.from('avatars').getPublicUrl(filePath)
setAvatarUrl(data.publicUrl)
// Save URL to profiles table
await nb.from('profiles').update({ avatar_url: data.publicUrl }).eq('user_id', userId)
}
setUploading(false)
}
return (
<div>
{avatarUrl && <img src={avatarUrl} alt="Avatar" width={100} height={100} style={{ borderRadius: '50%' }} />}
<input type="file" accept="image/*" onChange={uploadAvatar} disabled={uploading} />
{uploading && <p>Uploading...</p>}
</div>
)
}
Storage RLS
By default, authenticated users can upload to any bucket. You can restrict access with storage policies in your project settings, or use the service key in server-side upload routes for full control:
// Server-side upload route (uses service key — never expose in browser)
const nb = createClient(
process.env.NAIJABASE_URL,
process.env.NAIJABASE_SERVICE_KEY // service key: bypasses RLS
)
REST API (curl)
Upload
curl -X POST "https://api.naijabase.dev/storage/v1/object/avatars/public/user-123.jpg" \
-H "Authorization: Bearer eyJ..." \
-H "apikey: nb_anon_..." \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
Get public URL
Files in public buckets are accessible at:
https://api.naijabase.dev/storage/v1/object/public/{bucket}/{path}
List files
curl -X POST "https://api.naijabase.dev/storage/v1/object/list/avatars" \
-H "Authorization: Bearer eyJ..." \
-H "apikey: nb_anon_..." \
-H "Content-Type: application/json" \
-d '{"prefix": "public/", "limit": 100}'
Limits
| Feature | Value |
|---|---|
| Max file size | 50 MB per file |
| Max buckets per project | 100 |
| Allowed content types | All MIME types |
| Signed URL max expiry | 7 days |