From b1eafd395732690dd9df940031400192c95a105c Mon Sep 17 00:00:00 2001 From: Fabrizio Date: Tue, 24 Oct 2023 16:46:55 +0100 Subject: [PATCH] docs: Revamp storage docs (#18259) Co-authored-by: Greg Richardson Co-authored-by: Fabrizio Co-authored-by: Francesco Sansalvadore Co-authored-by: Inian --- .../MDX/product_management_sql_template.mdx | 2 +- ...ser_management_quickstart_sql_template.mdx | 2 +- .../NavigationMenu.constants.ts | 58 +- apps/docs/docs/ref/python/release-notes.mdx | 2 +- .../pages/guides/auth/sso/auth-sso-saml.mdx | 28 +- .../guides/functions/kysely-postgres.mdx | 2 +- .../pages/guides/getting-started/features.mdx | 6 +- .../pages/guides/storage/access-control.mdx | 177 -- .../storage/buckets/creating-buckets.mdx | 93 + .../guides/storage/buckets/fundamentals.mdx | 46 + apps/docs/pages/guides/storage/cdn.mdx | 125 -- .../pages/guides/storage/cdn/fundamentals.mdx | 41 + .../docs/pages/guides/storage/cdn/metrics.mdx | 56 + .../pages/guides/storage/cdn/smart-cdn.mdx | 44 + .../guides/storage/debugging/error-codes.mdx | 63 + .../pages/guides/storage/debugging/logs.mdx | 68 + .../guides/storage/production/scaling.mdx | 92 + .../pages/guides/storage/schema/design.mdx | 34 + .../storage/schema/helper-functions.mdx | 69 + .../storage/security/access-control.mdx | 109 + .../guides/storage/serving/downloads.mdx | 59 + .../{ => serving}/image-transformations.mdx | 14 +- .../pages/guides/storage/storage-sample.mdx | 11 - apps/docs/pages/guides/storage/uploads.mdx | 242 --- .../guides/storage/uploads/file-limits.mdx | 41 + .../storage/uploads/resumable-uploads.mdx | 161 ++ .../storage/uploads/standard-uploads.mdx | 131 ++ .../docs/public/img/storage/schema-design.png | Bin 0 -> 89188 bytes apps/docs/public/sitemap.xml | 1921 +++++++++-------- apps/www/_blog/2021-07-27-storage-beta.mdx | 2 +- ...05-supabase-beta-update-september-2022.mdx | 2 +- ...12-13-storage-image-resizing-smart-cdn.mdx | 4 +- .../2022-12-16-launch-week-6-wrap-up.mdx | 4 +- .../2023-02-08-supabase-beta-january-2023.mdx | 2 +- ...-queries-with-postgis-in-ionic-angular.mdx | 4 +- ...023-04-12-storage-v3-resumable-uploads.mdx | 4 +- ...23-06-09-supabase-beta-update-may-2023.mdx | 2 +- .../_blog/2023-08-01-react-native-storage.mdx | 2 +- .../www/components/LaunchWeek/6/lw6_days.json | 4 +- apps/www/components/LaunchWeek/7/lw7_days.ts | 4 +- apps/www/lib/redirects.js | 22 +- apps/www/pages/storage/Storage.tsx | 4 +- .../storage/resumable-upload-uppy/index.html | 2 +- spec/examples/examples.yml | 32 +- spec/supabase_js_v2.yml | 36 +- spec/supabase_kt_v0.yml | 216 +- spec/supabase_py_v2.yml | 258 +-- spec/supabase_swift_v0.yml | 24 +- .../BillingV2/Usage/Usage.constants.tsx | 2 +- 49 files changed, 2479 insertions(+), 1848 deletions(-) delete mode 100644 apps/docs/pages/guides/storage/access-control.mdx create mode 100644 apps/docs/pages/guides/storage/buckets/creating-buckets.mdx create mode 100644 apps/docs/pages/guides/storage/buckets/fundamentals.mdx delete mode 100644 apps/docs/pages/guides/storage/cdn.mdx create mode 100644 apps/docs/pages/guides/storage/cdn/fundamentals.mdx create mode 100644 apps/docs/pages/guides/storage/cdn/metrics.mdx create mode 100644 apps/docs/pages/guides/storage/cdn/smart-cdn.mdx create mode 100644 apps/docs/pages/guides/storage/debugging/error-codes.mdx create mode 100644 apps/docs/pages/guides/storage/debugging/logs.mdx create mode 100644 apps/docs/pages/guides/storage/production/scaling.mdx create mode 100644 apps/docs/pages/guides/storage/schema/design.mdx create mode 100644 apps/docs/pages/guides/storage/schema/helper-functions.mdx create mode 100644 apps/docs/pages/guides/storage/security/access-control.mdx create mode 100644 apps/docs/pages/guides/storage/serving/downloads.mdx rename apps/docs/pages/guides/storage/{ => serving}/image-transformations.mdx (88%) delete mode 100644 apps/docs/pages/guides/storage/storage-sample.mdx delete mode 100644 apps/docs/pages/guides/storage/uploads.mdx create mode 100644 apps/docs/pages/guides/storage/uploads/file-limits.mdx create mode 100644 apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx create mode 100644 apps/docs/pages/guides/storage/uploads/standard-uploads.mdx create mode 100644 apps/docs/public/img/storage/schema-design.png diff --git a/apps/docs/components/MDX/product_management_sql_template.mdx b/apps/docs/components/MDX/product_management_sql_template.mdx index e54e89e1ca4..48ee1ff8112 100644 --- a/apps/docs/components/MDX/product_management_sql_template.mdx +++ b/apps/docs/components/MDX/product_management_sql_template.mdx @@ -15,7 +15,7 @@ insert into storage.buckets (id, name) values ('Product Image', 'Product Image'); -- Set up access controls for storage. --- See https://supabase.com/docs/guides/storage#policy-examples for more details. +-- See https://supabase.com/docs/guides/storage/security/access-control#policy-examples for more details. CREATE POLICY "Enable read access for all users" ON "storage"."objects" AS PERMISSIVE FOR SELECT TO public diff --git a/apps/docs/components/MDX/user_management_quickstart_sql_template.mdx b/apps/docs/components/MDX/user_management_quickstart_sql_template.mdx index ba55c4aeab3..aa552b6af65 100644 --- a/apps/docs/components/MDX/user_management_quickstart_sql_template.mdx +++ b/apps/docs/components/MDX/user_management_quickstart_sql_template.mdx @@ -43,7 +43,7 @@ insert into storage.buckets (id, name) values ('avatars', 'avatars'); -- Set up access controls for storage. --- See https://supabase.com/docs/guides/storage#policy-examples for more details. +-- See https://supabase.com/docs/guides/storage/security/access-control#policy-examples for more details. create policy "Avatar images are publicly accessible." on storage.objects for select using (bucket_id = 'avatars'); diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index d92267b6b1d..1ccc936af4f 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -975,18 +975,64 @@ export const storage: NavMenuConstant = { { name: 'Overview', url: '/guides/storage' }, { name: 'Quickstart', url: '/guides/storage/quickstart' }, { - name: 'Fundamentals', + name: 'Buckets', url: undefined, items: [ - { name: 'Uploading files to Storage', url: '/guides/storage/uploads' }, - { name: 'Image Transformations', url: '/guides/storage/image-transformations' }, - { name: 'How caching works', url: '/guides/storage/cdn' }, + { name: 'Fundamentals', url: '/guides/storage/buckets/fundamentals' }, + { name: 'Creating Buckets', url: '/guides/storage/buckets/creating-buckets' }, ], }, { - name: 'Access and security', + name: 'Security', url: undefined, - items: [{ name: 'Access Control', url: '/guides/storage/access-control' }], + items: [{ name: 'Access Control', url: '/guides/storage/security/access-control' }], + }, + { + name: 'Uploads', + url: undefined, + items: [ + { name: 'Standard Uploads', url: '/guides/storage/uploads/standard-uploads' }, + { name: 'Resumable Uploads', url: '/guides/storage/uploads/resumable-uploads' }, + { name: 'Limits', url: '/guides/storage/uploads/file-limits' }, + ], + }, + { + name: 'Serving', + url: undefined, + items: [ + { name: 'Serving assets', url: '/guides/storage/serving/downloads' }, + { name: 'Image Transformations', url: '/guides/storage/serving/image-transformations' }, + ], + }, + { + name: 'CDN', + url: undefined, + items: [ + { name: 'Fundamentals', url: '/guides/storage/cdn/fundamentals' }, + { name: 'Smart CDN', url: '/guides/storage/cdn/smart-cdn' }, + { name: 'Metrics', url: '/guides/storage/cdn/metrics' }, + ], + }, + { + name: 'Debugging', + url: undefined, + items: [ + { name: 'Logs', url: '/guides/storage/debugging/logs' }, + { name: 'Error Codes', url: '/guides/storage/debugging/error-codes' }, + ], + }, + { + name: 'Schema', + url: undefined, + items: [ + { name: 'Database Design', url: '/guides/storage/schema/design' }, + { name: 'Helper Functions', url: '/guides/storage/schema/helper-functions' }, + ], + }, + { + name: 'Going to production', + url: undefined, + items: [{ name: 'Scaling', url: '/guides/storage/production/scaling' }], }, ], } diff --git a/apps/docs/docs/ref/python/release-notes.mdx b/apps/docs/docs/ref/python/release-notes.mdx index 1deb6705f7e..6d76c242f77 100644 --- a/apps/docs/docs/ref/python/release-notes.mdx +++ b/apps/docs/docs/ref/python/release-notes.mdx @@ -7,4 +7,4 @@ The community is actively working on the library and we will be upgrading the Au ## Storage Transformations -We currently support [image transformations](https://supabase.com/docs/guides/storage/image-transformations) in our storage library. +We currently support [image transformations](https://supabase.com/docs/guides/storage/serving/image-transformations) in our storage library. diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx index b95999b2eb3..f2db1cd981a 100644 --- a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -37,25 +37,25 @@ Please note that SAML 2.0 support is offered on plans Pro and above. Check the [ The number of SAML and SSO acronyms can often be overwhelming. Here's a glossary which you can refer back to at any time: -- **Identity Provider**, **IdP**, or **IDP** +- **Identity Provider**, **IdP**, or **IDP** An identity provider is a service that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project and other applications. It acts as a single source of truth for user identities and access rights. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (GSuite), PingIdentity, OneLogin, and many others. There are also self-hosted and on-prem versions of identity providers, and sometimes they are accessible only by having access to a company VPN or being in a specific building. -- **Service Provider**, **SP** +- **Service Provider**, **SP** This is the software that is asking for user information from an identity provider. In Supabase, this is your project's Auth server. -- **Assertion** +- **Assertion** An assertion is a statement issued by an identity provider that contains information about a user. -- **EntityID** +- **EntityID** A globally unique ID (usually a URL) that identifies an Identity Provider or Service Provider across the world. -- **NameID** +- **NameID** A unique ID (usually an email address) that identifies a user at an Identity Provider. -- **Metadata** +- **Metadata** An XML document that describes the features and configuration of an Identity Provider or Service Provider. It can be as a standalone document or as a URL. Usually (but not always) the `EntityID` is the URL at which you can access the Metadata. -- **Certificate** +- **Certificate** Supabase Auth (the Service Provider) trusts assertions from an Identity Provider based on the signature attached to the assertion. The signature is verified according to the certificate present in the Metadata. -- **Assertion Consumer Service (ACS) URL** +- **Assertion Consumer Service (ACS) URL** This is one of the most important SAML URLs. It is the URL where Supabase Auth will accept assertions from an identity provider. Basically, once the identity provider verifies the user's identity it will redirect to this URL and the redirect request will contain the assertion. -- **Binding (Redirect, POST, or Artifact)** +- **Binding (Redirect, POST, or Artifact)** This is a description of the way an identity provider communicates with Supabase Auth. When using the Redirect binding, the communication occurs using HTTP 301 redirects. When it's `POST`, it's using `POST` requests sent with `
` elements on a page. When using Artifact, it's using a more secure exchange over a Redirect or `POST`. -- **RelayState** +- **RelayState** State used by Supabase Auth to hold information about a request to verify the identity of a user. ## Important SAML 2.0 information @@ -81,11 +81,11 @@ Alternatively, you can use the `supabase sso info --project-ref ` User accounts and identities created via SSO differ from regular (email, phone, password, social login...) accounts in these ways: -- **No automatic linking.** +- **No automatic linking.** Each user account verified using a SSO identity provider will not be automatically linked to existing user accounts in the system. That is, if a user `jane.doe@company.com` had signed up with a password, and then uses their company SSO login with your project, there will be two `jane.doe@company.com` user accounts in the system. -- **Emails are not necessarily unique.** +- **Emails are not necessarily unique.** Given the behavior with no automatic linking, email addresses are no longer a unique identifier for a user account. Please always use the user's UUID to correctly reference user accounts. -- **Sessions may have a maximum duration.** +- **Sessions may have a maximum duration.** Depending on the configuration of the identity provider, a login session established with SSO may forcibly log out a user after a certain period of time. ### Row Level Security @@ -103,7 +103,7 @@ Here are some commonly used statements to extract SSO related information from t -If you use [Multi-Factor Authentication](/guides/auth/auth-mfa) with SSO, the `amr` array may have a different method at index `0`! +If you use [Multi-Factor Authentication](/docs/guides/auth/auth-mfa) with SSO, the `amr` array may have a different method at index `0`! diff --git a/apps/docs/pages/guides/functions/kysely-postgres.mdx b/apps/docs/pages/guides/functions/kysely-postgres.mdx index 3987a77e45d..211a520b4c3 100644 --- a/apps/docs/pages/guides/functions/kysely-postgres.mdx +++ b/apps/docs/pages/guides/functions/kysely-postgres.mdx @@ -16,7 +16,7 @@ export const meta = { > -Supabase Edge Functions can [connect directly to your Postgres database](/guides/functions/connect-to-postgres) to execute SQL queries. [Kysely](https://github.com/kysely-org/kysely#kysely) is a type-safe and autocompletion-friendly typescript SQL query builder. +Supabase Edge Functions can [connect directly to your Postgres database](/docs/guides/functions/connect-to-postgres) to execute SQL queries. [Kysely](https://github.com/kysely-org/kysely#kysely) is a type-safe and autocompletion-friendly typescript SQL query builder. Combining Kysely with Deno Postgres gives you a convenient developer experience for interacting directly with your Postgres database. diff --git a/apps/docs/pages/guides/getting-started/features.mdx b/apps/docs/pages/guides/getting-started/features.mdx index 4d9b63409fd..28e01945063 100755 --- a/apps/docs/pages/guides/getting-started/features.mdx +++ b/apps/docs/pages/guides/getting-started/features.mdx @@ -117,15 +117,15 @@ Supabase Storage makes it simple to store and serve files. [Docs](/docs/guides/s ### Resumable uploads -Upload large files easily using resumable uploads. [Docs](/docs/guides/storage/uploads). +Upload large files easily using resumable uploads. [Docs](/docs/guides/storage/uploads/resumable-uploads). ### Storage CDN -Cache large files using the Supabase CDN. [Docs](/docs/guides/storage-cdn). +Cache large files using the Supabase CDN. [Docs](/docs/guides/storage/cdn/fundamentals). ### Image Transformations -Transform images on the fly. [Docs](/docs/guides/storage/image-transformations). +Transform images on the fly. [Docs](/docs/guides/storage/serving/image-transformations).
diff --git a/apps/docs/pages/guides/storage/access-control.mdx b/apps/docs/pages/guides/storage/access-control.mdx deleted file mode 100644 index a7fcf337af5..00000000000 --- a/apps/docs/pages/guides/storage/access-control.mdx +++ /dev/null @@ -1,177 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'storage-access-control', - title: 'Access Control', - description: 'Manage who can access your Supabase Storage files.', - subtitle: 'Manage who can access your Supabase Storage files.', - tocVideo: '4ERX__Y908k', -} - -Supabase Storage is designed to work perfectly with [Postgres Row Level Security](/docs/guides/database/postgres/row-level-security) (RLS). - -You can use RLS to create [Security Access Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) that are incredibly powerful and flexible, allowing you to restrict access based on your business needs. - -## Policies - -Policies are just SQL, and they're easy once you get the hang of them. For example, here is a Policy that allows public access to any files in a bucket called "my_bucket_id". - -```sql -create policy "Public Access" -on storage.objects -for select -- Allow read access -to anon, authenticated -- Allow access to anonymous and authenticated users -using ( bucket_id = 'my_bucket_id' ); -- Identify the bucket -``` - -An easy way to get started would be to create RLS policies for `SELECT`, `INSERT`, `UPDATE`, `DELETE` operations and restrict the policies to meet your security requirements. For example, one can start with the following `INSERT` policy: - -```sql -create policy "policy_name" -ON storage.objects -for insert with check ( - true -); -``` - -and modify it to only allow authenticated users to upload assets to a specific bucket by changing it to: - -```sql -create policy "policy_name" -on storage.objects for insert to authenticated with check ( - -- restrict bucket - bucket_id = 'my_bucket_id' -); -``` - -## Metadata storage - -Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. - -To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/storage-createbucket). - -## Public and private buckets - -Storage buckets are private by default. For private buckets, you can access objects via the [download](/docs/reference/javascript/storage-from-download) method. This corresponds to `/object/auth/` API endpoint. Alternatively, you can create a publicly shareable URL with an expiry date using the [createSignedUrl](/docs/reference/javascript/storage-from-createsignedurl) method which calls the `/object/sign/` API. - -For public buckets, you can access the assets directly without a token or an Authorisation header. The [getPublicUrl](/docs/reference/javascript/storage-from-getpublicurl) helper method returns the full public URL for an asset. This calls the `/object/public/` API endpoint internally. While no authorization is required for accessing objects in a public bucket, [proper access control](#policies) is required for other operations like uploading, deleting objects from public buckets as well. - -Public buckets also tend to have [better performance](/docs/guides/storage-cdn#public-vs-private-buckets). - -The URLs to access storage endpoints are prefixed with /storage/v1. For example, on the hosted Platform they will be: - -https://[project_ref].supabase.co/storage/v1/object/public/[id] - -You can access the storage API directly with the same endpoint. See the API docs for a full list of operations available. - -## Helper functions - -Supabase Storage provides SQL helper functions which you can use in your database queries and RLS policies. - -### `storage.filename()` - -Returns the name of a file. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `'avatar.png'` - -**Usage** - -This example demonstrates how you would allow any user to download a file called `favicon.ico`: - -```sql -create policy "Allow authenticated uploads" -on storage.objects -for select -to public -using ( - storage.filename(name) = 'favicon.ico' -); -``` - -### `storage.foldername()` - -Returns an array path, with all of the subfolders that a file belongs to. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `[ 'public', 'subfolder' ]` - -**Usage** - -This example demonstrates how you would allow authenticated users to upload files to a folder called `private`: - -```sql -create policy "Allow authenticated uploads" -on storage.objects -for insert -to authenticated -with check ( - storage.foldername(name)[1] = 'private' -); -``` - -### `storage.extension()` - -Returns the extension of a file. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `'png'` - -**Usage** - -This example demonstrates how you would allow restrict uploads to only PNG files inside a bucket called `cats`: - -```sql -create policy "Only allow PNG uploads" -on storage.objects -for insert -to authenticated -with check ( - bucket_id = 'cats' and storage.extension(name) = 'png' -); -``` - -## Usage - -Row Level Security is extremely versatile, since it simply uses SQL to express access rules for your data. You can learn more about RLS in the [Database docs](/docs/guides/database/postgres/row-level-security). - -### Allowing public access to a bucket - -Allow public access to any files in the "public" bucket: - -```sql -create policy "Public Access" -on storage.objects for select -to public -using ( bucket_id = 'public' ); -``` - -### Allowing logged-in access to a bucket - -Allow logged-in access to any files in the "restricted" bucket: - -```sql -create policy "Restricted Access" -on storage.objects for select -to authenticated -using ( bucket_id = 'restricted' ); -``` - -### Allowing individual access to a file - -Allow a user to access their own files: - -```sql -create policy "Individual user Access" -on storage.objects for select -to authenticated -using ( auth.uid() = owner ); -``` - ---- - -{/* Finish with a video. This also appears in the Sidebar via the "tocVideo" metadata */} - -
- -
- -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/storage/buckets/creating-buckets.mdx b/apps/docs/pages/guides/storage/buckets/creating-buckets.mdx new file mode 100644 index 00000000000..cedf1723483 --- /dev/null +++ b/apps/docs/pages/guides/storage/buckets/creating-buckets.mdx @@ -0,0 +1,93 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-creating-buckets', + title: 'Creating Buckets', + description: 'Learn how to create Supabase Storage buckets.', + sidebar_label: 'Buckets', +} + +You can create a bucket using the Supabase Dashboard. Since storage is interoperable with your Postgres database, you can also use SQL or our client libraries. +Here we create a bucket called "avatars": + + + + +```js +// Use the JS library to create a bucket. + +const { data, error } = await supabase.storage.createBucket('avatars', { + public: true, // default: false +}) +``` + +[Reference.](/docs/reference/javascript/storage-createbucket) + + + + + +1. Go to the [Storage](https://supabase.com/dashboard/project/_/storage/buckets) page in the Dashboard. +2. Click **New Bucket** and enter a name for the bucket. +3. Click **Create Bucket**. + + + + +```sql +-- Use Postgres to create a bucket. + +insert into storage.buckets + (id, name, public) +values + ('avatars', 'avatars', true); +``` + + + + +```dart +void main() async { + final supabase = SupabaseClient('supabaseUrl', 'supabaseKey'); + + final storageResponse = await supabase + .storage + .createBucket('avatars'); +} +``` + +[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html) + + + + +## 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` + +```js +// Use the JS library to create a bucket. + +const { data, error } = await supabase.storage.createBucket('avatars', { + public: true, + allowedMimeTypes: ['image/*'], + maxFileSize: '1MB', +}) +``` + +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. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/buckets/fundamentals.mdx b/apps/docs/pages/guides/storage/buckets/fundamentals.mdx new file mode 100644 index 00000000000..a0ab22e26b9 --- /dev/null +++ b/apps/docs/pages/guides/storage/buckets/fundamentals.mdx @@ -0,0 +1,46 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-bucket-public-and-private', + title: 'Storage Buckets', + description: 'Learn how Supabase Storage Buckets works.', + sidebar_label: 'Buckets', +} + +Buckets allow you to keep your files organized and determines the [Access Model](#access-model) for your assets. [Upload restrictions](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) like max file size and allowed content types are also defined at the bucket level. + +## Access Model + +There are 2 access models for buckets, **public** and **private** buckets. + +### Private Buckets + +When a bucket is set to **Private** all operations are subject to access control via [RLS policies](/docs/guides/storage/uploads/access-control). This also applies when downloading assets. Buckets are private by default. + +The only ways to download assets within a private bucket is to: + +- Use the [download method](/docs/reference/javascript/storage-from-download) by providing a authorization header containing your user's JWT. The RLS policy you create on the `storage.objects` table will use this user to determine if they have access. +- Create a signed URL with the [createSignedUrl method](/docs/reference/javascript/storage-from-createsignedurl) that can be accessed for a limited time. + +#### Example Use Cases: + +- Uploading users' sensitive documents +- Securing private assets by using RLS to set up fine-grain access controls + +### Public Buckets + +When a bucket is designated as 'Public,' it effectively bypasses access controls for both retrieving and serving files within the bucket. This means that anyone who possesses the asset URL can readily access the file. + +Access control is still enforced for other types of operations including uploading, deleting, moving, and copying. + +#### Example Use Cases: + +- User profile pictures +- User public media +- Blog post content + +Public buckets are more performant than private buckets since they are [cached differently](/docs/guides/storage/cdn/fundamentals#public-vs-private-buckets). + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/cdn.mdx b/apps/docs/pages/guides/storage/cdn.mdx deleted file mode 100644 index d586f8de94f..00000000000 --- a/apps/docs/pages/guides/storage/cdn.mdx +++ /dev/null @@ -1,125 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'storage-cdn', - title: 'Storage CDN', - description: 'Learn how Supabase Storage caches objects with a CDN.', - sidebar_label: 'CDN', -} - -All assets uploaded to Supabase Storage are cached on a Content Delivery Network (CDN) to improve the latency for users all around the world. CDNs are a geographically distributed set of servers or **nodes** which caches content from an **origin server**. For Supabase Storage, the origin is the storage server running in the [same region as your project](https://supabase.com/dashboard/project/_/settings/general). Aside from performance, CDNs also help with security and availability by mitigating Distributed Denial of Service and other application attacks. - -## Basic CDN - -Our basic CDN caches objects based on the cache time set when uploading objects. - -### Example - -Let's walk through an example of how a CDN helps with performance. - -A new bucket is created for a Supabase project launched in Singapore. All requests to the Supabase Storage API are routed to the CDN first. - -A user from the United States requests an object and is routed to the U.S. CDN. At this point, that CDN node does not have the object in its cache and pings the origin server in Singapore. -![CDN cache miss](/docs/img/cdn-cache-miss.png) - -Another user, also in the United States, requests the same object and is served directly from the CDN cache in the United States instead of routing the request back to Singapore. -![CDN cache hit](/docs/img/cdn-cache-hit.png) - -### Cache duration - -By default, assets are cached both in the CDN and in the user's browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated. You can modify this cache time when you are [uploading](/docs/reference/javascript/storage-from-upload) or [updating](/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter. -If you expect the object to not change at a given URL, setting a longer cache duration is preferable. - -If you need to update the version of the object stored in the CDN, there are various cache-busting techniques you can use. The most common way to do this is to add a version query parameter in the URL. - -For example, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=1` in your applications and set a long cache time of 1 year. - -When you want to update the cat picture, you can increment the version query parameter in the URL. The CDN treats `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=2` as a new object and pings the origin for the updated version. - -If your asset is updated frequently, we recommend that you re-upload the new asset in a different key, this way you'll always have the latest changes available immediately. - -Note that CDNs might still evict your object from their cache if it has not been requested for a while from a specific region. For example, if no user from United States requests your object, it will be removed from the CDN cache even if you set a very long cache control duration. - -The cache status of a particular request is sent in the `cf-cache-status` header. A cache status of `MISS` indicates that the CDN node did not have the object in its cache and had to ping the origin to get it. A cache status of `HIT` indicates that the object was sent directly from the CDN. - -## Smart CDN Caching - - - -Smart CDN caching is automatically enabled for [Pro plan and above](https://supabase.com/pricing). - - - -With Smart CDN caching enabled, the asset metadata in your database is synchronized to the edge. This automatically revalidates the cache when the asset is changed or deleted. - -Additionally, the smart CDN has a higher cache hit ratio as the origin server is shielded from asset requests that haven't changed when using different query strings in the URL. - -### Cache duration - -When Smart CDN is enabled, the asset is cached on the CDN for as long as possible. You can still control how long assets are stored in the browser using the [cacheControl](/docs/reference/javascript/storage-from-upload) option when uploading a file. Smart CDN caching works with all types of storage operations including signed URLs. - -When a file is updated or deleted, the CDN cache is automatically invalidated to reflect the change (including transformed images). It can take **up to 60 seconds** for the CDN cache to be invalidated as the asset metadata has to propagate across all the data-centers around the globe. - -When an asset is invalidated at the CDN level, browsers may not update its cache. - -If your asset is updated frequently, we recommend that you re-upload the new asset in a different key, this way you'll always have the latest changes available without waiting the propagation delay. If you expect your asset to be deleted, we recommend setting a low browser TTL value using the `cacheControl` option when using smart CDN caching, the default is 1 hour which generally is a good default. - -## Public vs Private Buckets - -Objects in public buckets do not require any Authorization to access objects. This leads to a better cache hit rate compared to private buckets. For private buckets, permissions for accessing each object is checked on a per user level. For example, if two different users access the same object in a private bucket from the same region, it results in a cache miss for both the users since they might have different security policies attached to them. On the other hand, if two different users access the same object in a public bucket from the same region, it results in a cache hit for the second user. - -## Debugging Cache Hits - -The [storage report](https://supabase.com/dashboard/project/_/reports/storage) gives a breakdown of your project's cache hit rate. - -The [Logs Explorer](https://supabase.com/dashboard/project/_/logs/explorer) can also be used to debug cache misses. - -The reporting date range depends on your plan's log retention. - -Cache hits can be determined via the `metadata.response.headers.cf_cache_status` key. Any value corresponding to either `HIT`, `STALE`, `REVALIDATED`, or `UPDATING` is considered a cache hit. -The following example query will show the top cache misses from the `edge_logs`: - -```sql -select - r.path as path, - r.search as search, - count(id) as count -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(m.response) as res - cross join unnest(res.headers) as h -where - starts_with(r.path, '/storage/v1/object') - and r.method = 'GET' - and h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC') -group by path, search -order by count desc -limit 50; -``` - -Try out this query in the Logs Explorer [here]("https://supabase.com/dashboard/project/_/logs/explorer?q=%0Aselect%0A++r.path+as+path%2C%0A++r.search+as+search%2C%0A++count%28id%29+as+count%0Afrom%0A++edge_logs+as+f%0A++cross+join+unnest%28f.metadata%29+as+m%0A++cross+join+unnest%28m.request%29+as+r%0A++cross+join+unnest%28m.response%29+as+res%0A++cross+join+unnest%28res.headers%29+as+h%0Awhere%0A++starts_with%28r.path%2C+%27%2Fstorage%2Fv1%2Fobject%27%29%0A++and+r.method+%3D+%27GET%27%0A++and+h.cf_cache_status+in+%28%27MISS%27%2C+%27NONE%2FUNKNOWN%27%2C+%27EXPIRED%27%2C+%27BYPASS%27%2C+%27DYNAMIC%27%29%0Agroup+by+path%2C+search%0Aorder+by+count+desc%0Alimit+50%3B"). - -Your cache hit ratio over time can then be determined using the following query: - -```sql -select - timestamp_trunc(timestamp, hour) as timestamp, - countif(h.cf_cache_status in ('HIT', 'STALE', 'REVALIDATED', 'UPDATING')) / count(f.id) as ratio -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(m.response) as res - cross join unnest(res.headers) as h -where starts_with(r.path, '/storage/v1/object') and r.method = 'GET' -group by timestamp -order by timestamp desc; -``` - -Try out this query in the Logs Explorer [here]("https://supabase.com/dashboard/project/_/logs/explorer?q=%0Aselect%0A++timestamp_trunc%28timestamp%2C+hour%29+as+timestamp%2C%0A++countif%28h.cf_cache_status+in+%28%27HIT%27%2C+%27STALE%27%2C+%27REVALIDATED%27%2C+%27UPDATING%27%29%29+%2F+count%28f.id%29+as+ratio%0Afrom%0A++edge_logs+as+f%0A++cross+join+unnest%28f.metadata%29+as+m%0A++cross+join+unnest%28m.request%29+as+r%0A++cross+join+unnest%28m.response%29+as+res%0A++cross+join+unnest%28res.headers%29+as+h%0Awhere+starts_with%28r.path%2C+%27%2Fstorage%2Fv1%2Fobject%27%29+and+r.method+%3D+%27GET%27%0Agroup+by+timestamp%0Aorder+by+timestamp+desc%3B"). - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/storage/cdn/fundamentals.mdx b/apps/docs/pages/guides/storage/cdn/fundamentals.mdx new file mode 100644 index 00000000000..010d7b161bc --- /dev/null +++ b/apps/docs/pages/guides/storage/cdn/fundamentals.mdx @@ -0,0 +1,41 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-cdn', + title: 'Storage CDN', + description: 'Learn how Supabase Storage caches objects with a CDN.', + sidebar_label: 'CDN', +} + +All assets uploaded to Supabase Storage are cached on a Content Delivery Network (CDN) to improve the latency for users all around the world. CDNs are a geographically distributed set of servers or **nodes** which cache content from an **origin server**. For Supabase Storage, the origin is the storage server running in the [same region as your project](https://supabase.com/dashboard/project/_/settings/general). Aside from performance, CDNs also help with security and availability by mitigating Distributed Denial of Service (DDoS) and other application attacks. + +### Example + +Let's walk through an example of how a CDN helps with performance. + +A new bucket is created for a Supabase project launched in Singapore. All requests to the Supabase Storage API are routed to the CDN first. + +A user from the United States requests an object and is routed to the U.S. CDN. At this point, that CDN node does not have the object in its cache and pings the origin server in Singapore. +![CDN cache miss](/docs/img/cdn-cache-miss.png) + +Another user, also in the United States, requests the same object and is served directly from the CDN cache in the United States instead of routing the request back to Singapore. +![CDN cache hit](/docs/img/cdn-cache-hit.png) + + + +Note that CDNs might still evict your object from their cache if it has not been requested for a while from a specific region. For example, if no user from United States requests your object, it will be removed from the CDN cache even if we set a very long cache control duration. + + + +The cache status of a particular request is sent in the `cf-cache-status` header. A cache status of `MISS` indicates that the CDN node did not have the object in its cache and had to ping the origin to get it. A cache status of `HIT` indicates that the object was sent directly from the CDN. + +### Public vs Private Buckets + +Objects in public buckets do not require any authorization to access objects. This leads to a better cache hit rate compared to private buckets. + +For private buckets, permissions for accessing each object is checked on a per user level. For example, if two different users access the same object in a private bucket from the same region, it results in a cache miss for both the users since they might have different security policies attached to them. +On the other hand, if two different users access the same object in a public bucket from the same region, it results in a cache hit for the second user. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/cdn/metrics.mdx b/apps/docs/pages/guides/storage/cdn/metrics.mdx new file mode 100644 index 00000000000..9257fbb32d3 --- /dev/null +++ b/apps/docs/pages/guides/storage/cdn/metrics.mdx @@ -0,0 +1,56 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-cdn', + title: 'Cache Metrics', + description: 'Learn how Supabase Storage caches objects with a CDN.', + sidebar_label: 'CDN', +} + +Cache hits can be determined via the `metadata.response.headers.cf_cache_status` key in our [Logs Explorer](/docs/guides/platform/logs#logs-explorer). Any value that corresponds to either `HIT`, `STALE`, `REVALIDATED`, or `UPDATING` is categorized as a cache hit. +The following example query will show the top cache misses from the `edge_logs`: + +```sql +select + r.path as path, + r.search as search, + count(id) as count +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(m.response) as res + cross join unnest(res.headers) as h +where + starts_with(r.path, '/storage/v1/object') + and r.method = 'GET' + and h.cf_cache_status in ('MISS', 'NONE/UNKNOWN', 'EXPIRED', 'BYPASS', 'DYNAMIC') +group by path, search +order by count desc +limit 50; +``` + +Try out [this query](https://supabase.com/dashboard/project/_/logs/explorer?q=%0Aselect%0A++r.path+as+path%2C%0A++r.search+as+search%2C%0A++count%28id%29+as+count%0Afrom%0A++edge_logs+as+f%0A++cross+join+unnest%28f.metadata%29+as+m%0A++cross+join+unnest%28m.request%29+as+r%0A++cross+join+unnest%28m.response%29+as+res%0A++cross+join+unnest%28res.headers%29+as+h%0Awhere%0A++starts_with%28r.path%2C+%27%2Fstorage%2Fv1%2Fobject%27%29%0A++and+r.method+%3D+%27GET%27%0A++and+h.cf_cache_status+in+%28%27MISS%27%2C+%27NONE%2FUNKNOWN%27%2C+%27EXPIRED%27%2C+%27BYPASS%27%2C+%27DYNAMIC%27%29%0Agroup+by+path%2C+search%0Aorder+by+count+desc%0Alimit+50%3B) in the Logs Explorer. + +Your cache hit ratio over time can then be determined using the following query: + +```sql +select + timestamp_trunc(timestamp, hour) as timestamp, + countif(h.cf_cache_status in ('HIT', 'STALE', 'REVALIDATED', 'UPDATING')) / count(f.id) as ratio +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(m.response) as res + cross join unnest(res.headers) as h +where starts_with(r.path, '/storage/v1/object') and r.method = 'GET' +group by timestamp +order by timestamp desc; +``` + +Try out [this query](https://supabase.com/dashboard/project/_/logs/explorer?q=%0Aselect%0A++timestamp_trunc%28timestamp%2C+hour%29+as+timestamp%2C%0A++countif%28h.cf_cache_status+in+%28%27HIT%27%2C+%27STALE%27%2C+%27REVALIDATED%27%2C+%27UPDATING%27%29%29+%2F+count%28f.id%29+as+ratio%0Afrom%0A++edge_logs+as+f%0A++cross+join+unnest%28f.metadata%29+as+m%0A++cross+join+unnest%28m.request%29+as+r%0A++cross+join+unnest%28m.response%29+as+res%0A++cross+join+unnest%28res.headers%29+as+h%0Awhere+starts_with%28r.path%2C+%27%2Fstorage%2Fv1%2Fobject%27%29+and+r.method+%3D+%27GET%27%0Agroup+by+timestamp%0Aorder+by+timestamp+desc%3B) in the Logs Explorer. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/cdn/smart-cdn.mdx b/apps/docs/pages/guides/storage/cdn/smart-cdn.mdx new file mode 100644 index 00000000000..9ec314f1b63 --- /dev/null +++ b/apps/docs/pages/guides/storage/cdn/smart-cdn.mdx @@ -0,0 +1,44 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-cdn', + title: 'Smart CDN', + description: 'Learn how Supabase Storage caches objects with a CDN.', + sidebar_label: 'CDN', +} + +With Smart CDN caching enabled, the asset metadata in your database is synchronized to the edge. This automatically revalidates the cache when the asset is changed or deleted. + +Moreover, the Smart CDN achieves a greater cache hit rate by shielding the origin server from asset requests that remain unchanged, even when different query strings are used in the URL. + + + +Smart CDN caching is automatically enabled for [Pro plan and above](https://supabase.com/pricing). + + + +## Cache duration + +When Smart CDN is enabled, the asset is cached on the CDN for as long as possible. You can still control how long assets are stored in the browser using the [cacheControl](/docs/reference/javascript/storage-from-upload) option when uploading a file. Smart CDN caching works with all types of storage operations including signed URLs. + +When a file is updated or deleted, the CDN cache is automatically invalidated to reflect the change (including transformed images). It can take **up to 60 seconds** for the CDN cache to be invalidated as the asset metadata has to propagate across all the data-centers around the globe. + +When an asset is invalidated at the CDN level, browsers may not update its cache. This is where cache eviction comes into play. + +## Cache Eviction + +Even when an asset is marked as invalidated at the CDN level, browsers may not refresh their cache for that asset. + +If you have assets that undergo frequent updates, it is advisable to upload the new asset to a different path. This approach ensures that you always have the most up-to-date asset accessible. + +If you anticipate that your asset might be deleted, it's advisable to set a shorter browser Time-to-Live (TTL) value using the `cacheControl` option. The default TTL is typically set to 1 hour, which is generally a reasonable default value. + +## Bypassing Cache + +If you need to ensure assets refresh directly from the origin server and bypass the cache, you can achieve this by adding a unique query string to the URL. + +For instance, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?version=1` with a long browser cache (e.g., 1 year). To update the picture, increment the version query parameter in the URL, like `/storage/v1/object/sign/profile-pictures/cat.jpg?version=2`. The CDN will recognize it as a new object and fetch the updated version from the origin. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/debugging/error-codes.mdx b/apps/docs/pages/guides/storage/debugging/error-codes.mdx new file mode 100644 index 00000000000..39406cb2af7 --- /dev/null +++ b/apps/docs/pages/guides/storage/debugging/error-codes.mdx @@ -0,0 +1,63 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-errors-codes', + title: 'Error Codes', + description: 'Supabase Error Codes', + sidebar_label: 'Debugging', +} + +Here's a list of the most common error codes and their potential resolutions: + +## 404 not_found + +Indicates that the resource is not found or you don't have the correct permission to access it +**Resolution:** + +- Add a RLS policy to grant permission to the resource. See our [Access Control docs](/docs/guides/storage/uploads/access-control) for more information. +- Ensure you include the user `Authorization` header +- Verify the object exists + +## 409 already_exists + +Indicates that the resource already exists. +**Resolution:** + +- Use the `upsert` functionality in order to overwrite the file. Find out more [here](/docs/guides/storage/uploads/standard-uploads#overwriting-files). + +## 403 unauthorized + +You don't have permission to action this request +**Resolution:** + +- Add RLS policy to grant permission. See our [Access Control docs](/docs/guides/storage/uploads/access-control) for more information. +- Ensure you include the user `Authorization` header + +## 429 Too many requests + +This problem typically arises when a large number of clients are concurrently interacting with the Storage service, and the pooler has reached its `max_clients` limit. + +**Resolution:** + +- Increase the max_clients limits of the pooler. +- Upgrade to a bigger project compute instance [here](https://supabase.com/dashboard/project/_/settings/addons). + +## 544 database_timeout + +This problem arises when a high number of clients are concurrently using the Storage service, and Postgres doesn't have enough available connections to efficiently handle requests to Storage. + +**Resolution:** + +- Increase the pool_size limits of the pooler. +- Upgrade to a bigger project compute instance [here](https://supabase.com/dashboard/project/_/settings/addons). + +## 500 internal_server_error + +This issue occurs where there is a unhandled error. +**Resolution:** + +- File a support ticket to Storage team [here](https://supabase.com/dashboard/support/new) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/debugging/logs.mdx b/apps/docs/pages/guides/storage/debugging/logs.mdx new file mode 100644 index 00000000000..4165f9e0529 --- /dev/null +++ b/apps/docs/pages/guides/storage/debugging/logs.mdx @@ -0,0 +1,68 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-logs', + title: 'Logs', + description: 'Learn how to check Storage Logs', + sidebar_label: 'Debugging', +} + +Accessing the [Storage Logs](https://supabase.green/dashboard/project/__/logs/explorer?q=select+id%2C+storage_logs.timestamp%2C+event_message+from+storage_logs%0A++%0A++order+by+timestamp+desc%0A++limit+100%0A++) allows you to examine all incoming request logs to your Storage service. You can also filter logs and delve into specific aspects of your requests. + +### Common Log Queries + +#### Filter by status 5XX error + +```sql +select id, storage_logs.timestamp, event_message, r.statusCode, e.message as errorMessage e.raw as rawError + from storage_logs + cross join unnest(metadata) as m + cross join unnest(m.res) as r + cross join unnest(m.error) as e + where r.statusCode >= 500 + order by timestamp desc + limit 100 +``` + +#### Filter by status 4XX error + +```sql +select id, storage_logs.timestamp, event_message, r.statusCode, e.message as errorMessage e.raw as rawError + from storage_logs + cross join unnest(metadata) as m + cross join unnest(m.res) as r + cross join unnest(m.error) as e + where r.statusCode >= 400 and r.statusCode < 500 + order by timestamp desc + limit 100 +``` + +#### Filter by Method + +```sql +select id, storage_logs.timestamp, event_message, r.method +from + storage_logs + cross join unnest(metadata) as m + cross join unnest(m.req) as r +where r.method in ("POST") +order by timestamp desc +limit 100; +``` + +#### Filter by IP Address + +```sql +select id, storage_logs.timestamp, event_message, r.remoteAddress +from + storage_logs + cross join unnest(metadata) as m + cross join unnest(m.req) as r +where r.remoteAddress in ("IP_ADDRESS") +order by timestamp desc +limit 100; +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/production/scaling.mdx b/apps/docs/pages/guides/storage/production/scaling.mdx new file mode 100644 index 00000000000..099a4374393 --- /dev/null +++ b/apps/docs/pages/guides/storage/production/scaling.mdx @@ -0,0 +1,92 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-schema-optimisations', + title: 'Storage Optimisations', + description: 'Scaling Storage', + subtitle: 'Scaling Storage', + sidebar_label: 'Schema', +} + +Here are some optimisations that you can consider to improve performance and reduce costs as you start scaling Storage. + +## Egress + +If your project has high egress, these optimizations can help reducing it. + +#### Resize Images + +Images typically make up most of your egress. By keeping them as small as possible, you can cut down on egress and boost your application's performance. You can take advantage of our [Image Transformation](/docs/guides/storage/serving/image-transformations) service to optimize any image on the fly. + +#### Set a high cache-control value + +Using the browser cache can effectively lower your egress since the asset remains stored in the user's browser after the initial download. Setting a high `cache-control` value ensures the asset stays in the user's browser for an extended period, decreasing the need to download it from the server repeatedly. Read more [here](/docs/guides/storage/cdn/smart-cdn#cache-duration) + +#### Limit the upload size + +You have the option to set a maximum upload size for your bucket. Doing this can prevent users from uploading and then downloading excessively large files. You can control the maximum file size by configuring this option at the [bucket level](/docs/guides/storage/buckets/creating-buckets). + +## Optimize Listing Objects + +Once you have a substantial number of objects, you might observe that the `supabase.storage.list()` method starts to slow down. This occurs because the endpoint is quite generic and attempts to retrieve both folders and objects in a single query. While this approach is very useful for building features like the Storage viewer on the Supabase dashboard, it can impact performance with a large number of objects. + +If your application don't need the entire hierarchy computed you can speed up drastically the query execution for listing your objects by creating a Postgres function as following: + +```sql +create or replace function list_objects( + bucketid text, + prefix text, + limits int default 100, + offsets int default 0 +) returns table ( + name text, + id uuid, + updated_at timestamptz, + created_at timestamptz, + last_accessed_at timestamptz, + metadata jsonb +) as $$ +begin + return query SELECT + objects.name, + objects.id, + objects.updated_at, + objects.created_at, + objects.last_accessed_at, + objects.metadata + FROM storage.objects + WHERE objects.name like prefix || '%' + AND bucket_id = bucketid + ORDER BY name ASC + LIMIT limits + OFFSET offsets; +end; +$$ language plpgsql stable; +``` + +You can then use the your Postgres function as following: + +Using SQL: + +```sql +select * from list_objects('bucket_id', '', 100, 0); +``` + +Using the SDK: + +```js +const { data, error } = await supabase.rpc('list_objects', { + bucketid: 'yourbucket', + prefix: '', + limit: 100, + offset: 0, +}) +``` + +## Optimizing RLS + +When creating RLS policies against the storage tables you can add indexes to the interested columns to speed up the lookup + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/schema/design.mdx b/apps/docs/pages/guides/storage/schema/design.mdx new file mode 100644 index 00000000000..86cdedf4c5a --- /dev/null +++ b/apps/docs/pages/guides/storage/schema/design.mdx @@ -0,0 +1,34 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-schema-design', + title: 'The Storage Schema', + description: 'Learn about the storage schema', + subtitle: 'Learn about the storage schema', + sidebar_label: 'Schema', +} + +Storage uses Postgres to store metadata regarding your buckets and objects. Users can use RLS (Row-Level Security) policies for access control. This data is stored in a dedicated schema within your project called `storage`. + + +When working with SQL, it's crucial to consider all records in Storage tables as read-only. All operations, including uploading, copying, moving, and deleting, should **exclusively go through the API**. + +This is important because the storage schema only stores the metadata and the actual objects are stored in a provider like S3. Deleting the metadata doesn't remove the object in the underlying storage provider. This results in your object being inaccessible, but you'll still be billed for it. + + + +Here is the schema that represents the Storage service: + +Storage schema design + +You have the option to query this table directly to retrieve information about your files in Storage without the need to go through our API. + +## Modifying the Schema + +We strongly recommend refraining from making any alterations to the `storage` schema and treating it as read-only. This approach is important because any modifications to the schema on your end could potentially clash with our future updates, leading to downtime. + +However, we encourage you to add custom indexes as they can significantly improve the performance of the RLS policies you create for enforcing access control. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/schema/helper-functions.mdx b/apps/docs/pages/guides/storage/schema/helper-functions.mdx new file mode 100644 index 00000000000..3c0f7a2f30b --- /dev/null +++ b/apps/docs/pages/guides/storage/schema/helper-functions.mdx @@ -0,0 +1,69 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-schema', + title: 'Storage Helper Functions', + description: 'Learn the storage schema', + subtitle: 'Learn the storage schema', + sidebar_label: 'Schema', +} + +Supabase Storage provides SQL helper functions which you can use to write RLS policies. + +### `storage.filename()` + +Returns the name of a file. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `'avatar.png'` + +**Usage** + +This example demonstrates how you would allow any user to download a file called `favicon.ico`: + +```sql +create policy "Allow authenticated uploads" +on storage.objects +for select +to public +using ( + storage.filename(name) = 'favicon.ico' +); +``` + +### `storage.foldername()` + +Returns an array path, with all of the subfolders that a file belongs to. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `[ 'public', 'subfolder' ]` + +**Usage** + +This example demonstrates how you would allow authenticated users to upload files to a folder called `private`: + +```sql +create policy "Allow authenticated uploads" +on storage.objects +for insert +to authenticated +with check ( + storage.foldername(name)[1] = 'private' +); +``` + +### `storage.extension()` + +Returns the extension of a file. For example, if your file is stored in `public/subfolder/avatar.png` it would return: `'png'` + +**Usage** + +This example demonstrates how you would allow restrict uploads to only PNG files inside a bucket called `cats`: + +```sql +create policy "Only allow PNG uploads" +on storage.objects +for insert +to authenticated +with check ( + bucket_id = 'cats' and storage.extension(name) = 'png' +); +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/security/access-control.mdx b/apps/docs/pages/guides/storage/security/access-control.mdx new file mode 100644 index 00000000000..5ab691f8363 --- /dev/null +++ b/apps/docs/pages/guides/storage/security/access-control.mdx @@ -0,0 +1,109 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-access-control', + title: 'Storage Access Control', + description: 'Learn how to restrict Supabase file uploads.', + sidebar_label: 'Uploads', + tocVideo: '4ERX__Y908k', +} + +Supabase Storage is designed to work perfectly with Postgres [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security) (RLS). + +You can use RLS to create [Security Access Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) that are incredibly powerful and flexible, allowing you to restrict access based on your business needs. + +## Access Policies + +By default Storage does not allow any uploads to buckets without RLS policies. You selectively allow certain operations by creating RLS policies on the `storage.objects` table. + +You can find the documentation for the storage schema [here](/docs/guides/storage/schema/design) , and to simplify the process of crafting your policies, you can utilize these [helper functions](/docs/guides/storage/schema/helper-functions) . + + + +The RLS policies required for different operations are documented [here](/docs/reference/javascript/storage-createbucket) + + + +For example, the only RLS policy required for [uploading](/docs/reference/javascript/storage-from-upload) objects is to grant the `INSERT` permission to the `storage.objects` table. + +To allow overwriting files using the `upsert` functionality you will need to additionally grant `SELECT` and `UPDATE` permissions. + +## Policy Examples + +An easy way to get started would be to create RLS policies for `SELECT`, `INSERT`, `UPDATE`, `DELETE` operations and restrict the policies to meet your security requirements. For example, one can start with the following `INSERT` policy: + +```sql +create policy "policy_name" +ON storage.objects +for insert with check ( + true +); +``` + +and modify it to only allow authenticated users to upload assets to a specific bucket by changing it to: + +```sql +create policy "policy_name" +on storage.objects for insert to authenticated with check ( + -- restrict bucket + bucket_id = 'my_bucket_id' +); +``` + +This example demonstrates how you would allow authenticated users to upload files to a folder called `private` inside `my_bucket_id`: + +```sql +create policy "Allow authenticated uploads" +on storage.objects +for insert +to authenticated +with check ( + bucket_id = 'my_bucket_id' and + storage.foldername(name)[1] = 'private' +); +``` + +This example demonstrates how you would allow authenticated users to upload files to a folder called with their `users.id` inside `my_bucket_id`: + +```sql +create policy "Allow authenticated uploads" +on storage.objects +for insert +to authenticated +with check ( + bucket_id = 'my_bucket_id' and + storage.foldername(name)[1] = auth.uid() +); +``` + +Allow a user to access a file that was previously uploaded by the same user: + +```sql +create policy "Individual user Access" +on storage.objects for select +to authenticated +using ( auth.uid() = owner_id ); +``` + +--- + +{/* Finish with a video. This also appears in the Sidebar via the "tocVideo" metadata */} + +
+ +
+ +## Bypassing access controls + +If you exclusively use Storage from trusted clients, such as your own servers, and need to bypass the RLS policies, you can use the `service key` in the `Authorization` header. Service keys entirely bypass RLS policies, granting you unrestricted access to all Storage APIs. + +Remember you should not share the service key publicly. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/serving/downloads.mdx b/apps/docs/pages/guides/storage/serving/downloads.mdx new file mode 100644 index 00000000000..a3a80b507fb --- /dev/null +++ b/apps/docs/pages/guides/storage/serving/downloads.mdx @@ -0,0 +1,59 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-image-transformations', + title: 'Serving assets from Storage', + description: 'Serving assets from Storage', + subtitle: 'Serving assets from Storage', + sidebar_label: 'Serving assets', + tocVideo: 'dLqSmxX3r7I', +} + +## Public Buckets + +As mentioned in the [Buckets Fundamentals](/docs/guides/storage/buckets/fundamentals) all files uploaded in a public bucket are publicly accessible and benefit a high CDN cache HIT ratio. + +You can access them by using this conventional URL: + +``` +https://[project_id].supabase.co/storage/v1/public/[bucket]/[asset-name] +``` + +You can also use the Supabase SDK `getPublicUrl` to generate this url for you + +```js +const publicUrl = supabase.storage.from('bucket').getPublicUrl('filePath.jpg') +``` + +### Downloading + +If you want the browser to start an automatic download of the asset instead of trying serving it, you can add the `?download` querystring parameter. + +By default it will use the asset name to save the file on disk. You can optionally pass a custom name to the `download` parameter as following: `?download=customname.jpg` + +## Private buckets + +Assets stored in a non-public bucket are considered private and are not accessible via a public url like the public buckets. + +You can access them only by: + +- Signing a time limited URL on the Server, for example with Edge Functions. +- with a GET request the url `https://[project_id].supabase.co/storage/v1/authenticated/[bucket]/[asset-name]` and the user Authorization header + +### Signing URLs + +You can sign a time-limited URL that you can share to your users by invoking the `createSignedUrl` method on the SDK. + +```js +const { data, error } = supabase.storage + .from('bucket') + .createSignedUrl('private-document.pdf', 3600) + +if (data) { + console.log(data.signedUrl) +} +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/image-transformations.mdx b/apps/docs/pages/guides/storage/serving/image-transformations.mdx similarity index 88% rename from apps/docs/pages/guides/storage/image-transformations.mdx rename to apps/docs/pages/guides/storage/serving/image-transformations.mdx index cd01774cd72..bf31e12e78a 100644 --- a/apps/docs/pages/guides/storage/image-transformations.mdx +++ b/apps/docs/pages/guides/storage/serving/image-transformations.mdx @@ -9,7 +9,7 @@ export const meta = { tocVideo: 'dLqSmxX3r7I', } -Supabase Storage offers the functionality to transform and resize images dynamically. Any image stored in your buckets can be transformed and optimized for fast delivery. +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. @@ -28,7 +28,7 @@ Image Resizing is currently enabled for [Pro plan and above](https://supabase.co > -You can pass a `transform` option to the functions you are currently using to interact with your objects. This returns the public URL that serves the resized image. +Our client libraries methods like `getPublicUrl` and `createSignedUrl` support the `transform` option. This returns the URL that serves the transformed image. ```ts supabase.storage.from('bucket').getPublicUrl('image.jpg', { @@ -57,7 +57,7 @@ An example URL could look like this: ## Signing URLs with transformation options -To share a transformed image for a fixed amount of time, provide the transform options when you create the signed URL: +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: -The transformation options are embedded into the token—they cannot be changed once signed. +The transformation options are embedded into the token attached to the URL — they cannot be changed once signed. ## Downloading images @@ -133,7 +133,7 @@ supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { ## Automatic Image Optimisation (WebP) -When using the image transformation API we will automatically find the best format supported by the browser 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 the content-type returned is `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 bandwidth that you send to your users and your application will load much faster. @@ -188,7 +188,7 @@ supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { ## NextJS Loader -You can use Supabase Image Transformation to optimise your NextJS images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration). +You can use Supabase Image Transformation to optimize your NextJS 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 NextJS project which exports a default function: @@ -307,7 +307,7 @@ supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { ## Self Hosting -Our solution to image resizing and optimisation can be self-hosted as with any other Supabase product. Under the hood we use the awesome [Imgproxy](https://imgproxy.net/) +Our solution to image resizing and optimisation can be self-hosted as with any other Supabase product. Under the hood we use [Imgproxy](https://imgproxy.net/) #### Imgproxy Configuration: diff --git a/apps/docs/pages/guides/storage/storage-sample.mdx b/apps/docs/pages/guides/storage/storage-sample.mdx deleted file mode 100644 index 0c0e277b3fc..00000000000 --- a/apps/docs/pages/guides/storage/storage-sample.mdx +++ /dev/null @@ -1,11 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'storage-sample', - title: 'Storage Sample Doc', - description: 'Storage Sample Doc', -} - -## Description - -Sample... diff --git a/apps/docs/pages/guides/storage/uploads.mdx b/apps/docs/pages/guides/storage/uploads.mdx deleted file mode 100644 index d287d6386a7..00000000000 --- a/apps/docs/pages/guides/storage/uploads.mdx +++ /dev/null @@ -1,242 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'storage-uploads', - title: 'Storage Uploads', - description: 'Learn how to upload files to Supabase Storage.', - subtitle: 'Learn how to upload files to Supabase Storage.', - sidebar_label: 'Uploads', -} - -Supabase Storage provides two methods for uploading files: - -- Standard Uploads -- Resumable Uploads - -## Standard Upload - -The standard file upload method is ideal for small files that are not larger than 6MB. - -It uses the traditional `multipart/form-data` format and is simple to implement using the supabase-js SDK. Here's an example of how to upload a file using the standard upload method: - - - -Though you can upload up to 5GB files using the standard upload method, we recommend use [TUS Resumable Upload](#resumable-upload) for uploading files greater than 6MB in size for better reliability. - - - - - - -```javascript -import { createClient } from '@supabase/supabase-js' - -// Create Supabase client -const supabase = createClient('your_project_url', 'your_supabase_api_key') - -// Upload file using standard upload -async function uploadFile(file) { - const { data, error } = await supabase.storage.from('bucket_name').upload('file_path', file) - if (error) { - // Handle error - } else { - // Handle success - } -} -``` - - - - -```kotlin -val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { - install(Storage) -} - -suspend fun uploadData(bytes: ByteArray) { - supabase.storage["bucket_name"].upload("file_path", bytes) -} - -//Or on JVM/Android: (This will stream the data from the file to supabase) -suspend fun uploadFile(file: File) { - supabase.storage["bucket_name"].upload("file_path", file) -} -``` - - - - -## Resumable Upload - - - -Resumable upload is in **Beta**. We are rolling this feature gradually, please contact us if you want to be prioritized. - - - -The Resumable upload method is recommended for uploading large files that may exceed 6MB in size or for scenarios where network stability is a concern or if you simply want to have a progress bar for your uploads. - -Supabase Storage implements the [TUS protocol](https://tus.io/) to enable resumable uploads. TUS stands for The Upload Server and is an open protocol for supporting resumable uploads. The protocol allows the upload process to be resumed from where it left off in case of interruptions. This method can be implemented using the tus-js-client library, or other client-side libraries like [Uppy-js](https://uppy.io/docs/tus/) that support the TUS protocol. - - - - -Here's an example of how to upload a file using `tus-js-client`: - -```javascript -const tus = require('tus-js-client') - -const projectId = '' - -async function uploadFile(bucketName, fileName, file) { - const { data: session } = await supabase.auth.session() - - return new Promise((resolve, reject) => { - var upload = new tus.Upload(file, { - endpoint: `https://${projectId}.supabase.co/storage/v1/upload/resumable`, - retryDelays: [0, 3000, 5000, 10000, 20000], - headers: { - authorization: `Bearer ${session.access_token}`, - 'x-upsert': 'true', // optionally set upsert to true to overwrite existing files - }, - uploadDataDuringCreation: true, - removeFingerprintOnSuccess: true, // Important if you want to allow re-uploading the same file https://github.com/tus/tus-js-client/blob/main/docs/api.md#removefingerprintonsuccess - metadata: { - bucketName: bucketName, - objectName: fileName, - contentType: 'image/png', - cacheControl: 3600, - }, - chunkSize: 6 * 1024 * 1024, // NOTE: it must be set to 6MB (for now) do not change it - onError: function (error) { - console.log('Failed because: ' + error) - reject(error) - }, - onProgress: function (bytesUploaded, bytesTotal) { - var percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2) - console.log(bytesUploaded, bytesTotal, percentage + '%') - }, - onSuccess: function () { - // console.log(upload) - console.log('Download %s from %s', upload.file.name, upload.url) - resolve() - }, - }) - - // Check if there are any previous uploads to continue. - return upload.findPreviousUploads().then(function (previousUploads) { - // Found previous uploads so we select the first one. - if (previousUploads.length) { - upload.resumeFromPreviousUpload(previousUploads[0]) - } - - // Start the upload - upload.start() - }) - }) -} -``` - - - - -Kotlin supports resumable uploads natively for all targets: - -```kotlin -//Or on JVM/Android: (This will stream the data from the file to supabase) -suspend fun uploadFile(file: File) { - val upload: ResumableUpload = supabase.storage["bucket_name"].resumable.createOrContinueUpload("file_path", file) - - upload.stateFlow - .onEach { - println(it.progress) - } - .launchIn(yourCoroutineScope) - - upload.startOrResumeUploading() -} - -//On other platforms you might have to give the bytes directly and specify a source if you want to continue it alter: -suspend fun uploadData(bytes: ByteArray) { - val upload: ResumableUpload = supabase.storage["bucket_name"].resumable.createOrContinueUpload(bytes, "source", "file_path") - - upload.stateFlow - .onEach { - println(it.progress) - } - .launchIn(yourCoroutineScope) - - upload.startOrResumeUploading() -} -``` - - - - -### Upload URL - -When uploading using the resumable upload endpoint, the TUS server creates a unique URL for each upload, even for multiple uploads to the same path. All chunks will be uploaded to this URL using the `PATCH` method. - -This URL will be valid for **up to 24 hours**. If the upload is not completed within 24 hours, the URL will expire and you'll need to start the upload again. The TUS client library will automatically create a new URL if the previous one expires. - -### Concurrency - -When two or more clients try to upload to the same Upload URL only one of them will succeed. The other clients will receive a `409 Conflict` error. Only 1 client can upload to the same Upload URL at a time which prevents data corruption. - - - -We do not yet support checksum validation for the uploaded chunks. This means if a client changes the file mid way through the upload, the final upload will be an amalgamation of both the files. This has to be done intentionally by the client and is unlikely to happen in normal circumstances. - - - -When two or more clients upload a file to the same path using different upload URLs, the first client to complete the upload will succeed and the other clients will receive a `409 Conflict` error. - -If you provide the `x-upsert` header the last client to complete the upload will succeed instead. - -### UppyJS Example - -You can check a full example using UppyJS [Here](https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-uppy) - -Framework integration for UppyJS: - -- [React](https://uppy.io/docs/react/) -- [Svelte](https://uppy.io/docs/svelte/) -- [Vue](https://uppy.io/docs/vue/) -- [Angular](https://uppy.io/docs/angular/) - -## Overwriting Files - -When uploading a file to a path that already exists, the default behavior is to return a `409 Conflict` error. -If you want to overwrite a file on a specific path you can set the `x-upsert` header to `true`. - -We do advise against overwriting files when possible, as the CDN will take sometime to propagate the changes to all the edge nodes leading to stale content. -Uploading a file to a new path is the recommended way to avoid propagation delays and stale content. - -If you want to know more about our CDN, check the [CDN](/docs/guides/storage/cdn) guide. - -## Access Control with RLS Policies - -Both the standard file upload and TUS resumable file upload methods in Supabase Storage support Row-Level Security (RLS) policies. RLS allows you to define fine-grained access control rules to restrict access to files based on user roles, permissions, or other criteria. This ensures that your files are secure and accessible only to authorized users. - -For more information on RLS policies, see the [Storage Access Control](/docs/guides/storage/access-control) guide. - -## Conclusion - -Supabase Storage provides multiple options for uploading files, including standard file upload and TUS resumable file upload, both of which use RLS policies for access control. -Additionally, there are client-side libraries like Uppy-js that make it easy to implement TUS uploads in your application. Choose the upload method that best fits your needs based on file size, network stability, and other requirements. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/storage/uploads/file-limits.mdx b/apps/docs/pages/guides/storage/uploads/file-limits.mdx new file mode 100644 index 00000000000..c902964c542 --- /dev/null +++ b/apps/docs/pages/guides/storage/uploads/file-limits.mdx @@ -0,0 +1,41 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'storage-file-limits', + title: 'Limits', + description: 'Learn how to increase Supabase file limits.', + sidebar_label: 'Uploads', +} + +You can customize the Max file size for file uploads. + +## 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/_/settings/storage). + +For Free projects the limit cannot exceed 50MB. On the Pro Plan and above, you can set this value to up to 5GB. + +| Plan | Max File Size Limit | +| ---------- | ------------------- | +| Free | 50MB | +| Pro | 5GB | +| Team | 5GB | +| Enterprise | 5GB | + +If you need more than 5GB please [contact us](https://supabase.com/dashboard/support/new). + +Remember, this option is a global limit, which applies to all your buckets. +You can additionally specify the max file size on a per [bucket level](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) but it cannot be higher than this global limit. + +It is a good practice to have the global limit set to the highest possible file size that your application accepts, and apply per bucket limits. + +## Per Bucket Restrictions + +You can have different restrictions on a per bucket level. +You are able to restrict the file types (eg. `pdf`, `images`, `videos`) along with the max file size which should be lower then the global limit. + +To apply these limit on a bucket level see [Creating Bucket](/docs/guides/storage/buckets/creating-buckets#restricting-uploads) + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx b/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx new file mode 100644 index 00000000000..f4394b7dfb7 --- /dev/null +++ b/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx @@ -0,0 +1,161 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'resumable-uploads', + title: 'Resumable Uploads', + description: 'Learn how to upload files to Supabase Storage.', + subtitle: 'Learn how to upload files to Supabase Storage.', + sidebar_label: 'Uploads', +} + + + +Resumable upload is in **Beta**. We are rolling this feature gradually, please contact us if you want to be prioritized. + + + +The resumable upload method is recommended when: + +- Uploading large files that may exceed 6MB in size +- Network stability is a concern +- You want to have progress events for your uploads + +Supabase Storage implements the [TUS protocol](https://tus.io/) to enable resumable uploads. TUS stands for The Upload Server and is an open protocol for supporting resumable uploads. The protocol allows the upload process to be resumed from where it left off in case of interruptions. This method can be implemented using the [tus-js-client](https://github.com/tus/tus-js-client) library, or other client-side libraries like [Uppy-js](https://uppy.io/docs/tus/) that support the TUS protocol. + + + + + Here's an example of how to upload a file using `tus-js-client`: + + ```javascript + const tus = require('tus-js-client') + + const projectId = '' + + async function uploadFile(bucketName, fileName, file) { + const { data: session } = await supabase.auth.session() + + return new Promise((resolve, reject) => { + var upload = new tus.Upload(file, { + endpoint: `https://${projectId}.supabase.co/storage/v1/upload/resumable`, + retryDelays: [0, 3000, 5000, 10000, 20000], + headers: { + authorization: `Bearer ${session.access_token}`, + 'x-upsert': 'true', // optionally set upsert to true to overwrite existing files + }, + uploadDataDuringCreation: true, + removeFingerprintOnSuccess: true, // Important if you want to allow re-uploading the same file https://github.com/tus/tus-js-client/blob/main/docs/api.md#removefingerprintonsuccess + metadata: { + bucketName: bucketName, + objectName: fileName, + contentType: 'image/png', + cacheControl: 3600, + }, + chunkSize: 6 * 1024 * 1024, // NOTE: it must be set to 6MB (for now) do not change it + onError: function (error) { + console.log('Failed because: ' + error) + reject(error) + }, + onProgress: function (bytesUploaded, bytesTotal) { + var percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2) + console.log(bytesUploaded, bytesTotal, percentage + '%') + }, + onSuccess: function () { + console.log('Download %s from %s', upload.file.name, upload.url) + resolve() + }, + }) + + + // Check if there are any previous uploads to continue. + return upload.findPreviousUploads().then(function (previousUploads) { + // Found previous uploads so we select the first one. + if (previousUploads.length) { + upload.resumeFromPreviousUpload(previousUploads[0]) + } + + // Start the upload + upload.start() + }) + }) + } + ``` + + + + + Kotlin supports resumable uploads natively for all targets: + + ```kotlin + suspend fun uploadFile(file: File) { + val upload: ResumableUpload = supabase.storage["bucket_name"] + .resumable.createOrContinueUpload("file_path", file) + upload.stateFlow + .onEach { + println(it.progress) + } + .launchIn(yourCoroutineScope) + upload.startOrResumeUploading() + } + + // On other platforms you might have to give the bytes directly and specify a source if you want to continue it later: + suspend fun uploadData(bytes: ByteArray) { + val upload: ResumableUpload = supabase.storage["bucket_name"] + .resumable.createOrContinueUpload(bytes, "source", "file_path") + + upload.stateFlow + .onEach { + println(it.progress) + } + .launchIn(yourCoroutineScope) + upload.startOrResumeUploading() + } + ``` + + + + +### Upload URL + +When uploading using the resumable upload endpoint, the storage server creates a unique URL for each upload, even for multiple uploads to the same path. All chunks will be uploaded to this URL using the `PATCH` method. + +This unique upload URL will be valid for **up to 24 hours**. If the upload is not completed within 24 hours, the URL will expire and you'll need to start the upload again. TUS client libraries typically create a new URL if the previous one expires. + +### Concurrency + +When two or more clients upload to the same upload URL only one of them will succeed. The other clients will receive a `409 Conflict` error. Only 1 client can upload to the same upload URL at a time which prevents data corruption. + +When two or more clients upload a file to the same path using different upload URLs, the first client to complete the upload will succeed and the other clients will receive a `409 Conflict` error. + +If you provide the `x-upsert` header the last client to complete the upload will succeed instead. + +### UppyJS Example + +You can check a [full example using UppyJS](https://github.com/supabase/supabase/tree/master/examples/storage/resumable-upload-uppy). + +UppyJS has integrations with different frameworks: + +- [React](https://uppy.io/docs/react/) +- [Svelte](https://uppy.io/docs/svelte/) +- [Vue](https://uppy.io/docs/vue/) +- [Angular](https://uppy.io/docs/angular/) + +## Overwriting Files + +When uploading a file to a path that already exists, the default behavior is to return a `400 Asset Already Exists` error. +If you want to overwrite a file on a specific path you can set the `x-upsert` header to `true`. + +We do advise against overwriting files when possible, as the CDN will take some time to propagate the changes to all the edge nodes leading to stale content. +Uploading a file to a new path is the recommended way to avoid propagation delays and stale content. + +To learn more, see the [CDN](/docs/guides/storage/cdn/fundamentals) guide. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx b/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx new file mode 100644 index 00000000000..64061f54891 --- /dev/null +++ b/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx @@ -0,0 +1,131 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'standard-uploads', + title: 'Standard Uploads', + description: 'Learn how to upload files to Supabase Storage.', + subtitle: 'Learn how to upload files to Supabase Storage.', + sidebar_label: 'Uploads', +} + +## Uploading + +The standard file upload method is ideal for small files that are not larger than 6MB. + +It uses the traditional `multipart/form-data` format and is simple to implement using the supabase-js SDK. Here's an example of how to upload a file using the standard upload method: + + + +Though you can upload up to 5GB files using the standard upload method, we recommend using [TUS Resumable Upload](/docs/guides/storage/uploads/resumable-uploads) for uploading files greater than 6MB in size for better reliability. + + + + + + +```javascript +import { createClient } from '@supabase/supabase-js' + +// Create Supabase client +const supabase = createClient('your_project_url', 'your_supabase_api_key') + +// Upload file using standard upload +async function uploadFile(file) { + const { data, error } = await supabase.storage.from('bucket_name').upload('file_path', file) + if (error) { + // Handle error + } else { + // Handle success + } +} +``` + + + + +```kotlin +val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { + install(Storage) +} + +suspend fun uploadData(bytes: ByteArray) { + supabase.storage["bucket_name"].upload("file_path", bytes) +} + +//Or on JVM/Android: (This will stream the data from the file to supabase) +suspend fun uploadFile(file: File) { + supabase.storage["bucket_name"].upload("file_path", file) +} +``` + + + + +## Overwriting Files + +When uploading a file to a path that already exists, the default behavior is to return a `400 Asset Already Exists` error. +If you want to overwrite a file on a specific path you can set the `upsert` options to `true` or using the `x-upsert` header. + + + + +```javascript +// Create Supabase client +const supabase = createClient('your_project_url', 'your_supabase_api_key') + +await supabase.storage.from('bucket_name').upload('file_path', file, { + upsert: true, +}) +``` + + + + +We do advise against overwriting files when possible, as our Content Delivery Network will take sometime to propagate the changes to all the edge nodes leading to stale content. +Uploading a file to a new path is the recommended way to avoid propagation delays and stale content. + +## Content Type + +By default, Storage will assume the content type of an asset from the file extension. If you want to specify the content type for your asset simply pass the `contentType` option during upload. + + + + +```javascript +// Create Supabase client +const supabase = createClient('your_project_url', 'your_supabase_api_key') + +await supabase.storage.from('bucket_name').upload('file_path', file, { + contentType: 'image/jpeg', +}) +``` + + + + +## Concurrency + +When two or more clients upload a file to the same path, the first client to complete the upload will succeed and the other clients will receive a `400 Asset Already Exists` error. +If you provide the `x-upsert` header the last client to complete the upload will succeed instead. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/public/img/storage/schema-design.png b/apps/docs/public/img/storage/schema-design.png new file mode 100644 index 0000000000000000000000000000000000000000..79401732ce213d1d548a1c3d8e8b0e5edb6ce525 GIT binary patch literal 89188 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%0DKi?0>Z;8VI<)8UV&Hz3HDZgsVOG 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 -<<<<<<< HEAD https://supabase.com/docs/guides/ai/choosing-compute-addon weekly 0.5 @@ -152,124 +151,7 @@ - https://supabase.com/docs/guides/ai/structured-unstructured - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/vecs-python-client - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/vector-columns - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/vector-indexes -======= - https://supabase.com/docs/guides/api/api-keys ->>>>>>> @{-1} - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/customizing-email-templates - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/getting-started - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/local-development - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/managing-config - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/managing-environments - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/seeding-your-database - weekly - 0.5 - - - - https://supabase.com/docs/guides/cli/testing-and-linting - weekly - 0.5 - - - - https://supabase.com/docs/guides/api/using-custom-schemas - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/choosing-compute-addon - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/concepts - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/engineering-for-scale - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/going-to-prod - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/google-colab - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/hugging-face - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/langchain - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/python-clients + https://supabase.com/docs/guides/ai/rag-with-permissions weekly 0.5 @@ -358,6 +240,18 @@ 0.5 + + https://supabase.com/docs/guides/auth/native-mobile-deep-linking + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/native-mobile-login + weekly + 0.5 + + https://supabase.com/docs/guides/auth/phone-login weekly @@ -383,9 +277,6 @@ -<<<<<<< HEAD - https://supabase.com/docs/guides/database/arrays -======= https://supabase.com/docs/guides/cli/customizing-email-templates weekly 0.5 @@ -411,14 +302,11 @@ https://supabase.com/docs/guides/cli/managing-environments ->>>>>>> @{-1} weekly 0.5 -<<<<<<< HEAD -======= https://supabase.com/docs/guides/cli/seeding-your-database weekly 0.5 @@ -430,122 +318,6 @@ 0.5 - - https://supabase.com/docs/guides/database/arrays - weekly - 0.5 - - - ->>>>>>> @{-1} - https://supabase.com/docs/guides/database/column-encryption - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/connecting-to-postgres - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/extensions - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/full-text-search - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/functions - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/inspect - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/json - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/managing-passwords - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/managing-timezones - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/overview - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/partitions - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/query-optimization - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/replication - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/sql-to-api - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/tables - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/testing - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/vault - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/webhooks -<<<<<<< HEAD - weekly - 0.5 - - https://supabase.com/docs/guides/api/api-keys weekly @@ -578,110 +350,6 @@ https://supabase.com/docs/guides/api/using-custom-schemas -======= ->>>>>>> @{-1} - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/auth - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/cicd-workflow - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/connect-to-postgres - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/cors - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/dart-edge - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/debugging - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/global-deployments - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/import-maps - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/kysely-postgres - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/local-development - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/quickstart - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/schedule-functions - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/secrets - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/storage-caching - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/troubleshooting - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/typescript-support - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/unit-test weekly 0.5 @@ -800,6 +468,12 @@ 0.5 + + https://supabase.com/docs/guides/platform/multi-factor-authentication + weekly + 0.5 + + https://supabase.com/docs/guides/platform/network-restrictions weekly @@ -921,31 +595,7 @@ - https://supabase.com/docs/guides/resources/examples - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/glossary - weekly - 0.5 - - - - https://supabase.com/docs/guides/storage/access-control - weekly - 0.5 - - - - https://supabase.com/docs/guides/storage/cdn - weekly - 0.5 - - - - https://supabase.com/docs/guides/storage/image-transformations + https://supabase.com/docs/guides/self-hosting/docker weekly 0.5 @@ -956,24 +606,6 @@ 0.5 - - https://supabase.com/docs/guides/storage/storage-sample - weekly - 0.5 - - - - https://supabase.com/docs/guides/storage/uploads - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/docker - weekly - 0.5 - - https://supabase.com/docs/learn/auth-deep-dive/auth-deep-dive-jwts weekly @@ -1005,27 +637,240 @@ -<<<<<<< HEAD -======= - https://supabase.com/docs/guides/api/rest/auto-generated-docs + https://supabase.com/docs/guides/database/arrays weekly 0.5 - https://supabase.com/docs/guides/api/rest/client-libs + https://supabase.com/docs/guides/database/column-encryption weekly 0.5 - https://supabase.com/docs/guides/api/rest/generating-types + https://supabase.com/docs/guides/database/connecting-to-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/extensions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/full-text-search + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/inspect + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/json + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/managing-passwords + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/managing-timezones + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/overview + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/partitions + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/query-optimization + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/replication + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/sql-to-api + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/tables + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/testing + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/vault + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/webhooks + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cicd-workflow + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/connect-to-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/cors + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/dart-edge + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/debugging + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/global-deployments + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/import-maps + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/kysely-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/local-development + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/quickstart + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/schedule-functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/secrets + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/storage-caching + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/troubleshooting + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/typescript-support + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/unit-test + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/examples + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/glossary + weekly + 0.5 + + + + https://supabase.com/docs/guides/ai/integrations/llamaindex weekly 0.5 ->>>>>>> @{-1} https://supabase.com/docs/guides/ai/examples/building-chatgpt-plugins weekly 0.5 @@ -1062,57 +907,18 @@ -<<<<<<< HEAD https://supabase.com/docs/guides/ai/quickstarts/face-similarity -======= - https://supabase.com/docs/guides/ai/integrations/llamaindex ->>>>>>> @{-1} weekly 0.5 -<<<<<<< HEAD https://supabase.com/docs/guides/ai/quickstarts/generate-text-embeddings -======= - https://supabase.com/docs/guides/ai/quickstarts/face-similarity ->>>>>>> @{-1} weekly 0.5 -<<<<<<< HEAD - https://supabase.com/docs/guides/ai/quickstarts/hello-world -======= - https://supabase.com/docs/guides/ai/quickstarts/generate-text-embeddings ->>>>>>> @{-1} - weekly - 0.5 - - - -<<<<<<< HEAD - https://supabase.com/docs/guides/ai/quickstarts/text-deduplication - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/integrations/llamaindex - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/vector-indexes/hnsw-indexes - weekly - 0.5 - - - - https://supabase.com/docs/guides/ai/vector-indexes/ivf-indexes -======= https://supabase.com/docs/guides/ai/quickstarts/hello-world weekly 0.5 @@ -1120,7 +926,6 @@ https://supabase.com/docs/guides/ai/quickstarts/text-deduplication ->>>>>>> @{-1} weekly 0.5 @@ -1161,6 +966,32 @@ 0.5 + + https://supabase.com/docs/guides/auth/quickstarts/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/quickstarts/react + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr + weekly + 0.5 + + + + https://supabase.com/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr + weekly + 0.5 + + https://supabase.com/docs/guides/auth/concepts/redirect-urls weekly @@ -1186,8 +1017,6 @@ -<<<<<<< HEAD -======= https://supabase.com/docs/guides/ai/vector-indexes/hnsw-indexes weekly 0.5 @@ -1199,33 +1028,6 @@ 0.5 - ->>>>>>> @{-1} - https://supabase.com/docs/guides/auth/quickstarts/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/quickstarts/react - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr - weekly - 0.5 - - - - https://supabase.com/docs/guides/auth/server-side/oauth-with-pkce-flow-for-ssr - weekly - 0.5 - - https://supabase.com/docs/guides/auth/social-login/auth-apple weekly @@ -1340,12 +1142,516 @@ 0.5 + + https://supabase.com/docs/guides/getting-started/quickstarts/flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/kotlin + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/reactjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/redwoodjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/refine + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/quickstarts/vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-expo-react-native + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-flutter + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-kotlin + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-react + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-refine + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-svelte + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit + weekly + 0.5 + + + + https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3 + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/auto-generated-docs + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/client-libs + weekly + 0.5 + + + + https://supabase.com/docs/guides/api/rest/generating-types + weekly + 0.5 + + https://supabase.com/docs/guides/auth/sso/auth-sso-saml weekly 0.5 + + https://supabase.com/docs/guides/platform/sso/azure + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/gsuite + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/sso/okta + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/analytics/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/realtime/guides/client-side-throttling + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/realtime/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/storage/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/self-hosting/auth/config + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/cdn/fundamentals + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/cdn/metrics + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/cdn/smart-cdn + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/oauth-apps/build-a-supabase-integration + weekly + 0.5 + + + + https://supabase.com/docs/guides/platform/oauth-apps/oauth-scopes + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/buckets/creating-buckets + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/buckets/fundamentals + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/schema/design + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/schema/helper-functions + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/serving/downloads + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/serving/image-transformations + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/production/scaling + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/security/access-control + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/debugging/error-codes + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/debugging/logs + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/uploads/file-limits + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/uploads/resumable-uploads + weekly + 0.5 + + + + https://supabase.com/docs/guides/storage/uploads/standard-uploads + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/cascade-deletes + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/configuration + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/first-row-in-group + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/indexes + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/roles + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/row-level-security + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/triggers + weekly + 0.5 + + + + https://supabase.com/docs/guides/database/postgres/which-version-of-postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/cloudflare-turnstile + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/discord-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/github-actions + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/og-image + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/rate-limiting + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/screenshots + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/send-emails + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/slack-bot-mention + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/stripe-webhooks + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/telegram-bot + weekly + 0.5 + + + + https://supabase.com/docs/guides/functions/examples/upstash-redis + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/amazon-rds + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/firestore-data + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/mssql + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/mysql + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/postgres + weekly + 0.5 + + + + https://supabase.com/docs/guides/resources/migrating-to-supabase/render + weekly + 0.5 + + https://supabase.com/docs/guides/database/extensions/http weekly @@ -1502,491 +1808,6 @@ 0.5 - -<<<<<<< HEAD - https://supabase.com/docs/guides/api/graphql/graphiql - weekly - 0.5 - - - - https://supabase.com/docs/guides/api/rest/auto-generated-docs - weekly - 0.5 - - - - https://supabase.com/docs/guides/api/rest/client-libs - weekly - 0.5 - - - - https://supabase.com/docs/guides/api/rest/generating-types - weekly - 0.5 - - - -======= ->>>>>>> @{-1} - https://supabase.com/docs/guides/database/postgres/cascade-deletes - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/configuration - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/dropping-all-tables-in-schema - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/first-row-in-group - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/indexes - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/roles - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/row-level-security - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/triggers - weekly - 0.5 - - - - https://supabase.com/docs/guides/database/postgres/which-version-of-postgres - weekly - 0.5 - - - -<<<<<<< HEAD -======= - https://supabase.com/docs/guides/platform/oauth-apps/build-a-supabase-integration - weekly - 0.5 - - - ->>>>>>> @{-1} - https://supabase.com/docs/guides/functions/examples/cloudflare-turnstile - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/discord-bot - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/github-actions - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/og-image - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/rate-limiting - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/screenshots - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/send-emails - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/stripe-webhooks - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/telegram-bot - weekly - 0.5 - - - - https://supabase.com/docs/guides/functions/examples/upstash-redis - weekly - 0.5 - - - -<<<<<<< HEAD - https://supabase.com/docs/guides/getting-started/quickstarts/flutter - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/kotlin - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/reactjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/redwoodjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/refine - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/solidjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/vue - weekly - 0.5 - - - - https://supabase.com/docs/guides/platform/oauth-apps/build-a-supabase-integration - weekly - 0.5 - - - -======= ->>>>>>> @{-1} - https://supabase.com/docs/guides/getting-started/tutorials/with-angular - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-expo - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-flutter - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-angular - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-vue - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-kotlin - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-nuxt-3 - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-react - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-redwoodjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-refine - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-solidjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-svelte - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/tutorials/with-vue-3 -<<<<<<< HEAD -======= - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/flutter - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/kotlin - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nextjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/reactjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/redwoodjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/refine - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/solidjs - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit - weekly - 0.5 - - - - https://supabase.com/docs/guides/getting-started/quickstarts/vue ->>>>>>> @{-1} - weekly - 0.5 - - - - https://supabase.com/docs/guides/platform/sso/azure - weekly - 0.5 - - - - https://supabase.com/docs/guides/platform/sso/gsuite - weekly - 0.5 - - - - https://supabase.com/docs/guides/platform/sso/okta - weekly - 0.5 - - - - https://supabase.com/docs/guides/realtime/guides/client-side-throttling - weekly - 0.5 - - - -<<<<<<< HEAD -======= - https://supabase.com/docs/guides/self-hosting/analytics/config - weekly - 0.5 - - - ->>>>>>> @{-1} - https://supabase.com/docs/guides/resources/migrating-to-supabase/amazon-rds - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-auth - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firebase-storage - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/firestore-data - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/heroku - weekly - 0.5 - - - - https://supabase.com/docs/guides/resources/migrating-to-supabase/render - weekly - 0.5 - - - -<<<<<<< HEAD - https://supabase.com/docs/guides/self-hosting/analytics/config -======= - https://supabase.com/docs/guides/self-hosting/realtime/config - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/storage/config ->>>>>>> @{-1} - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/auth/config - weekly - 0.5 - - - -<<<<<<< HEAD - https://supabase.com/docs/guides/self-hosting/realtime/config - weekly - 0.5 - - - - https://supabase.com/docs/guides/self-hosting/storage/config -======= - https://supabase.com/docs/guides/database/extensions/wrappers/overview ->>>>>>> @{-1} - weekly - 0.5 - - https://supabase.com/docs/guides/database/extensions/wrappers/overview weekly @@ -2469,6 +2290,12 @@ 0.5 + + https://supabase.com/docs/reference/javascript/broadcastmessage + weekly + 0.5 + + https://supabase.com/docs/reference/javascript/getchannels weekly @@ -3183,6 +3010,150 @@ 0.5 + + https://supabase.com/docs/reference/python/update + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/upsert + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/delete + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/using-filters + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/eq + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/neq + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/gt + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/gte + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/lt + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/lte + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/like + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/ilike + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/is + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/in + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/contains + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/containedby + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/match + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/not + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/filter + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/using-modifiers + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/order + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/limit + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/single + weekly + 0.5 + + + + https://supabase.com/docs/reference/python/maybesingle + weekly + 0.5 + + https://supabase.com/docs/reference/python/functions-invoke weekly @@ -3261,12 +3232,6 @@ 0.5 - - https://supabase.com/docs/reference/python/subscribe - weekly - 0.5 - - https://supabase.com/docs/reference/csharp/initializing weekly @@ -4665,6 +4630,30 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-snippets + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-snippets-list + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-snippets-download + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-services + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-secrets weekly @@ -4701,6 +4690,12 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-projects-delete + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-projects-create weekly @@ -5025,6 +5020,24 @@ 0.5 + + https://supabase.com/docs/reference/cli/supabase-encryption + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-encryption-update-root-key + weekly + 0.5 + + + + https://supabase.com/docs/reference/cli/supabase-encryption-get-root-key + weekly + 0.5 + + https://supabase.com/docs/reference/cli/supabase-domains weekly diff --git a/apps/www/_blog/2021-07-27-storage-beta.mdx b/apps/www/_blog/2021-07-27-storage-beta.mdx index 817d32d4e72..f9449c456d0 100644 --- a/apps/www/_blog/2021-07-27-storage-beta.mdx +++ b/apps/www/_blog/2021-07-27-storage-beta.mdx @@ -38,7 +38,7 @@ You can make objects public in S3 via multiple mechanisms (e.g. tags, or prefixe Objects in public buckets can be accessed via a URL without any Authorization tokens or headers. This makes them perfect for hosting assets for your web or mobile application. -Public buckets still require [Auth Policies](/docs/guides/storage#policy-examples) to upload and delete objects, allowing you to securely manage their contents. +Public buckets still require [Auth Policies](/docs/guides/storage/security/access-control#policy-examples) to upload and delete objects, allowing you to securely manage their contents.