• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License
    • Branding
    • Data Explorer API
    • Storage Explorer API
    • Users API
    • Audit Logs API
    • Writing Packages
    • Writing Plugins

Storage Explorer API

Use the Storage Explorer API to fetch data from the storage with ease

The StorageService is a comprehensive service for managing Supabase storage operations with security-first design.

It provides secure file and folder management with comprehensive permission checks, path validation, and bulk operations support.

Table of Contents

  1. Basic Setup
  2. Core Features
  3. Security & Permissions
  4. Core Methods
  5. File Operations
  6. Folder Operations
  7. URL Generation
  8. Batch Operations
  9. Error Handling
  10. Examples

Basic Setup

typescript
import { createStorageService } from '@kit/storage-explorer';
import { Context } from 'hono';
// Create service instance
const storageService = createStorageService(context);

Core Features

Security-First Design

  • Path Validation: Prevents directory traversal attacks
  • Permission Checks: Validates user permissions before operations
  • Bulk Permission Validation: Optimized permission checking for multiple files
  • Safety Limits: Prevents abuse with file count limits

File Type Detection

  • Automatic Classification: Images, videos, documents, code, archives
  • Preview Generation: Signed URLs for private files
  • Public URL Support: Efficient URL generation for public buckets

Performance Optimizations

  • Batch Processing: Handles multiple files efficiently
  • Parallel Operations: Uses Promise.all for concurrent operations
  • Smart Pagination: Efficient content listing with metadata

Security & Permissions

Path Security

All file paths are validated and normalized to prevent attacks:

typescript
// Automatically validates and normalizes paths
const result = await storageService.renameFile({
bucket: 'documents',
fromPath: 'folder/../sensitive.txt', // Blocked
toPath: 'folder/document.txt' // Allowed
});

Permission Model

The service integrates with Supamode's permission system:

  • Read (select): View files and folders
  • Write (insert): Upload new files
  • Update: Rename and modify files
  • Delete: Remove files and folders

Bulk Permission Validation

Optimized permission checking for multiple files:

typescript
// Efficiently validates permissions for multiple files
const permissions = await storageService.getUserPermissions({
bucket: 'documents',
path: 'folder/'
});

Core Methods

1. Get Storage Buckets

Retrieve all storage buckets the user has access to.

typescript
async getBuckets(): Promise<Bucket[]>

Returns:

typescript
interface Bucket {
id: string;
name: string;
public: boolean;
created_at: string;
updated_at: string;
}

Example:

typescript
const buckets = await storageService.getBuckets();
console.log(buckets); // [{ id: '1', name: 'documents', public: false, ... }]

2. Get Bucket Contents

List files and folders in a bucket with comprehensive metadata.

typescript
async getBucketContents(params: {
bucket: string;
path?: string;
search?: string;
page?: number;
limit?: number;
}): Promise<{
contents: StorageFile[];
pagination: PaginationInfo;
}>

Parameters:

  • bucket: Storage bucket name
  • path: Folder path (optional, defaults to root)
  • search: Search term for filtering files
  • page: Page number for pagination
  • limit: Number of items per page

Returns:

typescript
interface StorageFile {
name: string;
id: string | null;
updated_at: string | null;
created_at: string | null;
last_accessed_at: string | null;
metadata: Record<string, unknown> | null;
isDirectory: boolean;
fileType: string;
publicUrl?: string;
previewUrl?: string;
permissions: {
canRead: boolean;
canUpdate: boolean;
canDelete: boolean;
canUpload: boolean;
};
}

Example:

typescript
const result = await storageService.getBucketContents({
bucket: 'documents',
path: 'reports/',
search: 'annual',
page: 1,
limit: 25
});
console.log(result.contents); // Array of files and folders
console.log(result.pagination); // Pagination metadata

3. Get User Permissions

Check user permissions for a specific bucket and path.

typescript
async getUserPermissions(params: {
bucket: string;
path: string;
}): Promise<{
canRead: boolean;
canUpdate: boolean;
canDelete: boolean;
canUpload: boolean;
}>

Example:

