Skip to content

Improve storage documentation #558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 145 additions & 63 deletions docs/pages/storage/mutations.mdx
Original file line number Diff line number Diff line change
@@ -1,22 +1,70 @@
import { Callout, Tabs } from 'nextra/components';
import { LinkedTabs } from '@/components/linked-tabs';

# Mutations
# Storage Mutations

The cache helpers query hooks wrap the mutation hooks of the cache libraries and automatically revalidate the relevant queries across your app. For example, if you list all files in `dirname/` with `useDirectory`, and upload a new file into `dirname/file.jpg`, the query is revalidated after the upload succeeded. The same goes for file removals.
The **Supabase Cache Helpers** provide optimized mutation hooks for interacting with Supabase Storage using **React Query** or **SWR**. These hooks automatically revalidate related queries upon execution, ensuring a seamless cache update when uploading or removing files.

## `useUpload`
---

Upload a list of files. Accepts `File[]`, `FileList` and `{ data: ArrayBuffer; type: string; name: string }` objects. The latter is primarily useful for uploading from React Native. By default, the path to which the file is uploaded to is computed with
### **Mutation Configuration Options**

```ts
const defaultBuildFileName: BuildFileNameFn = ({ path, fileName }) =>
[path, fileName].filter(Boolean).join("/");
import { UseMutationOptions } from '@tanstack/react-query';

type MutationConfig<TData, TError, TVariables> = Omit<
UseMutationOptions<TData, TError, TVariables>,
'mutationFn'
>;
```

A custom `BuildFileNameFn` can be passed to `config.buildFileName`.
These options are available for all mutations:

| Option | Type | Description |
|---------------|---------------------------------------------------------------------|-------------|
| `onMutate` | `(variables: TVariables) => Promise<TData> \| TData` | Called before the mutation function executes. Can return a context value used in `onError` or `onSettled`. |
| `onError` | `(error: TError, variables: TVariables, context?: unknown) => void` | Called if the mutation fails. |
| `onSuccess` | `(data: TData, variables: TVariables, context?: unknown) => void` | Called if the mutation succeeds. |
| `onSettled` | `(data?: TData, error?: TError, variables?: TVariables, context?: unknown) => void` | Called when mutation finishes, regardless of success or failure. |
| `retry` | `boolean \| number \| (failureCount: number, error: TError) => boolean` | Number of retry attempts before failing. |
| `retryDelay` | `(failureCount: number, error: TError) => number \| number` | Delay between retries. |
| `mutationKey` | `unknown` | A unique key to track the mutation state. |
| `meta` | `Record<string, unknown>` | Additional metadata to associate with the mutation. |

These options apply to:

- `useUpload`
- `useRemoveDirectory`
- `useRemoveFiles`

