Skip to main content

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

FeatureValue
Max file size50 MB per file
Max buckets per project100
Allowed content typesAll MIME types
Signed URL max expiry7 days