| <br /> |
| <p align="center"> |
| <a href="https://supabase.io"> |
| <picture> |
| <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--dark.svg"> |
| <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--light.svg"> |
| <img alt="Supabase Logo" width="300" src="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/logo-preview.jpg"> |
| </picture> |
| </a> |
| |
| <h1 align="center">Supabase Storage JS SDK</h1> |
|
|
| <h3 align="center">JavaScript SDK to interact with Supabase Storage, including file storage and vector embeddings.</h3> |
|
|
| <p align="center"> |
| <a href="https://supabase.com/docs/guides/storage">Guides</a> |
| · |
| <a href="https://supabase.com/docs/reference/javascript/storage-createbucket">Reference Docs</a> |
| · |
| <a href="https://supabase.github.io/supabase-js/storage-js/v2/spec.json">TypeDoc</a> |
| </p> |
| </p> |
| |
| <div align="center"> |
|
|
| [](https://github.com/supabase/supabase-js/actions?query=branch%3Amaster) |
| [](https://www.npmjs.com/package/@supabase/storage-js) |
| [](#license) |
| [](https://pkg.pr.new/~/supabase/storage-js) |
|
|
| </div> |
|
|
| ## Requirements |
|
|
| - **Node.js 22 or later** (Node.js 20 support dropped in v2.110.0) |
| - For browser support, all modern browsers are supported |
|
|
| > ⚠️ **Node.js 18 Deprecation Notice** |
| > |
| > Node.js 18 reached end-of-life on April 30, 2025. As announced in [our deprecation notice](https://github.com/orgs/supabase/discussions/37217), support for Node.js 18 was dropped on October 31, 2025. |
|
|
| > ⚠️ **Node.js 20 Deprecation Notice** |
| > |
| > Node.js 20 reached end-of-life on April 30, 2026. As announced in [our deprecation notice](https://github.com/orgs/supabase/discussions/45715), support for Node.js 20 was dropped in v2.110.0. |
|
|
| ## Features |
|
|
| - **File Storage**: Upload, download, list, move, and delete files |
| - **Access Control**: Public and private buckets with fine-grained permissions |
| - **Signed URLs**: Generate time-limited URLs for secure file access |
| - **Image Transformations**: On-the-fly image resizing and optimization |
| - **Vector Embeddings**: Store and query high-dimensional embeddings with similarity search |
| - **Analytics Buckets**: Iceberg table-based buckets optimized for analytical queries and data processing |
|
|
| ## Quick Start Guide |
|
|
| ### Installing the module |
|
|
| ```bash |
| npm install @supabase/storage-js |
| ``` |
|
|
| ### Connecting to the storage backend |
|
|
| There are two ways to use the Storage SDK: |
|
|
| #### Option 1: Via Supabase Client (Recommended) |
|
|
| If you're already using `@supabase/supabase-js`, access storage through the client: |
|
|
| ```js |
| import { createClient } from '@supabase/supabase-js' |
| |
| // Use publishable key for frontend applications |
| const supabase = createClient('https://<project_ref>.supabase.co', '<your-publishable-key>') |
| |
| // Access storage |
| const storage = supabase.storage |
| |
| // Access different bucket types |
| const regularBucket = storage.from('my-bucket') |
| const vectorBucket = storage.vectors.from('embeddings-bucket') |
| const analyticsBucket = storage.analytics // Analytics API |
| ``` |
|
|
| #### Option 2: Standalone StorageClient |
|
|
| For backend applications or when you need to bypass Row Level Security: |
|
|
| ```js |
| import { StorageClient } from '@supabase/storage-js' |
| |
| const STORAGE_URL = 'https://<project_ref>.supabase.co/storage/v1' |
| const SERVICE_KEY = '<your-secret-key>' // Use secret key for backend operations |
| |
| const storageClient = new StorageClient(STORAGE_URL, { |
| apikey: SERVICE_KEY, |
| Authorization: `Bearer ${SERVICE_KEY}`, |
| }) |
| |
| // Access different bucket types |
| const regularBucket = storageClient.from('my-bucket') |
| const vectorBucket = storageClient.vectors.from('embeddings-bucket') |
| const analyticsBucket = storageClient.analytics // Analytics API |
| ``` |
|
|
| > **When to use each approach:** |
| > |
| > - Use `supabase.storage` when working with other Supabase features (auth, database, etc.) in frontend applications |
| > - Use `new StorageClient()` for backend applications, Edge Functions, or when you need to bypass RLS policies |
|
|
| > **Note:** Refer to the [Storage Access Control guide](https://supabase.com/docs/guides/storage/access-control) for detailed information on creating RLS policies. |
|
|
| ### Understanding Bucket Types |
|
|
| Supabase Storage supports three types of buckets, each optimized for different use cases: |
|
|
| #### 1. Regular Storage Buckets (File Storage) |
|
|
| Standard buckets for storing files, images, videos, and other assets. |
|
|
| ```js |
| // Create regular storage bucket |
| const { data, error } = await storageClient.createBucket('my-files', { |
| public: false, |
| }) |
| |
| // Upload files |
| await storageClient.from('my-files').upload('avatar.png', file) |
| ``` |
|
|
| **Use cases:** User uploads, media assets, documents, backups |
|
|
| #### 2. Vector Buckets (Embeddings Storage) |
|
|
| Specialized buckets for storing and querying high-dimensional vector embeddings. |
|
|
| ```js |
| // Create vector bucket |
| await storageClient.vectors.createBucket('embeddings-prod') |
| |
| // Create index and insert vectors |
| const bucket = storageClient.vectors.from('embeddings-prod') |
| await bucket.createIndex({ |
| indexName: 'documents', |
| dimension: 1536, |
| distanceMetric: 'cosine', |
| }) |
| ``` |
|
|
| **Use cases:** Semantic search, AI-powered recommendations, similarity matching |
|
|
| **[See full Vector Embeddings documentation below](#vector-embeddings)** |
|
|
| #### 3. Analytics Buckets |
|
|
| Specialized buckets using Apache Iceberg table format, optimized for analytical queries and large-scale data processing. |
|
|
| ```js |
| // Create analytics bucket |
| await storageClient.analytics.createBucket('analytics-data') |
| |
| // List analytics buckets |
| const { data, error } = await storageClient.analytics.listBuckets() |
| |
| // Delete analytics bucket |
| await storageClient.analytics.deleteBucket('analytics-data') |
| ``` |
|
|
| **Use cases:** Time-series data, analytical queries, data lakes, large-scale data processing, business intelligence |
|
|
| **[See full Analytics Buckets documentation below](#analytics-buckets)** |
|
|
| --- |
|
|
| ### Handling resources |
|
|
| #### Handling Storage Buckets |
|
|
| - Create a new Storage bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.createBucket( |
| 'test_bucket', // Bucket name (must be unique) |
| { public: false } // Bucket options |
| ) |
| ``` |
|
|
| - Retrieve the details of an existing Storage bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.getBucket('test_bucket') |
| ``` |
|
|
| - Update a new Storage bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.updateBucket( |
| 'test_bucket', // Bucket name |
| { public: false } // Bucket options |
| ) |
| ``` |
|
|
| - Remove all objects inside a single bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.emptyBucket('test_bucket') |
| ``` |
|
|
| - Delete an existing bucket (a bucket can't be deleted with existing objects inside it): |
|
|
| ```js |
| const { data, error } = await storageClient.deleteBucket('test_bucket') |
| ``` |
|
|
| - Retrieve the details of all Storage buckets within an existing project: |
|
|
| ```js |
| // List all buckets |
| const { data, error } = await storageClient.listBuckets() |
| |
| // List buckets with options (pagination, sorting, search) |
| const { data, error } = await storageClient.listBuckets({ |
| limit: 10, |
| offset: 0, |
| sortColumn: 'created_at', |
| sortOrder: 'desc', |
| search: 'prod', |
| }) |
| ``` |
|
|
| #### Handling Files |
|
|
| - Upload a file to an existing bucket: |
|
|
| ```js |
| const fileBody = ... // load your file here |
| |
| const { data, error } = await storageClient.from('bucket').upload('path/to/file', fileBody) |
| ``` |
|
|
| > Note: |
| > The path in `data.Key` is prefixed by the bucket ID and is not the value which should be passed to the `download` method in order to fetch the file. |
| > To fetch the file via the `download` method, use `data.path` and `data.bucketId` as follows: |
| > |
| > ```javascript |
| > const { data, error } = await storageClient.from('bucket').upload('/folder/file.txt', fileBody) |
| > // check for errors |
| > const { data2, error2 } = await storageClient.from(data.bucketId).download(data.path) |
| > ``` |
|
|
| > Note: The `upload` method also accepts a map of optional parameters. For a complete list see the [Supabase API reference](https://supabase.com/docs/reference/javascript/storage-from-upload). |
|
|
| - Download a file from an exisiting bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.from('bucket').download('path/to/file') |
| ``` |
|
|
| - List all the files within a bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.from('bucket').list('folder') |
| ``` |
|
|
| > Note: The `list` method also accepts a map of optional parameters. For a complete list see the [Supabase API reference](https://supabase.com/docs/reference/javascript/storage-from-list). |
|
|
| - Replace an existing file at the specified path with a new one: |
|
|
| ```js |
| const fileBody = ... // load your file here |
| |
| const { data, error } = await storageClient |
| .from('bucket') |
| .update('path/to/file', fileBody) |
| ``` |
|
|
| > Note: The `upload` method also accepts a map of optional parameters. For a complete list see the [Supabase API reference](https://supabase.com/docs/reference/javascript/storage-from-upload). |
|
|
| - Move an existing file: |
|
|
| ```js |
| const { data, error } = await storageClient |
| .from('bucket') |
| .move('old/path/to/file', 'new/path/to/file') |
| ``` |
|
|
| - Delete files within the same bucket: |
|
|
| ```js |
| const { data, error } = await storageClient.from('bucket').remove(['path/to/file']) |
| ``` |
|
|
| - Create signed URL to download file without requiring permissions: |
|
|
| ```js |
| const expireIn = 60 |
| |
| const { data, error } = await storageClient |
| .from('bucket') |
| .createSignedUrl('path/to/file', expireIn) |
| ``` |
|
|
| - Retrieve URLs for assets in public buckets: |
|
|
| ```js |
| const { data, error } = await storageClient.from('public-bucket').getPublicUrl('path/to/file') |
| ``` |
|
|
| ## Analytics Buckets |
|
|
| Supabase Storage provides specialized analytics buckets using Apache Iceberg table format, optimized for analytical workloads and large-scale data processing. These buckets are designed for data lake architectures, time-series data, and business intelligence applications. |
|
|
| ### What are Analytics Buckets? |
|
|
| Analytics buckets use the Apache Iceberg open table format, providing: |
|
|
| - **ACID transactions** for data consistency |
| - **Schema evolution** without data rewrites |
| - **Time travel** to query historical data |
| - **Efficient metadata management** for large datasets |
| - **Optimized for analytical queries** rather than individual file operations |
|
|
| ### When to Use Analytics Buckets |
|
|
| **Use analytics buckets for:** |
|
|
| - Time-series data (logs, metrics, events) |
| - Data lake architectures |
| - Business intelligence and reporting |
| - Large-scale batch processing |
| - Analytical workloads requiring ACID guarantees |
|
|
| **Use regular storage buckets for:** |
|
|
| - User file uploads (images, documents, videos) |
| - Individual file management |
| - Content delivery |
| - Simple object storage needs |
|
|
| ### Quick Start |
|
|
| You can access analytics functionality through the `analytics` property on your storage client: |
|
|
| #### Via Supabase Client |
|
|
| ```typescript |
| import { createClient } from '@supabase/supabase-js' |
| |
| const supabase = createClient('https://your-project.supabase.co', 'your-publishable-key') |
| |
| // Access analytics operations |
| const analytics = supabase.storage.analytics |
| |
| // Create an analytics bucket |
| const { data, error } = await analytics.createBucket('analytics-data') |
| if (error) { |
| console.error('Failed to create analytics bucket:', error.message) |
| } else { |
| console.log('Created bucket:', data.name) |
| } |
| ``` |
|
|
| #### Via StorageClient |
|
|
| ```typescript |
| import { StorageClient } from '@supabase/storage-js' |
| |
| const storageClient = new StorageClient('https://your-project.supabase.co/storage/v1', { |
| apikey: 'YOUR_API_KEY', |
| Authorization: 'Bearer YOUR_TOKEN', |
| }) |
| |
| // Access analytics operations |
| const analytics = storageClient.analytics |
| |
| // Create an analytics bucket |
| await analytics.createBucket('analytics-data') |
| ``` |
|
|
| ### API Reference |
|
|
| #### Create Analytics Bucket |
|
|
| Creates a new analytics bucket using Iceberg table format: |
|
|
| ```typescript |
| const { data, error } = await analytics.createBucket('my-analytics-bucket') |
| |
| if (error) { |
| console.error('Error:', error.message) |
| } else { |
| console.log('Created bucket:', data) |
| } |
| ``` |
|
|
| **Returns:** |
|
|
| ```typescript |
| { |
| data: { |
| id: string |
| type: 'ANALYTICS' |
| format: string |
| created_at: string |
| updated_at: string |
| } | null |
| error: StorageError | null |
| } |
| ``` |
|
|
| #### List Analytics Buckets |
|
|
| Retrieves all analytics buckets in your project with optional filtering and pagination: |
|
|
| ```typescript |
| const { data, error } = await analytics.listBuckets({ |
| limit: 10, |
| offset: 0, |
| sortColumn: 'created_at', |
| sortOrder: 'desc', |
| search: 'prod', |
| }) |
| |
| if (data) { |
| console.log(`Found ${data.length} analytics buckets`) |
| data.forEach((bucket) => { |
| console.log(`- ${bucket.id} (created: ${bucket.created_at})`) |
| }) |
| } |
| ``` |
|
|
| **Parameters:** |
|
|
| - `limit?: number` - Maximum number of buckets to return |
| - `offset?: number` - Number of buckets to skip (for pagination) |
| - `sortColumn?: 'id' | 'name' | 'created_at' | 'updated_at'` - Column to sort by |
| - `sortOrder?: 'asc' | 'desc'` - Sort direction |
| - `search?: string` - Search term to filter bucket names |
|
|
| **Returns:** |
|
|
| ```typescript |
| { |
| data: AnalyticBucket[] | null |
| error: StorageError | null |
| } |
| ``` |
|
|
| **Example with Pagination:** |
|
|
| ```typescript |
| // Fetch first page |
| const firstPage = await analytics.listBuckets({ |
| limit: 100, |
| offset: 0, |
| sortColumn: 'created_at', |
| sortOrder: 'desc', |
| }) |
| |
| // Fetch second page |
| const secondPage = await analytics.listBuckets({ |
| limit: 100, |
| offset: 100, |
| sortColumn: 'created_at', |
| sortOrder: 'desc', |
| }) |
| ``` |
|
|
| #### Delete Analytics Bucket |
|
|
| Deletes an analytics bucket. The bucket must be empty before deletion. |
|
|
| ```typescript |
| const { data, error } = await analytics.deleteBucket('old-analytics-bucket') |
| |
| if (error) { |
| console.error('Failed to delete:', error.message) |
| } else { |
| console.log('Bucket deleted:', data.message) |
| } |
| ``` |
|
|
| **Returns:** |
|
|
| ```typescript |
| { |
| data: { message: string } | null |
| error: StorageError | null |
| } |
| ``` |
|
|
| > **Note:** A bucket cannot be deleted if it contains data. You must empty the bucket first. |
|
|
| #### Get Iceberg Catalog for Advanced Operations |
|
|
| For advanced operations like creating tables, namespaces, and querying Iceberg metadata, use the `from()` method to get a configured [iceberg-js](https://github.com/supabase/iceberg-js) client: |
|
|
| ```typescript |
| // Get an Iceberg REST Catalog client for your analytics bucket |
| const catalog = analytics.from('analytics-data') |
| |
| // Create a namespace |
| await catalog.createNamespace({ namespace: ['default'] }, { properties: { owner: 'data-team' } }) |
| |
| // Create a table with schema |
| await catalog.createTable( |
| { namespace: ['default'] }, |
| { |
| name: 'events', |
| schema: { |
| type: 'struct', |
| fields: [ |
| { id: 1, name: 'id', type: 'long', required: true }, |
| { id: 2, name: 'timestamp', type: 'timestamp', required: true }, |
| { id: 3, name: 'user_id', type: 'string', required: false }, |
| ], |
| 'schema-id': 0, |
| 'identifier-field-ids': [1], |
| }, |
| 'partition-spec': { |
| 'spec-id': 0, |
| fields: [], |
| }, |
| 'write-order': { |
| 'order-id': 0, |
| fields: [], |
| }, |
| properties: { |
| 'write.format.default': 'parquet', |
| }, |
| } |
| ) |
| |
| // List tables in namespace |
| const tables = await catalog.listTables({ namespace: ['default'] }) |
| console.log(tables) // [{ namespace: ['default'], name: 'events' }] |
| |
| // Load table metadata |
| const table = await catalog.loadTable({ namespace: ['default'], name: 'events' }) |
| |
| // Update table properties |
| await catalog.updateTable( |
| { namespace: ['default'], name: 'events' }, |
| { properties: { 'read.split.target-size': '134217728' } } |
| ) |
| |
| // Drop table |
| await catalog.dropTable({ namespace: ['default'], name: 'events' }) |
| |
| // Drop namespace |
| await catalog.dropNamespace({ namespace: ['default'] }) |
| ``` |
|
|
| **Returns:** `IcebergRestCatalog` instance from [iceberg-js](https://github.com/supabase/iceberg-js) |
|
|
| > **Note:** The `from()` method returns an Iceberg REST Catalog client that provides full access to the Apache Iceberg REST API. For complete documentation of available operations, see the [iceberg-js documentation](https://supabase.github.io/iceberg-js/). |
|
|
| ### Error Handling |
|
|
| Analytics buckets use the same error handling pattern as the rest of the Storage SDK: |
|
|
| ```typescript |
| const { data, error } = await analytics.createBucket('my-bucket') |
| |
| if (error) { |
| console.error('Error:', error.message) |
| console.error('Status:', error.status) |
| console.error('Status Code:', error.statusCode) |
| // Handle error appropriately |
| } |
| ``` |
|
|
| #### Throwing Errors |
|
|
| You can configure the client to throw errors instead of returning them: |
|
|
| ```typescript |
| const analytics = storageClient.analytics |
| analytics.throwOnError() |
| |
| try { |
| const { data } = await analytics.createBucket('my-bucket') |
| // data is guaranteed to be present |
| console.log('Success:', data) |
| } catch (error) { |
| if (error instanceof StorageApiError) { |
| console.error('API Error:', error.statusCode, error.message) |
| } |
| } |
| ``` |
|
|
| ### TypeScript Types |
|
|
| The library exports TypeScript types for analytics buckets: |
|
|
| ```typescript |
| import type { AnalyticBucket, BucketType, StorageError } from '@supabase/storage-js' |
| |
| // AnalyticBucket type |
| interface AnalyticBucket { |
| id: string |
| type: 'ANALYTICS' |
| format: string |
| created_at: string |
| updated_at: string |
| } |
| ``` |
|
|
| ### Common Patterns |
|
|
| #### Checking if a Bucket Exists |
|
|
| ```typescript |
| async function bucketExists(bucketName: string): Promise<boolean> { |
| const { data, error } = await analytics.listBuckets({ |
| search: bucketName, |
| }) |
| |
| if (error) { |
| console.error('Error checking bucket:', error.message) |
| return false |
| } |
| |
| return data?.some((bucket) => bucket.id === bucketName) ?? false |
| } |
| ``` |
|
|
| #### Creating Bucket with Error Handling |
|
|
| ```typescript |
| async function ensureAnalyticsBucket(bucketName: string) { |
| // Try to create the bucket |
| const { data, error } = await analytics.createBucket(bucketName) |
| |
| if (error) { |
| // Check if bucket already exists (conflict error) |
| if (error.statusCode === '409') { |
| console.log(`Bucket '${bucketName}' already exists`) |
| return { success: true, created: false } |
| } |
| |
| // Other error occurred |
| console.error('Failed to create bucket:', error.message) |
| return { success: false, error } |
| } |
| |
| console.log(`Created new bucket: '${bucketName}'`) |
| return { success: true, created: true, data } |
| } |
| ``` |
|
|
| #### Listing All Buckets with Pagination |
|
|
| ```typescript |
| async function getAllAnalyticsBuckets() { |
| const allBuckets: AnalyticBucket[] = [] |
| let offset = 0 |
| const limit = 100 |
| |
| while (true) { |
| const { data, error } = await analytics.listBuckets({ |
| limit, |
| offset, |
| sortColumn: 'created_at', |
| sortOrder: 'desc', |
| }) |
| |
| if (error) { |
| console.error('Error fetching buckets:', error.message) |
| break |
| } |
| |
| if (!data || data.length === 0) { |
| break |
| } |
| |
| allBuckets.push(...data) |
| |
| // If we got fewer results than the limit, we've reached the end |
| if (data.length < limit) { |
| break |
| } |
| |
| offset += limit |
| } |
| |
| return allBuckets |
| } |
| ``` |
|
|
| ## Vector Embeddings |
|
|
| Supabase Storage provides built-in support for storing and querying high-dimensional vector embeddings, powered by S3 Vectors. This enables semantic search, similarity matching, and AI-powered applications without needing a separate vector database. |
|
|
| > **Note:** Vector embeddings functionality is available in `@supabase/storage-js` v2.76 and later. |
|
|
| ### Features |
|
|
| - **Vector Buckets**: Organize vector indexes into logical containers |
| - **Vector Indexes**: Define schemas with configurable dimensions and distance metrics |
| - **Batch Operations**: Insert/update/delete up to 500 vectors per request |
| - **Similarity Search**: Query for nearest neighbors using cosine, euclidean, or dot product distance |
| - **Metadata Filtering**: Store and filter vectors by arbitrary JSON metadata |
| - **Pagination**: Efficiently scan large vector datasets |
| - **Parallel Scanning**: Distribute scans across multiple workers for high throughput |
| - **Cross-platform**: Works in Node.js, browsers, and edge runtimes |
|
|
| ### Quick Start |
|
|
| You can access vector functionality in three ways, depending on your use case: |
|
|
| #### Option 1: Via Supabase Client (Most Common) |
|
|
| If you're using the full Supabase client: |
|
|
| ```typescript |
| import { createClient } from '@supabase/supabase-js' |
| |
| const supabase = createClient('https://your-project.supabase.co', 'your-publishable-key') |
| |
| // Access vector operations through storage |
| const vectors = supabase.storage.vectors |
| |
| // Create a vector bucket |
| await vectors.createBucket('embeddings-prod') |
| |
| // Create an index |
| const bucket = vectors.from('embeddings-prod') |
| await bucket.createIndex({ |
| indexName: 'documents-openai', |
| dataType: 'float32', |
| dimension: 1536, |
| distanceMetric: 'cosine', |
| }) |
| |
| // Insert vectors |
| const index = bucket.index('documents-openai') |
| await index.putVectors({ |
| vectors: [ |
| { |
| key: 'doc-1', |
| data: { float32: [0.1, 0.2, 0.3 /* ...1536 dimensions */] }, |
| metadata: { title: 'Introduction', category: 'docs' }, |
| }, |
| ], |
| }) |
| |
| // Query similar vectors |
| const { data, error } = await index.queryVectors({ |
| queryVector: { float32: [0.15, 0.25, 0.35 /* ...1536 dimensions */] }, |
| topK: 5, |
| returnDistance: true, |
| returnMetadata: true, |
| }) |
| |
| if (data) { |
| data.matches.forEach((match) => { |
| console.log(`${match.key}: distance=${match.distance}`) |
| console.log('Metadata:', match.metadata) |
| }) |
| } |
| ``` |
|
|
| #### Option 2: Via StorageClient |
|
|
| If you're using the standalone `StorageClient` for storage operations, access vectors through the `vectors` property: |
|
|
| ```typescript |
| import { StorageClient } from '@supabase/storage-js' |
| |
| const storageClient = new StorageClient('https://your-project.supabase.co/storage/v1', { |
| apikey: 'YOUR_API_KEY', |
| Authorization: 'Bearer YOUR_TOKEN', |
| }) |
| |
| // Access vector operations |
| const vectors = storageClient.vectors |
| |
| // Use the same API as shown in Option 1 |
| await vectors.createBucket('embeddings-prod') |
| const bucket = vectors.from('embeddings-prod') |
| // ... rest of operations |
| ``` |
|
|
| #### Option 3: Standalone Vector Client |
|
|
| For vector-only applications that don't need regular file storage operations: |
|
|
| ```typescript |
| import { StorageVectorsClient } from '@supabase/storage-js' |
| |
| // Initialize standalone vector client |
| const vectorClient = new StorageVectorsClient('https://your-project.supabase.co/storage/v1', { |
| headers: { Authorization: 'Bearer YOUR_TOKEN' }, |
| }) |
| |
| // Use the same API as shown in Option 1 |
| await vectorClient.createBucket('embeddings-prod') |
| const bucket = vectorClient.from('embeddings-prod') |
| // ... rest of operations |
| ``` |
|
|
| > **When to use each approach:** |
| > |
| > - **Option 1**: When using other Supabase features (auth, database, realtime) |
| > - **Option 2**: When working with both file storage and vectors |
| > - **Option 3**: For dedicated vector-only applications without file storage |
|
|
| ### API Reference |
|
|
| #### Client Initialization |
|
|
| ```typescript |
| const vectorClient = new StorageVectorsClient(url, options?) |
| ``` |
|
|
| **Options:** |
|
|
| - `headers?: Record<string, string>` - Custom HTTP headers (e.g., Authorization) |
| - `fetch?: Fetch` - Custom fetch implementation |
|
|
| #### Vector Buckets |
|
|
| Vector buckets are top-level containers for organizing vector indexes. |
|
|
| ##### Create Bucket |
|
|
| ```typescript |
| const { data, error } = await vectorClient.createBucket('my-bucket') |
| ``` |
|
|
| ##### Get Bucket |
|
|
| ```typescript |
| const { data, error } = await vectorClient.getBucket('my-bucket') |
| console.log('Created at:', new Date(data.vectorBucket.creationTime! * 1000)) |
| ``` |
|
|
| ##### List Buckets |
|
|
| ```typescript |
| const { data, error } = await vectorClient.listBuckets({ |
| prefix: 'prod-', |
| maxResults: 100, |
| }) |
| |
| // Pagination |
| if (data?.nextToken) { |
| const next = await vectorClient.listBuckets({ nextToken: data.nextToken }) |
| } |
| ``` |
|
|
| ##### Delete Bucket |
|
|
| ```typescript |
| // Bucket must be empty (all indexes deleted first) |
| const { error } = await vectorClient.deleteBucket('my-bucket') |
| ``` |
|
|
| #### Vector Indexes |
|
|
| Vector indexes define the schema for embeddings including dimension and distance metric. |
|
|
| ##### Create Index |
|
|
| ```typescript |
| const bucket = vectorClient.from('my-bucket') |
| |
| await bucket.createIndex({ |
| indexName: 'my-index', |
| dataType: 'float32', |
| dimension: 1536, |
| distanceMetric: 'cosine', // 'cosine' | 'euclidean' | 'dotproduct' |
| metadataConfiguration: { |
| nonFilterableMetadataKeys: ['raw_text', 'internal_id'], |
| }, |
| }) |
| ``` |
|
|
| **Distance Metrics:** |
|
|
| - `cosine` - Cosine similarity (normalized dot product) |
| - `euclidean` - Euclidean distance (L2 norm) |
| - `dotproduct` - Dot product similarity |
|
|
| ##### Get Index |
|
|
| ```typescript |
| const { data, error } = await bucket.getIndex('my-index') |
| console.log('Dimension:', data?.index.dimension) |
| console.log('Distance metric:', data?.index.distanceMetric) |
| ``` |
|
|
| ##### List Indexes |
|
|
| ```typescript |
| const { data, error } = await bucket.listIndexes({ |
| prefix: 'documents-', |
| maxResults: 100, |
| }) |
| ``` |
|
|
| ##### Delete Index |
|
|
| ```typescript |
| // Deletes index and all its vectors |
| await bucket.deleteIndex('my-index') |
| ``` |
|
|
| #### Vector Operations |
|
|
| ##### Insert/Update Vectors (Upsert) |
|
|
| ```typescript |
| const index = vectorClient.from('my-bucket').index('my-index') |
| |
| await index.putVectors({ |
| vectors: [ |
| { |
| key: 'unique-id-1', |
| data: { |
| float32: [ |
| /* 1536 numbers */ |
| ], |
| }, |
| metadata: { |
| title: 'Document Title', |
| category: 'technical', |
| page: 1, |
| }, |
| }, |
| // ... up to 500 vectors per request |
| ], |
| }) |
| ``` |
|
|
| **Limitations:** |
|
|
| - 1-500 vectors per request |
| - Vectors must match index dimension |
| - Keys must be unique within index |
|
|
| ##### Get Vectors by Key |
|
|
| ```typescript |
| const { data, error } = await index.getVectors({ |
| keys: ['doc-1', 'doc-2', 'doc-3'], |
| returnData: true, // Include embeddings |
| returnMetadata: true, // Include metadata |
| }) |
| |
| data?.vectors.forEach((v) => { |
| console.log(v.key, v.metadata) |
| }) |
| ``` |
|
|
| ##### Query Similar Vectors (ANN Search) |
|
|
| ```typescript |
| const { data, error } = await index.queryVectors({ |
| queryVector: { |
| float32: [ |
| /* 1536 numbers */ |
| ], |
| }, |
| topK: 10, |
| filter: { |
| category: 'technical', |
| published: true, |
| }, |
| returnDistance: true, |
| returnMetadata: true, |
| }) |
| |
| // Results ordered by similarity |
| data?.matches.forEach((match) => { |
| console.log(`${match.key}: distance=${match.distance}`) |
| }) |
| ``` |
|
|
| **Filter Syntax:** |
| The `filter` parameter accepts arbitrary JSON for metadata filtering. Non-filterable keys (configured at index creation) cannot be used in filters but can still be returned. |
|
|
| ##### List/Scan Vectors |
|
|
| ```typescript |
| // Simple pagination |
| let nextToken: string | undefined |
| do { |
| const { data } = await index.listVectors({ |
| maxResults: 500, |
| nextToken, |
| returnMetadata: true, |
| }) |
| |
| console.log('Batch:', data?.vectors.length) |
| nextToken = data?.nextToken |
| } while (nextToken) |
| |
| // Parallel scanning (4 workers) |
| const workers = [0, 1, 2, 3].map(async (segmentIndex) => { |
| const { data } = await index.listVectors({ |
| segmentCount: 4, |
| segmentIndex, |
| returnMetadata: true, |
| }) |
| return data?.vectors || [] |
| }) |
| |
| const results = await Promise.all(workers) |
| const allVectors = results.flat() |
| ``` |
|
|
| **Limitations:** |
|
|
| - `maxResults`: 1-1000 (default: 500) |
| - `segmentCount`: 1-16 |
| - Response may be limited by 1MB size |
|
|
| ##### Delete Vectors |
|
|
| ```typescript |
| await index.deleteVectors({ |
| keys: ['doc-1', 'doc-2', 'doc-3'], |
| // ... up to 500 keys per request |
| }) |
| ``` |
|
|
| ### Error Handling |
|
|
| The library uses a consistent error handling pattern: |
|
|
| ```typescript |
| const { data, error } = await vectorClient.createBucket('my-bucket') |
| |
| if (error) { |
| console.error('Error:', error.message) |
| console.error('Status:', error.status) |
| console.error('Code:', error.statusCode) |
| } |
| ``` |
|
|
| #### Error Codes |
|
|
| | Code | HTTP | Description | |
| | ---------------------------- | ---- | ----------------------- | |
| | `InternalError` | 500 | Internal server error | |
| | `S3VectorConflictException` | 409 | Resource already exists | |
| | `S3VectorNotFoundException` | 404 | Resource not found | |
| | `S3VectorBucketNotEmpty` | 400 | Bucket contains indexes | |
| | `S3VectorMaxBucketsExceeded` | 400 | Bucket quota exceeded | |
| | `S3VectorMaxIndexesExceeded` | 400 | Index quota exceeded | |
|
|
| #### Throwing Errors |
|
|
| You can configure the client to throw errors instead: |
|
|
| ```typescript |
| const vectorClient = new StorageVectorsClient(url, options) |
| vectorClient.throwOnError() |
| |
| try { |
| const { data } = await vectorClient.createBucket('my-bucket') |
| // data is guaranteed to be present |
| } catch (error) { |
| if (error instanceof StorageVectorsApiError) { |
| console.error('API Error:', error.statusCode) |
| } |
| } |
| ``` |
|
|
| ### Advanced Usage |
|
|
| #### Scoped Clients |
|
|
| Create scoped clients for cleaner code: |
|
|
| ```typescript |
| // Bucket-scoped operations |
| const bucket = vectorClient.from('embeddings-prod') |
| await bucket.createIndex({ |
| /* ... */ |
| }) |
| await bucket.listIndexes() |
| |
| // Index-scoped operations |
| const index = bucket.index('documents-openai') |
| await index.putVectors({ |
| /* ... */ |
| }) |
| await index.queryVectors({ |
| /* ... */ |
| }) |
| ``` |
|
|
| #### Custom Fetch |
|
|
| Provide a custom fetch implementation: |
|
|
| ```typescript |
| import { StorageVectorsClient } from '@supabase/storage-js' |
| |
| const vectorClient = new StorageVectorsClient(url, { |
| fetch: customFetch, |
| headers: { |
| /* ... */ |
| }, |
| }) |
| ``` |
|
|
| #### Batch Processing |
|
|
| Process large datasets in batches: |
|
|
| ```typescript |
| async function insertLargeDataset(vectors: VectorObject[]) { |
| const batchSize = 500 |
| |
| for (let i = 0; i < vectors.length; i += batchSize) { |
| const batch = vectors.slice(i, i + batchSize) |
| await index.putVectors({ vectors: batch }) |
| console.log(`Inserted ${i + batch.length}/${vectors.length}`) |
| } |
| } |
| ``` |
|
|
| #### Float32 Validation |
|
|
| Ensure vectors are properly normalized to float32: |
|
|
| ```typescript |
| import { normalizeToFloat32 } from '@supabase/storage-js' |
| |
| const vector = normalizeToFloat32([0.1, 0.2, 0.3 /* ... */]) |
| ``` |
|
|
| ### Type Definitions |
|
|
| The library exports comprehensive TypeScript types: |
|
|
| ```typescript |
| import type { |
| VectorBucket, |
| VectorIndex, |
| VectorData, |
| VectorObject, |
| VectorMatch, |
| VectorMetadata, |
| DistanceMetric, |
| ApiResponse, |
| StorageVectorsError, |
| } from '@supabase/storage-js' |
| ``` |
|
|
| ## Development |
|
|
| This package is part of the [Supabase JavaScript monorepo](https://github.com/supabase/supabase-js). To work on this package: |
|
|
| ### Building |
|
|
| #### Build Scripts Overview |
|
|
| ```bash |
| # Build the package |
| pnpm nx build storage-js |
| |
| # Watch mode for development |
| pnpm nx build storage-js --watch |
| |
| # Generate documentation |
| pnpm nx docs storage-js |
| ``` |
|
|
| ### Testing |
|
|
| **Important:** The storage-js tests require a local Supabase stack running via the Supabase CLI. Docker must be running since the Supabase CLI uses it internally. |
|
|
| #### Prerequisites |
|
|
| 1. **Docker** must be installed and running (used by Supabase CLI internally) |
| 2. **Supabase CLI** — installed automatically via `pnpm exec supabase` |
|
|
| #### Test Scripts Overview |
|
|
| | Script | Description | What it does | |
| | ----------------- | --------------------------------- | ----------------------------------------------------------------- | |
| | `test:storage` | **Complete test workflow** | Runs the full test cycle: clean → start infra → run tests → clean | |
| | `test:suite` | **Jest tests only** | Runs Jest tests with coverage (requires infra to be running) | |
| | `test:infra` | **Start test infrastructure** | Starts Supabase CLI stack (PostgreSQL, Storage API, Kong, etc.) | |
| | `test:clean-post` | **Stop and clean infrastructure** | Stops the Supabase CLI stack | |
|
|
| #### Running Tests |
|
|
| ##### Option 1: Complete Test Run (Recommended) |
|
|
| This handles everything automatically - starting infrastructure, running tests, and cleaning up: |
|
|
| ```bash |
| # From monorepo root |
| pnpm nx test:storage storage-js |
| ``` |
|
|
| This command will: |
|
|
| 1. Stop any existing test containers |
| 2. Build and start fresh test infrastructure |
| 3. Wait for services to be ready |
| 4. Run all Jest tests with coverage |
| 5. Clean up all containers after tests complete |
|
|
| ##### Option 2: Manual Infrastructure Management |
|
|
| Useful for development when you want to run tests multiple times without restarting Docker: |
|
|
| ```bash |
| # Step 1: Start the test infrastructure |
| # From root |
| pnpm nx test:infra storage-js |
| # This starts: PostgreSQL, Storage API, Kong Gateway, and imgproxy |
| |
| # Step 2: Run tests (can run multiple times) |
| pnpm nx test:suite storage-js |
| |
| # Step 3: When done, clean up the infrastructure |
| pnpm nx test:clean-post storage-js |
| ``` |
|
|
| ##### Option 3: Development Mode |
|
|
| For actively developing and debugging tests: |
|
|
| ```bash |
| # Start infrastructure once (from root) |
| pnpm nx test:infra storage-js |
| |
| # Run tests in watch mode |
| pnpm nx test:suite storage-js --watch |
| |
| # Clean up when done |
| pnpm nx test:clean-post storage-js |
| ``` |
|
|
| #### Test Infrastructure Details |
|
|
| The test infrastructure is managed via the Supabase CLI (`pnpm exec supabase start --workdir test`), which starts a local Supabase stack defined by the config in `test/`. This includes PostgreSQL, the Storage API, Kong Gateway, and supporting services. |
|
|
| #### Common Issues and Solutions |
|
|
| | Issue | Solution | |
| | --------------------------------- | ------------------------------------------------------------------------------------------------- | |
| | Port conflicts | Another service is using a required port. Run `pnpm nx test:clean-post storage-js` then try again | |
| | "request failed, reason:" errors | Infrastructure isn't running. Run `pnpm nx test:infra storage-js` first | |
| | Tests fail with connection errors | Ensure Docker is running (Supabase CLI requires Docker) | |
| | Stack already running | Run `pnpm nx test:clean-post storage-js` to stop it before restarting | |
|
|
| #### Understanding Test Failures |
|
|
| - **StorageUnknownError with "request failed"**: Infrastructure not running |
| - **Snapshot failures**: Expected test data has changed — review and update snapshots if needed |
|
|
| ### Contributing |
|
|
| We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details on how to get started. |
|
|
| For major changes or if you're unsure about something, please open an issue first to discuss your proposed changes. |
|
|