For more details, refer to the [React Query `useMutation` documentation](https://tanstack.com/query/latest/docs/react/reference/useMutation).

---

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
## **Uploading Files**

### `useUpload`

Uploads a **list of files** to a specified directory in Supabase Storage.

#### **Parameters:**
```ts
useUpload(
fileApi: StorageFileApi, // Supabase storage API instance
config?: UploadFetcherConfig & UseMutationOptions<UploadFileResponse[], StorageError, UseUploadInput>
)
```

#### **`UseUploadInput` Options:**
```ts
type UseUploadInput = {
files: FileList | (File | FileInput)[]; // Files to upload
path?: string; // Optional storage path
};
```

#### **Example Usage:**
<LinkedTabs items={['SWR', 'React Query']} id="useUpload">
<Tabs.Tab>
```tsx
import { useUpload } from '@supabase-cache-helpers/storage-swr';
@@ -27,128 +75,162 @@ A custom `BuildFileNameFn` can be passed to `config.buildFileName`.
process.env.SUPABASE_ANON_KEY
);

const dirName = 'my-directory';

function Page() {
const { trigger: upload } = useUpload(
client.storage.from('private_contact_files'),
{ buildFileName: ({ fileName, path }) => `${dirName}/${path}/${fileName}` }
);
return <div>...</div>;

function handleUpload(files) {
upload({ files, path: 'user-123' });
}

}
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useUpload } from '@supabase-cache-helpers/storage-react-query';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY
);

const dirName = 'my-directory';

function Page() {
const { mutateAsync: upload } = useUpload(
client.storage.from('private_contact_files'),
{ buildFileName: ({ fileName, path }) => `${dirName}/${path}/${fileName}` }
);
return <div>...</div>;

async function handleUpload(files) {
await upload({ files, path: 'user-123' });
}
}
```

</Tabs.Tab>
</LinkedTabs>

## `useRemoveDirectory`
---

## **Removing a Directory**

### `useRemoveDirectory`

Remove all files in a directory. Does not delete files recursively.
Removes all files **inside a directory** but does **not** delete subdirectories recursively.

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
#### **Parameters:**
```ts
useRemoveDirectory(
fileApi: StorageFileApi, // Supabase storage API instance
config?: UseMutationOptions<FileObject[], StorageError, string>
)
```

#### **Example Usage:**
<LinkedTabs items={['SWR', 'React Query']} id="useRemoveDirectory">
<Tabs.Tab>
```tsx
import { useRemoveDirectory } from '@supabase-cache-helpers/storage-swr';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
function Page() {}
const { trigger: remove } = useRemoveDirectory(
client.storage.from('private_contact_files')
);
return <div>...</div>;

function handleRemove() {
remove('user-123');
}
}
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useRemoveDirectory } from '@supabase-cache-helpers/storage-react-query';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { mutateAsync: remove } = useRemoveDirectory(
client.storage.from('private_contact_files')
);
return <div>...</div>;

async function handleRemove() {
await remove('user-123');
}
}
```

</Tabs.Tab>
</LinkedTabs>

## `useRemoveFiles`
---

Remove a list of files by paths.
## **Removing Specific Files**

### `useRemoveFiles`

Removes **specific files** in Supabase Storage by their paths.

#### **Parameters:**
```ts
useRemoveFiles(
fileApi: StorageFileApi, // Supabase storage API instance
config?: UseMutationOptions<FileObject[], StorageError, string[]>
)
```

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
#### **Example Usage:**
<LinkedTabs items={['SWR', 'React Query']} id="useRemoveFiles">
<Tabs.Tab>
```tsx
import { useRemoveFiles } from '@supabase-cache-helpers/storage-swr';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { trigger: remove } = useRemoveFiles(
client.storage.from('private_contact_files')
);
return <div>...</div>;
const { trigger: remove } = useRemoveFiles(client.storage.from('private_contact_files'));

function handleRemove() {
remove(['user-123/file1.png', 'user-123/file2.jpg']);
}
}
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useRemoveFiles } from '@supabase-cache-helpers/storage-react-query';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { mutateAsync: remove } = useRemoveFiles(
client.storage.from('private_contact_files')
);
return <div>...</div>;
const { mutateAsync: remove } = useRemoveFiles(client.storage.from('private_contact_files'));

async function handleRemove() {
await remove(['user-123/file1.png', 'user-123/file2.jpg']);
}
}
```

```
</Tabs.Tab>
</LinkedTabs>

---

### **Return Types for Mutations**

| Hook | Return Type | Description |
|--------------------|------------|-------------|
| `useUpload` | `UploadFileResponse[]` | List of uploaded files with paths and potential errors |
| `useRemoveDirectory` | `FileObject[]` | List of removed files in the directory |
| `useRemoveFiles` | `FileObject[]` | List of removed files by path |

#### **`UploadFileResponse` Definition:**
```ts
type UploadFileResponse = {
path: string; // The uploaded file path
error?: StorageError; // Error if applicable
};
```

#### **`StorageError` Definition:**
```ts
export interface StorageError {
message: string; // Error message
statusCode?: number; // HTTP status code (if applicable)
error?: string; // Additional error info (if available)
}
```
289 changes: 142 additions & 147 deletions docs/pages/storage/queries.mdx
Original file line number Diff line number Diff line change
@@ -1,207 +1,202 @@
import { Callout, Tabs } from 'nextra/components';
import { LinkedTabs } from '@/components/linked-tabs';

# Queries
# Storage Queries

The cache helpers query hooks wrap the data fetching hooks of the cache libraries and pass both the cache key and the fetcher function from the Storage query. For example,
The **Supabase Cache Helpers** provide optimized query hooks for interacting with Supabase Storage using **React Query** or **SWR**. These hooks streamline caching, signed URL generation, and storage operations.

---

## **Query Configuration Options**

Most query hooks accept a configuration object based on React Query's `UseQueryOptions` or SWR's equivalent.

```ts
useFileUrl(
client.storage.from("public_contact_files"),
"postgrest-storage-file-url-94/1.jpg",
"public",
);
// Base type for query configuration
type QueryConfig<TData> = Omit<UseQueryOptions<TData | undefined, StorageError>, 'queryKey' | 'queryFn'>;
```

is parsed into
### **Common Options**

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
<Tabs.Tab>
` storage$public_contact_files$postgrest-storage-file-url-94/1.jpg `
</Tabs.Tab>
<Tabs.Tab>
` [ "storage", "public_contact_files", "postgrest-storage-file-url-94/1.jpg"
] `
</Tabs.Tab>
</LinkedTabs>
These options are commonly used across all query hooks:

| Option | Type | Description |
|-------------------------|------------------------------------------|-------------|
| `refetchOnWindowFocus` | `boolean` | Refetches data when the window regains focus. Defaults to `true`. |
| `staleTime` | `number` | Time in milliseconds before data is considered stale. |
| `cacheTime` | `number` | Time in milliseconds to cache data before garbage collection. |
| `enabled` | `boolean` | If `false`, disables the query from automatically running. |
| `onSuccess` | `(data: TData) => void` | Callback when the query successfully fetches data. |
| `onError` | `(error: StorageError) => void` | Callback when the query encounters an error. |

For the full list of React Query options, see the [React Query `useQuery` documentation](https://tanstack.com/query/latest/docs/react/reference/useQuery).

### **Additional Options for `useDirectoryFileUrls`**

In addition to the common options, `useDirectoryFileUrls` includes:

```ts
// Extended configuration for useDirectoryFileUrls
type DirectoryFileUrlsConfig = Omit<UseQueryOptions<(FileObject & { url: string })[] | undefined, StorageError>, 'queryKey' | 'queryFn'> &
Pick<URLFetcherConfig, 'expiresIn'>;
```

## `useFileUrl`
| Option | Type | Description |
|------------|---------|-------------|
| `expiresIn` | `number` | Number of seconds before a signed URL expires (only for private storage). |

Supports `private`, and `public` buckets. You can pass `URLFetcherConfig` to configure signed urls, and ensure that a file in a public bucket exists.
Each hook's documentation specifies which options apply.


---

## **Fetching File URLs**

### `useFileUrl`

Fetches the **URL of a file** in Supabase Storage, handling both **private** and **public** files.

#### **Parameters:**
```ts
useFileUrl(
fileApi: StorageFileApi, // Supabase storage API instance
path: string, // Path to the file (e.g., 'dirname/myfile.jpg')
mode: 'public' | 'private', // Storage privacy mode
config?: URLFetcherConfig // Optional fetcher config (see below)
)
```

#### **`URLFetcherConfig` Options:**
```ts
type URLFetcherConfig = {
// For private buckets only, set how long the signed url should be valid
expiresIn?: number;
// For public buckets only, if true queries the file using .list()
// and returns null if file does not exist
ensureExistence?: boolean;
// Triggers the file as a download if set to true. Set this parameter as the name of the file of you want to trigger the download with a different filename.
download?: string | boolean | undefined;
// Transform the asset before serving it to the client.
transform?: TransformOptions | undefined;
expiresIn?: number; // Expiration time for signed URLs (private buckets)
ensureExistence?: boolean; // Check if the file exists (public buckets only)
download?: string | boolean | undefined; // Trigger file download
transform?: TransformOptions | undefined; // Transform image before serving
};
```

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
#### **Return Type:**
```ts
UseQueryResult<string | undefined, StorageError>
```

| Property | Type | Description |
|--------------|-------------------------|-------------|
| `data` | `string \| undefined` | The file URL if found, otherwise `undefined`. |
| `error` | `StorageError \| null` | Error details if the request fails. |

#### **Usage Example:**
<LinkedTabs items={["SWR", "React Query"]} id="useFileUrl">
<Tabs.Tab>
```tsx
import { useFileUrl } from '@supabase-cache-helpers/storage-swr';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);
const client = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);