typescript
const permissions = await storageService.getUserPermissions({
bucket: 'documents',
path: 'reports/sensitive.pdf'
});
if (permissions.canRead) {
// User can view this file
}

File Operations

1. Rename File

Rename or move a file or folder.

typescript
async renameFile(params: {
bucket: string;
fromPath: string;
toPath: string;
}): Promise<{ success: boolean }>

Security Features:

  • Path validation and normalization
  • Permission checks for both source and destination
  • Prevents directory traversal attacks

Example:

typescript
const result = await storageService.renameFile({
bucket: 'documents',
fromPath: 'reports/draft.pdf',
toPath: 'reports/final.pdf'
});
if (result.success) {
console.log('File renamed successfully');
}

2. Delete Files

Delete single or multiple files and folders.

typescript
async deleteFile(params: {
bucket: string;
paths: string[];
}): Promise<{ success: boolean }>

Security Features:

  • Batch path validation (max 50 files)
  • Bulk permission validation
  • Recursive folder deletion with safety limits
  • Protection against excessive deletion (max 1000 files)

Example:

typescript
const result = await storageService.deleteFile({
bucket: 'documents',
paths: [
'reports/old-report.pdf',
'temp/', // Deletes entire folder
'drafts/notes.txt'
]
});
if (result.success) {
console.log('Files deleted successfully');
}

3. Get Download URL

Generate download URLs for files.

typescript
async getDownloadUrl(params: {
bucket: string;
path: string;
}): Promise<string>

Features:

  • Automatic detection of public vs private buckets
  • Signed URL generation for private files
  • Public URL generation for public buckets

Example:

typescript
const downloadUrl = await storageService.getDownloadUrl({
bucket: 'documents',
path: 'reports/annual-report.pdf'
});
// Use URL for download
window.open(downloadUrl, '_blank');

Folder Operations

1. Create Folder

Create new folders with proper security validation.

typescript
async createFolder(params: {
bucket: string;
folderName: string;
parentPath?: string;
}): Promise<{ success: boolean }>

Security Features:

  • Folder name validation
  • Path normalization
  • Permission validation for target location
  • Prevents overwriting existing folders

Example:

typescript
const result = await storageService.createFolder({
bucket: 'documents',
folderName: 'Q4-Reports',
parentPath: 'reports/2024'
});
if (result.success) {
console.log('Folder created successfully');
}

2. Batch Folder Processing

The service handles recursive folder operations efficiently:

typescript
// Deleting a folder with contents
const result = await storageService.deleteFile({
bucket: 'documents',
paths: ['reports/2023/'] // Deletes folder and all contents
});

Safety Features:

  • Batch processing with size limits
  • Progress tracking for large operations
  • Error handling for individual file failures
  • Protection against infinite loops

URL Generation

1. Public URLs

Generate public URLs for files in public buckets.

typescript
async getPublicUrl(params: {
bucket: string;
path: string;
}): Promise<string>

Example:

typescript
const publicUrl = await storageService.getPublicUrl({
bucket: 'public-assets',
path: 'images/logo.png'
});
// URL can be used directly in <img> tags

2. Signed URLs

Generate temporary signed URLs for private files.

typescript
async getSignedUrl(params: {
bucket: string;
path: string;
expiresIn?: number;
}): Promise<string>

Parameters:

  • expiresIn: Expiration time in seconds (default: 3600 = 1 hour)

Example:

typescript
const signedUrl = await storageService.getSignedUrl({
bucket: 'private-documents',
path: 'contracts/agreement.pdf',
expiresIn: 7200 // 2 hours
});
// URL expires after 2 hours

Batch Operations

Bulk Permission Checking

Efficiently check permissions for multiple files:

typescript
// Automatically used by getBucketContents
const contents = await storageService.getBucketContents({
bucket: 'documents',
path: 'reports/'
});
// Each file includes permission information
contents.contents.forEach(file => {
console.log(file.name, file.permissions);
});

Bulk File Operations

Handle multiple files in single operations:

