Skip to content
Merged
Show file tree
Hide file tree
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
20 changes: 19 additions & 1 deletion apps/design-system/content/docs/icons.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
---
title: Icons
description: Icons system breakdown. Copy values of Icons.
description: Icons make actions and navigation across Supabase easier.
---

## Principles

1. Paired: Icons should accompany text, as they aren’t often obvious enough on their own.
2. Clear: Icons should be legible at small sizes and unembellished. Let the text do the heavy lifting.
3. Consistent: Use the same icons for similar actions throughout Supabase. This makes the app easier to use.

## Tints

Destructive actions, such as deleting an API key, don’t need to be [tinted](color-usage#text) with `text-destructive` because there should be a confirmation dialog as a failsafe right after.

## UI Icons

We rely on Lucide icons for most of our UI icons.

## Custom Icons

Tap on an icon below to copy the JSX, SVG, or import path.

<Icons />
7 changes: 2 additions & 5 deletions apps/docs/content/guides/storage/buckets/creating-buckets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,8 @@ supabase.storage.create_bucket(
## Restricting uploads

When creating a bucket you can add additional configurations to restrict the type or size of files you want this bucket to contain.
For example, imagine you want to allow your users to upload only images to the `avatars` bucket and the size must not be greater than 1MB.

You can achieve the following by providing: `allowedMimeTypes` and `maxFileSize`
For example, imagine you want to allow your users to upload only images to the `avatars` bucket and the size must not be greater than 1MB. You can achieve the following by providing `allowedMimeTypes` and `maxFileSize`:

```js
import { createClient } from '@supabase/supabase-js'
Expand All @@ -114,6 +113,4 @@ const { data, error } = await supabase.storage.createBucket('avatars', {
})
```

If an upload request doesn't meet the above restrictions it will be rejected.

For more information check [File Limits](/docs/guides/storage/uploads/file-limits) Section.
If an upload request doesn't meet the above restrictions it will be rejected. See [File Limits](/docs/guides/storage/uploads/file-limits) for more information.
6 changes: 3 additions & 3 deletions apps/docs/content/guides/storage/uploads/file-limits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sidebar_label: 'Limits'

## Global file size

You can set the max file size across all your buckets by setting this global value in the dashboard [here](https://supabase.com/dashboard/project/_/storage/settings). For Free projects, the limit can't exceed 50 MB. On the Pro Plan and up, you can set this value to up to 500 GB. If you need more than 500 GB, [contact us](https://supabase.com/dashboard/support/new).
You can set the maximum file size across all your buckets by setting the _Global file size limit_ value in your [Storage Settings](https://supabase.com/dashboard/project/_/storage/settings). For Free projects, the limit can't exceed 50 MB. On the Pro Plan and up, you can set this value to up to 500 GB. If you need more than 500 GB, [contact us](https://supabase.com/dashboard/support/new).

| Plan | Max File Size Limit |
| ---------- | ------------------- |
Expand All @@ -23,8 +23,8 @@ This option is a global limit, which applies to all your buckets.

</Admonition>

Additionally, you can specify the max file size on a per [bucket level](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) but it can't be higher than this global limit. As a good practice, the global limit should be set to the highest possible file size that your application accepts, and apply per bucket limits.
Additionally, you can specify the maximum file size on a per [bucket level](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) but it can't be higher than this global limit. As a good practice, the global limit should be set to the highest possible file size that your application accepts, with smaller per-bucket limits set as needed.

## Per bucket restrictions

You can have different restrictions on a per bucket level such as restricting the file types (e.g. `pdf`, `images`, `videos`) or the max file size, which should be lower than the global limit. To apply these limit on a bucket level see [Creating Buckets](/docs/guides/storage/buckets/creating-buckets#restricting-uploads).
You can have different restrictions on a per bucket level such as restricting the file types (e.g. `pdf`, `images`, `videos`) or the maximum file size, which should be lower than the global limit. To apply these limit on a bucket level see [Creating Buckets](/docs/guides/storage/buckets/creating-buckets#restricting-uploads).
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
---
title = "Upload file size restrictions"
github_url = "https://github.com/orgs/supabase/discussions/27431"
date_created = "2024-06-20T23:15:48+00:00"
date_created = "2025-09-03T23:15:48+00:00"
topics = [ "storage" ]
keywords = [ "upload", "file", "size", "restriction" ]
database_id = "3b0aa0f2-9608-4691-94d6-4fca8a32130e"
---

You can view the max permissible upload size for your plan in the [docs](https://supabase.com/docs/guides/storage/uploads/file-limits).
You can view the maximum file upload size from the [docs](https://supabase.com/docs/guides/storage/uploads/file-limits). It differs based on your plan and on any bucket or global limits you might have already set.

## There are two ways to control the max upload sizes:
## Edit the maximum file upload size

The first way is through the [global storage settings](https://supabase.com/dashboard/project/_/storage/settings):
There are two ways to edit the maximum file upload size:

<img
width="920"
alt="Screenshot 2024-06-20 at 7 06 57 PM"
src="https://github.com/supabase/supabase/assets/91111415/c33acc4a-efd8-4746-ac98-ddc71e17f8f1"
/>
- Global file upload size limit
- Bucket-level file upload size limit

The second way is at the [bucket level](https://supabase.com/dashboard/project/_/storage/buckets/)
### Global file upload size limit

1. Edit a bucket's configurations:
To edit the global file upload size limit, change the value of _Global file size limit_ in the top section of [Storage Settings](https://supabase.com/dashboard/project/_/storage/settings).

<img
width="355"
alt="Screenshot 2024-06-20 at 7 07 48 PM"
src="https://github.com/supabase/supabase/assets/91111415/51719d9f-3644-40ed-9aaa-8b21fff41634"
/>
If they are set higher than your new global limit, you’ll be asked to edit or remove any existing bucket-level limits first. The global limit takes precedence.

2. Change the file upload restriction if set:
<img
width="340"
alt="Screenshot 2024-06-20 at 7 07 56 PM"
src="https://github.com/supabase/supabase/assets/91111415/917f3cf6-81c4-444f-9ac1-f26eec5eac0c"
/>
### Bucket-level file upload size limit

## Different upload methods impose file size restrictions:
Follow these steps to edit the maximum file upload size at the bucket level:

- [Standard uploads can only transfer up to 5GBs](https://supabase.com/docs/guides/storage/uploads/standard-uploads?queryGroups=language&language=js). However, for files above 6MB, the below methods are more performant and reliable
1. Open a bucket in the [Storage Buckets](https://supabase.com/dashboard/project/_/storage/buckets/) page.
2. Select the overflow menu (three vertical dots icon), and then select _Edit bucket_.
3. Enable _Restrict file size_ and then set the file size limit for that bucket.

You won’t be able to set a limit that is greater than the global file upload size limit, as that takes precedence.

## File size restrictions

Upload methods impose differing file size restrictions:

- [Standard uploads can only transfer up to 5 GBs](https://supabase.com/docs/guides/storage/uploads/standard-uploads?queryGroups=language&language=js). However, for files above 6MB, the below methods are more performant and reliable.
- [Resumable](https://supabase.com/docs/guides/storage/uploads/resumable-uploads) and [S3](https://supabase.com/docs/guides/storage/uploads/resumable-uploads) uploads can support transfers up to 50GB in size.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps
},
}}
>
<Trash2 className="size-4 text-destructive" strokeWidth={1.5} /> Delete API key
<Trash2 size={14} strokeWidth={1.5} /> Delete API key
</DropdownMenuItemTooltip>
<TextConfirmModal
visible={isOpen}
Expand Down
122 changes: 49 additions & 73 deletions apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { AnimatePresence, motion } from 'framer-motion'
import { Eye } from 'lucide-react'

import { Eye, EyeOff } from 'lucide-react'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'

Expand Down Expand Up @@ -67,13 +67,13 @@ export function ApiKeyPill({
}
}, [show, data?.api_key, projectRef, queryClient, apiKey.id])

async function onSubmitShow() {
async function onSubmitToggle() {
// Don't reveal key if not allowed or loading
if (isSecret && !canManageSecretKeys) return
if (isLoadingPermission) return

// This will enable the API key query to fetch and reveal the key
setShowState(true)
// Toggle the show state
setShowState(!show)
}

async function onCopy() {
Expand Down Expand Up @@ -108,74 +108,50 @@ export function ApiKeyPill({

return (
<>
<AnimatePresence mode="wait" initial={false}>
<div
className={cn(
InputVariants({ size: 'tiny' }),
'flex-1 grow gap-0 font-mono rounded-full',
isSecret ? 'overflow-hidden' : '',
show ? 'ring-1 ring-foreground-lighter ring-opacity-50' : 'ring-0 ring-opacity-0',
'transition-all',
'max-w-[100px] sm:max-w-[140px] md:max-w-[180px] lg:max-w-[340px]',
'cursor-text',
'relative'
)}
style={{ userSelect: 'all' }}
>
{isSecret ? (
<>
<span>{apiKey?.api_key.slice(0, 15)}</span>
<motion.span
key={show ? 'shown' : 'hidden'}
initial={{ opacity: 0, y: show ? 16 : -16 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: show ? 16 : -16 }}
transition={{
duration: 0.1,
y: { type: 'spring', stiffness: 1000, damping: 55 },
}}
>
{show && data?.api_key ? data?.api_key.slice(15) : '••••••••••••••••'}
</motion.span>
</>
) : (
<span>{apiKey?.api_key}</span>
)}
</div>
</AnimatePresence>

{/* Reveal button - only shown for secret keys and when not already revealed */}
<div
className={cn(
InputVariants({ size: 'tiny' }),
'w-[100px] sm:w-[140px] md:w-[180px] lg:w-[340px] gap-0 font-mono rounded-full',
isSecret ? 'overflow-hidden' : '',
show ? 'ring-1 ring-foreground-lighter ring-opacity-50' : 'ring-0 ring-opacity-0',
'transition-all',
'cursor-text',
'relative'
)}
style={{ userSelect: 'all' }}
>
{isSecret ? (
<>
<span>{apiKey?.api_key.slice(0, 15)}</span>
<span>{show && data?.api_key ? data?.api_key.slice(15) : '••••••••••••••••'}</span>
</>
) : (
<span>{apiKey?.api_key}</span>
)}
</div>

{/* Toggle button */}
{isSecret && (
<AnimatePresence initial={false}>
{!show && (
<motion.div
initial={{ opacity: 0, scale: 1, width: 0 }}
animate={{ opacity: 1, scale: 1, width: 'auto' }}
exit={{ opacity: 0, scale: 1, width: 0 }}
transition={{ duration: 0.12 }}
style={{ overflow: 'hidden' }}
>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="outline"
className="rounded-full px-2 pointer-events-auto cursor-default"
icon={<Eye strokeWidth={2} />}
onClick={onSubmitShow}
disabled={isRestricted}
/>
</TooltipTrigger>
<TooltipContent side="bottom">
{isRestricted
? 'You need additional permissions to reveal secret API keys'
: isLoadingPermission
? 'Loading permissions...'
: 'Reveal API key'}
</TooltipContent>
</Tooltip>
</motion.div>
)}
</AnimatePresence>
<Tooltip>
<TooltipTrigger asChild>
<Button
type="outline"
className="rounded-full px-2 pointer-events-auto"
icon={show ? <EyeOff strokeWidth={2} /> : <Eye strokeWidth={2} />}
onClick={onSubmitToggle}
disabled={isRestricted}
/>
</TooltipTrigger>
<TooltipContent side="bottom">
{isRestricted
? 'You need additional permissions to reveal secret API keys'
: isLoadingPermission
? 'Loading permissions...'
: show
? 'Hide API key'
: 'Reveal API key'}
</TooltipContent>
</Tooltip>
)}

<Tooltip>
Expand All @@ -184,7 +160,7 @@ export function ApiKeyPill({
type="default"
asyncText={onCopy}
iconOnly
className="rounded-full px-2 pointer-events-auto cursor-default"
className="rounded-full px-2 pointer-events-auto"
disabled={isRestricted || isLoadingPermission}
/>
</TooltipTrigger>
Expand Down
38 changes: 29 additions & 9 deletions apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import CopyButton from 'components/ui/CopyButton'
import { FormHeader } from 'components/ui/Forms/FormHeader'
import { useAPIKeysQuery } from 'data/api-keys/api-keys-query'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { cn, EyeOffIcon, Input_Shadcn_, Skeleton, WarningIcon } from 'ui'
import {
cn,
EyeOffIcon,
Input_Shadcn_,
Skeleton,
WarningIcon,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'

// to add in later with follow up PR
// import CreatePublishableAPIKeyDialog from './CreatePublishableAPIKeyDialog'
Expand Down Expand Up @@ -47,14 +56,25 @@ export const PublishableAPIKeys = () => {
<span className="text-sm">Publishable key</span>
<div className="flex items-center gap-2">
<ApiKeyInput />
<CopyButton
iconOnly
size="tiny"
type="default"
className="px-2 rounded-full"
disabled={isPermissionsLoading || isLoadingApiKeys || !canReadAPIKeys}
text={apiKey?.api_key}
/>
<Tooltip>
<TooltipTrigger asChild>
<CopyButton
iconOnly
size="tiny"
type="default"
className="px-2 rounded-full"
disabled={isPermissionsLoading || isLoadingApiKeys || !canReadAPIKeys}
text={apiKey?.api_key}
/>
</TooltipTrigger>
<TooltipContent side="bottom">
{!canReadAPIKeys
? 'You need additional permissions to copy publishable keys'
: isLoadingApiKeys
? 'Loading permissions...'
: 'Copy publishable key'}
</TooltipContent>
</Tooltip>
</div>
</div>
{error && canReadAPIKeys ? (
Expand Down
7 changes: 3 additions & 4 deletions apps/studio/state/storage-explorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1009,13 +1009,12 @@ function createStorageExplorerState({
Failed to upload {numberOfFilesRejected} file{numberOfFilesRejected > 1 ? 's' : ''} as{' '}
{numberOfFilesRejected > 1 ? 'their' : 'its'} size
{numberOfFilesRejected > 1 ? 's are' : ' is'} beyond the global upload limit of{' '}
{value}
{unit}.
{value} {unit}.
</p>
<p className="text-foreground-light">
You can change the global file size upload limit in{' '}
<InlineLink href={`/project/${state.projectRef}/storage/settings`}>
Storage settings
Storage Settings
</InlineLink>
.
</p>
Expand Down Expand Up @@ -1216,7 +1215,7 @@ function createStorageExplorerState({
case 413:
// Payload too large
toast.error(
`Failed to upload ${file.name}: File size exceeds the bucket upload limit.`
`Failed to upload ${file.name}: File size exceeds the bucket file size limit.`
)
break
case 409:
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ services:

functions:
container_name: supabase-edge-functions
image: supabase/edge-runtime:v1.67.4
image: supabase/edge-runtime:v1.69.6
restart: unless-stopped
volumes:
- ./volumes/functions:/home/deno/functions:Z
Expand Down
Loading