function Page() {
const { data: url } = useFileUrl(
client.storage.from('public_contact_files'),
'dirname/myfile.jpg',
'public',
{
ensureExistence: true,
revalidateOnFocus: false,
}
{ ensureExistence: true, revalidateOnFocus: false }
);
return <div>{url}</div>;
}
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useFileUrl } from '@supabase-cache-helpers/storage-react-query';
import { createClient } from '@supabase/supabase-js';

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);
const client = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);

function Page() {
const { data: url } = useFileUrl(
client.storage.from('public_contact_files'),
'dirname/myfile.jpg',
'public',
{
ensureExistence: true,
refetchOnWindowFocus: false,
}
{ ensureExistence: true, refetchOnWindowFocus: false }
);
return <div>{url}</div>;
}
```

</Tabs.Tab>
</LinkedTabs>

## `useDirectory`
---

Returns all files in a directory.
## **Fetching Directory Listings**

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
<Tabs.Tab>
```tsx
import { useDirectory } from "@supabase-cache-helpers/storage-swr";
import { createClient } from "@supabase/supabase-js";

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { data: files } = useDirectory(
client.storage.from("private_contact_files"),
dirName,
{
revalidateOnFocus: false,
}
);
return <div>...</div>;
}

````
### `useDirectory`

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useDirectory } from "@supabase-cache-helpers/storage-react-query";
import { createClient } from "@supabase/supabase-js";

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { data: files } = useDirectory(
client.storage.from("private_contact_files"),
dirName,
{
refetchOnWindowFocus: false,
}
);
return <div>...</div>;
}
````
Fetches **all files** in a given **directory** from Supabase Storage.