typescript
// Delete multiple files efficiently
const result = await storageService.deleteFile({
bucket: 'documents',
paths: [
'temp/file1.txt',
'temp/file2.txt',
'temp/file3.txt',
'temp/folder/',
'drafts/old-notes.txt'
]
});

Error Handling

Common Error Types

typescript
try {
await storageService.deleteFile({
bucket: 'documents',
paths: ['protected/file.txt']
});
} catch (error) {
if (error.message.includes('permission')) {
// Handle permission error
console.error('No permission to delete file');
} else if (error.message.includes('Too many files')) {
// Handle batch size limit
console.error('Too many files to delete at once');
} else {
// Handle other errors
console.error('Storage operation failed:', error.message);
}
}

Validation Errors

typescript
try {
await storageService.renameFile({
bucket: 'documents',
fromPath: '../../../etc/passwd', // Invalid path
toPath: 'normal-file.txt'
});
} catch (error) {
// Path validation error
console.error('Invalid file path:', error.message);
}

Permission Errors

typescript
try {
await storageService.createFolder({
bucket: 'restricted',
folderName: 'new-folder'
});
} catch (error) {
// Permission validation error
console.error('No permission to create folder:', error.message);
}

Examples

Example 1: File Manager Interface

typescript
class FileManager {
constructor(private storageService: StorageService) {}
async loadFolderContents(bucket: string, path: string = '/') {
const result = await this.storageService.getBucketContents({
bucket,
path,
page: 1,
limit: 50
});
return {
files: result.contents.filter(item => !item.isDirectory),
folders: result.contents.filter(item => item.isDirectory),
pagination: result.pagination
};
}
async searchFiles(bucket: string, searchTerm: string) {
return await this.storageService.getBucketContents({
bucket,
search: searchTerm,
limit: 20
});
}
async uploadFile(bucket: string, path: string, file: File) {
// Check upload permission first
const permissions = await this.storageService.getUserPermissions({
bucket,
path
});
if (!permissions.canUpload) {
throw new Error('No permission to upload to this location');
}
// Upload logic would go here
// This service focuses on management, not upload
}
async deleteSelected(bucket: string, selectedPaths: string[]) {
// Validate permissions for all selected items
const permissionChecks = await Promise.all(
selectedPaths.map(path =>
this.storageService.getUserPermissions({ bucket, path })
)
);
const canDeleteAll = permissionChecks.every(p => p.canDelete);
if (!canDeleteAll) {
throw new Error('Insufficient permissions to delete some files');
}
return await this.storageService.deleteFile({
bucket,
paths: selectedPaths
});
}
async createNewFolder(bucket: string, folderName: string, parentPath?: string) {
return await this.storageService.createFolder({
bucket,
folderName,
parentPath
});
}
async renameItem(bucket: string, fromPath: string, newName: string) {
// Calculate new path
const pathParts = fromPath.split('/');
pathParts[pathParts.length - 1] = newName;
const toPath = pathParts.join('/');
return await this.storageService.renameFile({
bucket,
fromPath,
toPath
});
}
}

Example 2: Document Management System

typescript
class DocumentManager {
constructor(private storageService: StorageService) {}
async getDocumentsByType(bucket: string, fileType: string) {
const allContents = await this.storageService.getBucketContents({
bucket,
limit: 100
});
return allContents.contents.filter(file =>
!file.isDirectory && file.fileType === fileType
);
}
async getRecentDocuments(bucket: string, days: number = 7) {
const allContents = await this.storageService.getBucketContents({
bucket,
limit: 100
});
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
return allContents.contents.filter(file => {
if (file.isDirectory || !file.updated_at) return false;
return new Date(file.updated_at) > cutoffDate;
});
}
async organizeDocuments(bucket: string) {
const allContents = await this.storageService.getBucketContents({
bucket,
limit: 1000
});
const documentsByType = allContents.contents
.filter(file => !file.isDirectory)
.reduce((acc, file) => {
if (!acc[file.fileType]) {
acc[file.fileType] = [];
}
acc[file.fileType].push(file);
return acc;
}, {} as Record<string, typeof allContents.contents>);
// Create folders for each type
for (const fileType of Object.keys(documentsByType)) {
try {
await this.storageService.createFolder({
bucket,
folderName: fileType
});
} catch (error) {
// Folder might already exist
console.log(`Folder ${fileType} already exists`);
}
}
return documentsByType;
}
async generateShareableLink(bucket: string, path: string, expiresIn: number = 3600) {
const permissions = await this.storageService.getUserPermissions({
bucket,
path
});
if (!permissions.canRead) {
throw new Error('No permission to share this file');
}
return await this.storageService.getSignedUrl({
bucket,
path,
expiresIn
});
}
async bulkDownloadUrls(bucket: string, paths: string[]) {
const urls = await Promise.all(
paths.map(async path => {
try {
const url = await this.storageService.getDownloadUrl({
bucket,
path
});
return { path, url, success: true };
} catch (error) {
return {
path,
url: null,
success: false,
error: error.message
};
}
})
);
return urls;
}
}

