mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
## What kind of change does this PR introduce? Docs update <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a "Manage image transformations" guide in Storage docs describing where to find the setting and how to use the Enable Image Transformations toggle in Storage > Settings. Clarifies that disabling the toggle stops all image transformation requests from being processed and includes step‑by‑step user guidance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
721 lines
16 KiB
Plaintext
721 lines
16 KiB
Plaintext
---
|
|
id: 'storage-image-transformations'
|
|
title: 'Storage Image Transformations'
|
|
description: 'Transform images with Storage'
|
|
subtitle: 'Transform images with Storage'
|
|
sidebar_label: 'Image Transformations'
|
|
tocVideo: 'dLqSmxX3r7I'
|
|
---
|
|
|
|
Supabase Storage offers the functionality to optimize and resize images on the fly. Any image stored in your buckets can be transformed and optimized for fast delivery.
|
|
|
|
<Admonition type="note">
|
|
|
|
Image Resizing is currently enabled for [Pro Plan and above](/pricing).
|
|
|
|
</Admonition>
|
|
|
|
## Manage image transformations
|
|
|
|
You can enable or disable Image Transformations for your project from the [**Storage** > **Settings**](/dashboard/project/_/storage/files/settings) section of the Dashboard and toggle the **Enable Image Transformations** option.
|
|
|
|
<Admonition type="note">
|
|
|
|
Disabling Image Transformations prevents any image transformation requests from being processed. This can be useful to prevent unexpected usage or cost if you do not use this feature.
|
|
|
|
</Admonition>
|
|
|
|
## Get a public URL for a transformed image
|
|
|
|
Our client libraries methods like `getPublicUrl` and `createSignedUrl` support the `transform` option. This returns the URL that serves the transformed image.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
supabase.storage.from('bucket').getPublicUrl('image.jpg', {
|
|
transform: {
|
|
width: 500,
|
|
height: 600,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final url = supabase.storage.from('bucket').getPublicUrl(
|
|
'image.jpg',
|
|
transform: const TransformOptions(
|
|
width: 500,
|
|
height: 600,
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let url = try await supabase.storage.from("bucket")
|
|
.getPublicURL(
|
|
path: "image.jpg"
|
|
options: TransformOptions(with: 500, height: 600)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val url = supabase.storage.from("bucket").publicRenderUrl("image.jpg") {
|
|
size(width = 500, height = 600)
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
```python
|
|
url = supabase.storage.from_('avatars').get_public_url(
|
|
'image.jpg',
|
|
{
|
|
'transform': {
|
|
'width': 500,
|
|
'height': 500,
|
|
},
|
|
}
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
An example URL could look like this:
|
|
|
|
```
|
|
https://project_id.supabase.co/storage/v1/render/image/public/bucket/image.jpg?width=500&height=600`
|
|
```
|
|
|
|
## Signing URLs with transformation options
|
|
|
|
To share a transformed image in a private bucket for a fixed amount of time, provide the transform option when you create the signed URL:
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
supabase.storage.from('bucket').createSignedUrl('image.jpg', 60000, {
|
|
transform: {
|
|
width: 200,
|
|
height: 200,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final url = await supabase.storage.from('bucket').createSignedUrl(
|
|
'image.jpg',
|
|
60000,
|
|
transform: const TransformOptions(
|
|
width: 200,
|
|
height: 200,
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let url = try await supabase.storage.from("bucket")
|
|
.createSignedURL(
|
|
path: "image.jpg",
|
|
expiresIn: 60,
|
|
transform: TransformOptions(
|
|
width: 200,
|
|
height: 200
|
|
)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val url = supabase.storage.from("bucket").createSignedUrl("image.jpg", 60.seconds) {
|
|
size(200, 200)
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
The transformation options are embedded into the token attached to the URL — they cannot be changed once signed.
|
|
|
|
## Downloading images
|
|
|
|
To download a transformed image, pass the `transform` option to the `download` function.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
supabase.storage.from('bucket').download('image.jpg', {
|
|
transform: {
|
|
width: 800,
|
|
height: 300,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final data = await supabase.storage.from('bucket').download(
|
|
'image.jpg',
|
|
transform: const TransformOptions(
|
|
width: 800,
|
|
height: 300,
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let data = try await supabase.storage.from("bucket")
|
|
.download(
|
|
path: "image.jpg",
|
|
options: TransformOptions(
|
|
width: 800,
|
|
height: 300
|
|
)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
|
|
transform {
|
|
size(800, 300)
|
|
}
|
|
}
|
|
|
|
//Or on JVM stream directly to a file
|
|
val file = File("image.jpg")
|
|
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
|
|
transform {
|
|
size(800, 300)
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
```python
|
|
response = supabase.storage.from_('bucket').download(
|
|
'image.jpg',
|
|
{
|
|
'width': 800,
|
|
'height': 300,
|
|
},
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
## Automatic image optimization (WebP)
|
|
|
|
When using the image transformation API, Storage will automatically find the best format supported by the client and return that to the client, without any code change. For instance, if you use Chrome when viewing a JPEG image and using transformation options, you'll see that images are automatically optimized as `webp` images.
|
|
|
|
As a result, this will lower the egress that you send to your users and your application will load much faster.
|
|
|
|
<Admonition type="note">
|
|
|
|
We currently only support WebP. AVIF support will come in the near future.
|
|
|
|
</Admonition>
|
|
|
|
**Disabling automatic optimization:**
|
|
|
|
In case you'd like to return the original format of the image and **opt-out** from the automatic image optimization detection, you can pass the `format=origin` parameter when requesting a transformed image, this is also supported in the JavaScript SDK starting from v2.2.0
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
await supabase.storage.from('bucket').download('image.jpeg', {
|
|
transform: {
|
|
width: 200,
|
|
height: 200,
|
|
format: 'origin',
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final data = await supabase.storage.from('bucket').download(
|
|
'image.jpeg',
|
|
transform: const TransformOptions(
|
|
width: 200,
|
|
height: 200,
|
|
format: RequestImageFormat.origin,
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let data = try await supabase.storage.from("bucket")
|
|
.download(
|
|
path: "image.jpg",
|
|
options: TransformOptions(
|
|
width: 200,
|
|
height: 200,
|
|
format: "origin"
|
|
)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
|
|
transform {
|
|
size(200, 200)
|
|
format = ImageTransformation.Format.ORIGIN
|
|
}
|
|
}
|
|
|
|
//Or on JVM stream directly to a file
|
|
val file = File("image.jpg")
|
|
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
|
|
transform {
|
|
size(200, 200)
|
|
format = ImageTransformation.Format.ORIGIN
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
```python
|
|
response = supabase.storage.from_('bucket').download(
|
|
'image.jpeg',
|
|
{
|
|
'transform': {
|
|
'width': 200,
|
|
'height': 200,
|
|
'format': 'origin',
|
|
},
|
|
}
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
## Next.js loader
|
|
|
|
You can use Supabase Image Transformation to optimize your Next.js images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration).
|
|
|
|
To get started, create a `supabase-image-loader.js` file in your Next.js project which exports a default function:
|
|
|
|
```ts
|
|
const projectId = '' // your supabase project id
|
|
|
|
export default function supabaseLoader({ src, width, quality }) {
|
|
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&quality=${quality || 75}`
|
|
}
|
|
```
|
|
|
|
In your `next.config.js` file add the following configuration to instruct Next.js to use our custom loader
|
|
|
|
```js
|
|
module.exports = {
|
|
images: {
|
|
loader: 'custom',
|
|
loaderFile: './supabase-image-loader.js',
|
|
},
|
|
}
|
|
```
|
|
|
|
At this point you are ready to use the `Image` component provided by Next.js
|
|
|
|
```tsx
|
|
import Image from 'next/image'
|
|
|
|
const MyImage = (props) => {
|
|
return <Image src="bucket/image.png" alt="Picture of the author" width={500} height={500} />
|
|
}
|
|
```
|
|
|
|
## Transformation options
|
|
|
|
We currently support a few transformation options focusing on optimizing, resizing, and cropping images.
|
|
|
|
### Optimizing
|
|
|
|
You can set the quality of the returned image by passing a value from 20 to 100 (with 100 being the highest quality) to the `quality` parameter. This parameter defaults to 80.
|
|
|
|
Example:
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
supabase.storage.from('bucket').download('image.jpg', {
|
|
transform: {
|
|
quality: 50,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final data = await supabase.storage.from('bucket').download(
|
|
'image.jpg',
|
|
transform: const TransformOptions(
|
|
quality: 50,
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let data = try await supabase.storage.from("bucket")
|
|
.download(
|
|
path: "image.jpg",
|
|
options: TransformOptions(
|
|
quality: 50
|
|
)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") {
|
|
transform {
|
|
quality = 50
|
|
}
|
|
}
|
|
|
|
//Or on JVM stream directly to a file
|
|
val file = File("image.jpg")
|
|
supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) {
|
|
transform {
|
|
quality = 50
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
```python
|
|
response = supabase.storage.from_('bucket').download(
|
|
'image.jpg',
|
|
{
|
|
'transform': {
|
|
'quality': 50,
|
|
},
|
|
}
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
### Resizing
|
|
|
|
You can use `width` and `height` parameters to resize an image to a specific dimension. If only one parameter is specified, the image will be resized and cropped, maintaining the aspect ratio.
|
|
|
|
### Modes
|
|
|
|
You can use different resizing modes to fit your needs, each of them uses a different approach to resize the image:
|
|
|
|
Use the `resize` parameter with one of the following values:
|
|
|
|
- `cover` : resizes the image while keeping the aspect ratio to fill a given size and crops projecting parts. (default)
|
|
|
|
- `contain` : resizes the image while keeping the aspect ratio to fit a given size.
|
|
|
|
- `fill` : resizes the image without keeping the aspect ratio.
|
|
|
|
Example:
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
const supabase = createClient('your_project_url', 'your_supabase_api_key')
|
|
|
|
// ---cut---
|
|
supabase.storage.from('bucket').download('image.jpg', {
|
|
transform: {
|
|
width: 800,
|
|
height: 300,
|
|
resize: 'contain', // 'cover' | 'fill'
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<$Show if="sdk:dart">
|
|
<TabPanel id="dart" label="Dart">
|
|
|
|
```dart
|
|
final data = supabase.storage.from('bucket').download(
|
|
'image.jpg',
|
|
transform: const TransformOptions(
|
|
width: 800,
|
|
height: 300,
|
|
resize: ResizeMode.contain, // 'cover' | 'fill'
|
|
),
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:swift">
|
|
<TabPanel id="swift" label="Swift">
|
|
|
|
```swift
|
|
let data = try await supabase.storage.from("bucket")
|
|
.download(
|
|
path: "image.jpg",
|
|
options: TransformOptions(
|
|
width: 800,
|
|
height: 300,
|
|
resize: "contain" // "cover" | "fill"
|
|
)
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:kotlin">
|
|
<TabPanel id="kotlin" label="Kotlin">
|
|
|
|
```kotlin
|
|
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
|
|
transform {
|
|
size(800, 300)
|
|
resize = ImageTransformation.Resize.CONTAIN
|
|
}
|
|
}
|
|
|
|
//Or on JVM stream directly to a file
|
|
val file = File("image.jpg")
|
|
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
|
|
transform {
|
|
size(800, 300)
|
|
resize = ImageTransformation.Resize.CONTAIN
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
<$Show if="sdk:python">
|
|
<TabPanel id="python" label="Python">
|
|
|
|
```python
|
|
response = supabase.storage.from_('bucket').download(
|
|
'image.jpg',
|
|
{
|
|
'transform': {
|
|
'width': 800,
|
|
'height': 300,
|
|
'resize': 'contain', # 'cover' | 'fill'
|
|
}
|
|
}
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</$Show>
|
|
</Tabs>
|
|
|
|
### Limits
|
|
|
|
- Width and height must be an integer value between 1-2500.
|
|
- The image size cannot exceed 25MB.
|
|
- The image resolution cannot exceed 50MP.
|
|
|
|
### Supported image formats
|
|
|
|
| Format | Extension | Source | Result |
|
|
| ------ | --------- | ------ | ------ |
|
|
| PNG | `png` | ☑️ | ☑️ |
|
|
| JPEG | `jpg` | ☑️ | ☑️ |
|
|
| WebP | `webp` | ☑️ | ☑️ |
|
|
| AVIF | `avif` | ☑️ | ☑️ |
|
|
| GIF | `gif` | ☑️ | ☑️ |
|
|
| ICO | `ico` | ☑️ | ☑️ |
|
|
| SVG | `svg` | ☑️ | ☑️ |
|
|
| HEIC | `heic` | ☑️ | ❌ |
|
|
| BMP | `bmp` | ☑️ | ☑️ |
|
|
| TIFF | `tiff` | ☑️ | ☑️ |
|
|
|
|
<$Show if="billing:all">
|
|
<$Partial path="billing/pricing/pricing_storage_image_transformations.mdx" />
|
|
</$Show>
|
|
|
|
## Self hosting
|
|
|
|
Our solution to image resizing and optimization can be self-hosted as with any other Supabase product. Under the hood we use [imgproxy](https://imgproxy.net/)
|
|
|
|
#### imgproxy configuration:
|
|
|
|
Deploy an imgproxy container with the following configuration:
|
|
|
|
```yaml
|
|
imgproxy:
|
|
image: darthsim/imgproxy
|
|
environment:
|
|
- IMGPROXY_ENABLE_WEBP_DETECTION=true
|
|
- IMGPROXY_JPEG_PROGRESSIVE=true
|
|
```
|
|
|
|
Note: make sure that this service can only be reachable within an internal network and not exposed to the public internet
|
|
|
|
#### Storage API configuration:
|
|
|
|
Once [imgproxy](https://imgproxy.net/) is deployed we need to configure a couple of environment variables in your self-hosted [`storage-api`](https://github.com/supabase/storage-api) service as follows:
|
|
|
|
```shell
|
|
ENABLE_IMAGE_TRANSFORMATION=true
|
|
IMGPROXY_URL=yourinternalimgproxyurl.internal.com
|
|
```
|
|
|
|
{/* Finish with a video. This also appears in the Sidebar via the "tocVideo" metadata */}
|
|
|
|
<div className="video-container">
|
|
<iframe
|
|
src="https://www.youtube-nocookie.com/embed/dLqSmxX3r7I"
|
|
frameBorder="1"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
></iframe>
|
|
</div>
|