</Tabs.Tab>
</LinkedTabs>
#### **Parameters:**
```ts
useDirectory(
fileApi: StorageFileApi, // Supabase storage API instance
path: string, // Directory path (e.g., 'dirname/')
config?: UseQueryOptions<FileObject[] | undefined, StorageError> // Optional query config
)
```

## `useDirectoryFileUrls`
#### **Return Type:**
```ts
UseQueryResult<FileObject[] | undefined, StorageError>
```

Convenience hook that returns the files in a directory similar to `useDirectory` but adds the `url` for each.
| Property | Type | Description |
|--------------|-------------------------|-------------|
| `data` | `FileObject[] \| undefined` | List of files in the directory (or `undefined` if none exist). |
| `error` | `StorageError \| null` | Error details if the request fails. |

<LinkedTabs items={['SWR', 'React Query']} id="data-fetcher">
<Tabs.Tab>
```tsx
import { useDirectoryFileUrls } from "@supabase-cache-helpers/storage-swr";
import { createClient } from "@supabase/supabase-js";

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { data: files } = useDirectoryFileUrls(
client.storage.from("private_contact_files"),
dirName,
"private",
{
revalidateOnFocus: false,
}
);
return <div>...</div>;
}

````
#### **FileObject Structure:**
```ts
type FileObject = {
name: string; // File name (e.g., 'image.png')
id: string; // Unique identifier
updated_at: string; // Last modified timestamp
created_at: string; // Creation timestamp
metadata: Record<string, any>; // File metadata (e.g., size, type)
bucket_id: string; // The storage bucket name
};
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
import { useDirectoryFileUrls } from "@supabase-cache-helpers/storage-react-query";
import { createClient } from "@supabase/supabase-js";

const client = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);

function Page() {
const { data: files } = useDirectoryFileUrls(
client.storage.from("private_contact_files"),
dirName,
"private",
{
refetchOnWindowFocus: false,
}
);
return <div>...</div>;
}
````
---

## **Fetching Directory Files with URLs**

### `useDirectoryFileUrls`

Fetches **all files in a directory and their URLs**, combining `useDirectory` with `useFileUrl`.

#### **Parameters:**
```ts
useDirectoryFileUrls(
fileApi: StorageFileApi, // Supabase storage API instance
path: string, // Directory path (e.g., 'dirname/')
mode: 'public' | 'private', // Storage privacy mode
config?: UseQueryOptions<(FileObject & { url: string })[] | undefined, StorageError> // Query config
)
```

#### **Return Type:**
```ts
UseQueryResult<(FileObject & { url: string })[] | undefined, StorageError>
```

| Property | Type | Description |
|--------------|-----------------------------------------|-------------|
| `data` | `(FileObject & { url: string })[] \| undefined` | List of files with their URLs (or `undefined` if no files exist). |
| `error` | `StorageError \| null` | Error details if the request fails. |

#### **FileObjectWithUrl Structure:**
```ts
type FileObjectWithUrl = FileObject & {
url: string; // The signed or public URL for the file
};
```

</Tabs.Tab>
</LinkedTabs>