Example 3: Media Gallery

typescript
class MediaGallery {
constructor(private storageService: StorageService) {}
async getImageGallery(bucket: string, path: string = '/') {
const contents = await this.storageService.getBucketContents({
bucket,
path,
limit: 100
});
const images = contents.contents.filter(file =>
file.fileType === 'image' && file.permissions.canRead
);
return images.map(image => ({
name: image.name,
url: image.publicUrl || image.previewUrl,
thumbnail: image.previewUrl,
metadata: image.metadata,
canDelete: image.permissions.canDelete
}));
}
async getVideoGallery(bucket: string, path: string = '/') {
const contents = await this.storageService.getBucketContents({
bucket,
path,
limit: 50
});
return contents.contents.filter(file =>
file.fileType === 'video' && file.permissions.canRead
);
}
async organizeMediaByDate(bucket: string) {
const allMedia = await this.storageService.getBucketContents({
bucket,
limit: 1000
});
const mediaFiles = allMedia.contents.filter(file =>
['image', 'video'].includes(file.fileType)
);
const mediaByDate = mediaFiles.reduce((acc, file) => {
if (!file.created_at) return acc;
const date = new Date(file.created_at);
const yearMonth = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`;
if (!acc[yearMonth]) {
acc[yearMonth] = [];
}
acc[yearMonth].push(file);
return acc;
}, {} as Record<string, typeof mediaFiles>);
return mediaByDate;
}
async cleanupOldMedia(bucket: string, daysOld: number = 30) {
const allMedia = await this.storageService.getBucketContents({
bucket,
limit: 1000
});
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
const oldMedia = allMedia.contents.filter(file => {
if (file.isDirectory || !file.created_at) return false;
return new Date(file.created_at) < cutoffDate && file.permissions.canDelete;
});
if (oldMedia.length === 0) {
return { deleted: 0, message: 'No old media found' };
}
const paths = oldMedia.map(file => file.name);
const result = await this.storageService.deleteFile({
bucket,
paths
});
return {
deleted: oldMedia.length,
success: result.success,
files: oldMedia.map(f => f.name)
};
}
}
On this page
  1. Table of Contents
    1. Basic Setup
      1. Core Features
        1. Security-First Design
        2. File Type Detection
        3. Performance Optimizations
      2. Security & Permissions
        1. Path Security
        2. Permission Model
        3. Bulk Permission Validation
      3. Core Methods
        1. 1. Get Storage Buckets
        2. 2. Get Bucket Contents
        3. 3. Get User Permissions
      4. File Operations
        1. 1. Rename File
        2. 2. Delete Files
        3. 3. Get Download URL
      5. Folder Operations
        1. 1. Create Folder
        2. 2. Batch Folder Processing
      6. URL Generation
        1. 1. Public URLs
        2. 2. Signed URLs
      7. Batch Operations
        1. Bulk Permission Checking
        2. Bulk File Operations
      8. Error Handling
        1. Common Error Types
        2. Validation Errors
        3. Permission Errors
      9. Examples
        1. Example 1: File Manager Interface
        2. Example 2: Document Management System
        3. Example 3: Media Gallery