Date: Tue, 9 Sep 2025 17:00:59 +1000
Subject: [PATCH 06/13] generic opt in copy (#38117)
* generic opt in copy
* privacy
* revert privacy
* remove foundation
* update opt in copy
* refine copy
---
.../GeneralSettings/AIOptInLevelSelector.tsx | 19 ++++++++------
.../GeneralSettings/OptInToOpenAIToggle.tsx | 26 +++++++------------
2 files changed, 21 insertions(+), 24 deletions(-)
diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx
index 28e9686ef77e4..fd766e97cf546 100644
--- a/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx
+++ b/apps/studio/components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector.tsx
@@ -39,7 +39,7 @@ export const AIOptInLevelSelector = ({
value: 'disabled',
title: 'Disabled',
description:
- 'You do not consent to sharing any database information with Amazon Bedrock and understand that responses will be generic and not tailored to your database',
+ 'You do not consent to sharing any database information with third-party AI providers and understand that responses will be generic and not tailored to your database',
},
]
: []),
@@ -49,7 +49,7 @@ export const AIOptInLevelSelector = ({
value: 'schema',
title: 'Schema Only',
description:
- 'You consent to sharing your database’s schema metadata (such as table and column names, data types, and relationships—but not actual database data) with Amazon Bedrock',
+ 'You consent to sharing your database’s schema metadata (such as table and column names, data types, and relationships—but not actual database data) with third-party AI providers',
},
]
: []),
@@ -59,7 +59,7 @@ export const AIOptInLevelSelector = ({
value: 'schema_and_log',
title: 'Schema & Logs',
description:
- 'You consent to sharing your schema and logs (which may contain PII/database data) with Amazon Bedrock for better results',
+ 'You consent to sharing your schema and logs (which may contain PII/database data) with third-party AI providers for better results',
},
]
: []),
@@ -69,7 +69,7 @@ export const AIOptInLevelSelector = ({
value: 'schema_and_log_and_data',
title: 'Schema, Logs & Database Data',
description:
- 'You consent to give Amazon Bedrock full access to run database read only queries and analyze results for optimal results',
+ 'You consent to give third-party AI providers full access to run database read-only queries and analyze results for optimal results',
},
]
: []),
@@ -83,10 +83,13 @@ export const AIOptInLevelSelector = ({
Supabase AI can provide more relevant answers if you choose to share different levels of
- data. This feature is powered by Amazon Bedrock which does not store or log your prompts
- and completions, nor does it use them to train AWS models or distribute them to third
- parties. This is an organization-wide setting, so please select the level of data you
- are comfortable sharing.
+ data. This feature is powered by third-party AI providers. This is an organization-wide
+ setting, so please select the level of data you are comfortable sharing.
+
+
+ For organizations with HIPAA compliance enabled in their Supabase configuration, any
+ consented information will only be shared with third-party AI providers with whom
+ Supabase has established a Business Associate Agreement (BAA).
diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx
index 33197145ab31a..0cee6710c139c 100644
--- a/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx
+++ b/apps/studio/components/interfaces/Organization/GeneralSettings/OptInToOpenAIToggle.tsx
@@ -27,28 +27,22 @@ export const OptInToOpenAIToggle = () => {
className="flex flex-col gap-y-4 text-sm text-foreground-light"
>
- Supabase AI utilizes Amazon Bedrock ("Bedrock"), a service designed with a strong focus
- on data privacy and security.
+ Supabase AI utilizes third-party AI providers designed with a strong focus on data
+ privacy and security.
- Amazon Bedrock does not store or log your prompts and completions. This data is not used
- to train any AWS models and is not distributed to third parties or model providers.
- Model providers do not have access to Amazon Bedrock logs or customer prompts and
- completions.
+ By default, only schema data is shared with third-party AI providers. This is not
+ retained by them nor used as training data. With your permission, Supabase may also
+ share customer-generated prompts, database data, and project logs with these providers.
+ This information is used solely to generate responses to your queries and is not
+ retained by the providers or used to train their models.
- By default, no information is shared with Bedrock unless you explicitly provide consent.
- With your permission, Supabase may share customer-generated prompts, database schema,
- database data, and project logs with Bedrock. This information is used solely to
- generate responses to your queries and is not retained by Bedrock or used to train their
- foundation models.
-
-
-
- If you are a HIPAA Covered Entity, please note that Bedrock is HIPAA eligible, and
- Supabase has a Business Associate Agreement in place covering this use.
+ For organizations with HIPAA compliance enabled in their Supabase configuration, any
+ consented information will only be shared with third-party AI providers with whom
+ Supabase has established a Business Associate Agreement (BAA).
From 774825c1ccc17d1ef43a30683fb6d60b69741756 Mon Sep 17 00:00:00 2001
From: Alaister Young
Date: Tue, 9 Sep 2025 09:04:27 +0200
Subject: [PATCH 07/13] chore: customizable dashboard auth (#38516)
* chore: customizable dashboard auth
* minor fixes
---------
Co-authored-by: Joshen Lim
---
.../interfaces/SignIn/SignInWithCustom.tsx | 48 +++++++++
.../studio/components/ui/UnknownInterface.tsx | 12 ++-
.../custom-content/CustomContent.types.ts | 2 +
.../hooks/custom-content/custom-content.json | 2 +
.../custom-content/custom-content.sample.json | 2 +
.../custom-content/custom-content.schema.json | 5 +
apps/studio/pages/sign-in-sso.tsx | 8 ++
apps/studio/pages/sign-in.tsx | 97 +++++++++++++------
apps/studio/pages/sign-up.tsx | 35 +++++--
.../enabled-features/enabled-features.json | 5 +
.../enabled-features.schema.json | 21 ++++
11 files changed, 193 insertions(+), 44 deletions(-)
create mode 100644 apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx
diff --git a/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx
new file mode 100644
index 0000000000000..a51b1a25a0da6
--- /dev/null
+++ b/apps/studio/components/interfaces/SignIn/SignInWithCustom.tsx
@@ -0,0 +1,48 @@
+import * as Sentry from '@sentry/nextjs'
+import { useState } from 'react'
+import { toast } from 'sonner'
+
+import { BASE_PATH } from 'lib/constants'
+import { auth, buildPathWithParams } from 'lib/gotrue'
+import { Button } from 'ui'
+
+interface SignInWithCustomProps {
+ providerName: string
+}
+
+export const SignInWithCustom = ({ providerName }: SignInWithCustomProps) => {
+ const [loading, setLoading] = useState(false)
+
+ async function handleCustomSignIn() {
+ setLoading(true)
+
+ try {
+ // redirects to /sign-in to check if the user has MFA setup (handled in SignInLayout.tsx)
+ const redirectTo = buildPathWithParams(
+ `${
+ process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
+ ? location.origin
+ : process.env.NEXT_PUBLIC_SITE_URL
+ }${BASE_PATH}/sign-in-mfa?method=${providerName.toLowerCase()}`
+ )
+
+ const { error } = await auth.signInWithOAuth({
+ // @ts-expect-error - providerName is a string
+ provider: providerName.toLowerCase(),
+ options: { redirectTo },
+ })
+
+ if (error) throw error
+ } catch (error: any) {
+ toast.error(`Failed to sign in via ${providerName}: ${error.message}`)
+ Sentry.captureMessage('[CRITICAL] Failed to sign in via GH: ' + error.message)
+ setLoading(false)
+ }
+ }
+
+ return (
+
+ Continue with {providerName}
+
+ )
+}
diff --git a/apps/studio/components/ui/UnknownInterface.tsx b/apps/studio/components/ui/UnknownInterface.tsx
index d53ef6c7aaff4..0baf1c5a4015c 100644
--- a/apps/studio/components/ui/UnknownInterface.tsx
+++ b/apps/studio/components/ui/UnknownInterface.tsx
@@ -1,11 +1,17 @@
import Link from 'next/link'
-import { Button } from 'ui'
+import { Button, cn } from 'ui'
import { Admonition } from 'ui-patterns'
-export const UnknownInterface = ({ urlBack }: { urlBack: string }) => {
+export const UnknownInterface = ({
+ urlBack,
+ fullHeight = true,
+}: {
+ urlBack: string
+ fullHeight?: boolean
+}) => {
return (
-
+
{
+ const signInWithSSOEnabled = useIsFeatureEnabled('dashboard_auth:sign_in_with_sso')
+
+ if (!signInWithSSOEnabled) {
+ return
+ }
+
return (
<>
diff --git a/apps/studio/pages/sign-in.tsx b/apps/studio/pages/sign-in.tsx
index 62f383d27773e..db421158ce828 100644
--- a/apps/studio/pages/sign-in.tsx
+++ b/apps/studio/pages/sign-in.tsx
@@ -5,9 +5,12 @@ import { useEffect } from 'react'
import { LastSignInWrapper } from 'components/interfaces/SignIn/LastSignInWrapper'
import { SignInForm } from 'components/interfaces/SignIn/SignInForm'
+import { SignInWithCustom } from 'components/interfaces/SignIn/SignInWithCustom'
import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub'
import { AuthenticationLayout } from 'components/layouts/AuthenticationLayout'
import SignInLayout from 'components/layouts/SignInLayout/SignInLayout'
+import { useCustomContent } from 'hooks/custom-content/useCustomContent'
+import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { IS_PLATFORM } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { Button } from 'ui'
@@ -15,6 +18,25 @@ import { Button } from 'ui'
const SignInPage: NextPageWithLayout = () => {
const router = useRouter()
+ const {
+ dashboardAuthSignInWithGithub: signInWithGithubEnabled,
+ dashboardAuthSignInWithSso: signInWithSsoEnabled,
+ dashboardAuthSignInWithEmail: signInWithEmailEnabled,
+ dashboardAuthSignUp: signUpEnabled,
+ } = useIsFeatureEnabled([
+ 'dashboard_auth:sign_in_with_github',
+ 'dashboard_auth:sign_in_with_sso',
+ 'dashboard_auth:sign_in_with_email',
+ 'dashboard_auth:sign_up',
+ ])
+
+ const { dashboardAuthCustomProvider: customProvider } = useCustomContent([
+ 'dashboard_auth:custom_provider',
+ ])
+
+ const showOrDivider =
+ (signInWithGithubEnabled || signInWithSsoEnabled || customProvider) && signInWithEmailEnabled
+
useEffect(() => {
if (!IS_PLATFORM) {
// on selfhosted instance just redirect to projects page
@@ -25,45 +47,58 @@ const SignInPage: NextPageWithLayout = () => {
return (
<>
-
-
- }>
+ {customProvider && }
+ {signInWithGithubEnabled && }
+ {signInWithSsoEnabled && (
+
+ }
+ >
+
+ Continue with SSO
+
+
+
+ )}
+
+ {showOrDivider && (
+
+ )}
+ {signInWithEmailEnabled && }
+
+
+ {signUpEnabled && (
+
+
+
Don't have an account? {' '}
- Continue with SSO
+ Sign Up Now
-
-
-
-
-
-
-
-
-
- Don't have an account? {' '}
-
- Sign Up Now
-
-
-
+ )}
>
)
}
diff --git a/apps/studio/pages/sign-up.tsx b/apps/studio/pages/sign-up.tsx
index c60e96d342b5e..5fbaeb116eb81 100644
--- a/apps/studio/pages/sign-up.tsx
+++ b/apps/studio/pages/sign-up.tsx
@@ -3,22 +3,37 @@ import Link from 'next/link'
import { SignInWithGitHub } from 'components/interfaces/SignIn/SignInWithGitHub'
import { SignUpForm } from 'components/interfaces/SignIn/SignUpForm'
import SignInLayout from 'components/layouts/SignInLayout/SignInLayout'
+import { UnknownInterface } from 'components/ui/UnknownInterface'
+import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import type { NextPageWithLayout } from 'types'
const SignUpPage: NextPageWithLayout = () => {
+ const {
+ dashboardAuthSignUp: signUpEnabled,
+ dashboardAuthSignInWithGithub: signInWithGithubEnabled,
+ } = useIsFeatureEnabled(['dashboard_auth:sign_up', 'dashboard_auth:sign_in_with_github'])
+
+ if (!signUpEnabled) {
+ return
+ }
+
return (
<>
-
-
-
+ {signInWithGithubEnabled && (
+ <>
+
+
+
+ >
+ )}
diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json
index 7c7ee99b442e0..0f2a3178fb6e9 100644
--- a/packages/common/enabled-features/enabled-features.json
+++ b/packages/common/enabled-features/enabled-features.json
@@ -24,6 +24,11 @@
"billing:all": true,
+ "dashboard_auth:sign_up": true,
+ "dashboard_auth:sign_in_with_github": true,
+ "dashboard_auth:sign_in_with_sso": true,
+ "dashboard_auth:sign_in_with_email": true,
+
"database:replication": true,
"database:roles": true,
diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json
index ae6eeb10c655c..13325bae07f74 100644
--- a/packages/common/enabled-features/enabled-features.schema.json
+++ b/packages/common/enabled-features/enabled-features.schema.json
@@ -86,6 +86,23 @@
"description": "Enable the billing settings page"
},
+ "dashboard_auth:sign_up": {
+ "type": "boolean",
+ "description": "Enable the sign up page in the dashboard"
+ },
+ "dashboard_auth:sign_in_with_github": {
+ "type": "boolean",
+ "description": "Enable the sign in with github provider"
+ },
+ "dashboard_auth:sign_in_with_sso": {
+ "type": "boolean",
+ "description": "Enable the sign in with sso provider"
+ },
+ "dashboard_auth:sign_in_with_email": {
+ "type": "boolean",
+ "description": "Enable the sign in with email/password provider"
+ },
+
"database:replication": {
"type": "boolean",
"description": "Enable the database replication page"
@@ -235,6 +252,10 @@
"authentication:show_sort_by_phone",
"authentication:show_user_type_filter",
"billing:all",
+ "dashboard_auth:sign_up",
+ "dashboard_auth:sign_in_with_github",
+ "dashboard_auth:sign_in_with_sso",
+ "dashboard_auth:sign_in_with_email",
"database:replication",
"database:roles",
"docs:self-hosting",
From 58100a012140a1a905dc0cd78f7e7246efaef343 Mon Sep 17 00:00:00 2001
From: Chris Chinchilla
Date: Tue, 9 Sep 2025 09:28:49 +0200
Subject: [PATCH 08/13] docs: Change key name for dynamic loading docs (#38011)
Change key name for dynamic loading docs
---
apps/docs/app/contributing/content.mdx | 4 ++--
.../ProjectConfigVariables/ProjectConfigVariables.tsx | 6 +++---
.../ProjectConfigVariables/ProjectConfigVariables.utils.ts | 4 ++--
.../content/guides/auth/server-side/creating-a-client.mdx | 2 +-
apps/docs/content/guides/auth/server-side/nextjs.mdx | 4 ++--
apps/docs/content/guides/auth/server-side/sveltekit.mdx | 2 +-
.../content/guides/getting-started/quickstarts/flutter.mdx | 2 +-
.../content/guides/getting-started/quickstarts/hono.mdx | 2 +-
.../guides/getting-started/quickstarts/ios-swiftui.mdx | 2 +-
.../content/guides/getting-started/quickstarts/kotlin.mdx | 2 +-
.../content/guides/getting-started/quickstarts/nextjs.mdx | 2 +-
.../content/guides/getting-started/quickstarts/nuxtjs.mdx | 2 +-
.../content/guides/getting-started/quickstarts/reactjs.mdx | 2 +-
.../content/guides/getting-started/quickstarts/refine.mdx | 2 +-
.../content/guides/getting-started/quickstarts/solidjs.mdx | 2 +-
.../guides/getting-started/quickstarts/sveltekit.mdx | 2 +-
.../docs/content/guides/getting-started/quickstarts/vue.mdx | 2 +-
apps/docs/content/guides/telemetry/log-drains.mdx | 2 +-
18 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/apps/docs/app/contributing/content.mdx b/apps/docs/app/contributing/content.mdx
index 2a5086cd2d922..c3632971a2a52 100644
--- a/apps/docs/app/contributing/content.mdx
+++ b/apps/docs/app/contributing/content.mdx
@@ -354,11 +354,11 @@ Some guides and tutorials will require that users copy their Supabase project UR
```mdx
-
+
```
-
+
### Step Hike
diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx
index 88423df94174e..b34ed122426bb 100644
--- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx
+++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.tsx
@@ -274,7 +274,7 @@ function VariableView({ variable, className }: { variable: Variable; className?:
const hasBranches = selectedProject?.is_branch_enabled ?? false
const ref = hasBranches ? selectedBranch?.project_ref : selectedProject?.ref
- const needsApiQuery = variable === 'anonKey' || variable === 'url'
+ const needsApiQuery = variable === 'publishableKey' || variable === 'url'
const needsSupavisorQuery = variable === 'sessionPooler'
const {
@@ -303,7 +303,7 @@ function VariableView({ variable, className }: { variable: Variable; className?:
switch (variable) {
case 'url':
return !apiData.app_config?.endpoint
- case 'anonKey':
+ case 'publishableKey':
return !apiData.service_api_keys?.some((key) => key.tags === 'anon')
}
}
@@ -336,7 +336,7 @@ function VariableView({ variable, className }: { variable: Variable; className?:
case 'url':
variableValue = `https://${apiData?.app_config?.endpoint}`
break
- case 'anonKey':
+ case 'publishableKey':
variableValue = apiData?.service_api_keys?.find((key) => key.tags === 'anon')?.api_key || ''
break
case 'sessionPooler':
diff --git a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts
index 1d1a125e3dd60..2ed28261bedd4 100644
--- a/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts
+++ b/apps/docs/components/ProjectConfigVariables/ProjectConfigVariables.utils.ts
@@ -6,7 +6,7 @@ export type Org = OrganizationsData[number]
export type Project = ProjectsData[number]
export type Branch = BranchesData[number]
-export type Variable = 'url' | 'anonKey' | 'sessionPooler'
+export type Variable = 'url' | 'publishableKey' | 'sessionPooler'
function removeDoubleQuotes(str: string) {
return str.replaceAll('"', '')
@@ -30,7 +30,7 @@ function unescapeDoubleQuotes(str: string) {
export const prettyFormatVariable: Record = {
url: 'Project URL',
- anonKey: 'Anon key',
+ publishableKey: 'Publishable key',
sessionPooler: 'Connection string (pooler session mode)',
}
diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
index 08e8649f1d433..bbefe0d40c0c5 100644
--- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
+++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
@@ -42,7 +42,7 @@ pnpm add @supabase/ssr @supabase/supabase-js
In your environment variables file, set your Supabase URL and Supabase Anon Key:
-
+
diff --git a/apps/docs/content/guides/auth/server-side/nextjs.mdx b/apps/docs/content/guides/auth/server-side/nextjs.mdx
index ca43981674c78..6d437f6caddff 100644
--- a/apps/docs/content/guides/auth/server-side/nextjs.mdx
+++ b/apps/docs/content/guides/auth/server-side/nextjs.mdx
@@ -39,7 +39,7 @@ Create a `.env.local` file in your project root directory.
Fill in your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`:
-
+
@@ -560,7 +560,7 @@ Create a `.env.local` file in your project root directory.
Fill in your `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`:
-
+
diff --git a/apps/docs/content/guides/auth/server-side/sveltekit.mdx b/apps/docs/content/guides/auth/server-side/sveltekit.mdx
index 81db989fa9ecc..404fb252e2a48 100644
--- a/apps/docs/content/guides/auth/server-side/sveltekit.mdx
+++ b/apps/docs/content/guides/auth/server-side/sveltekit.mdx
@@ -34,7 +34,7 @@ Create a `.env.local` file in your project root directory.
Fill in your `PUBLIC_SUPABASE_URL` and `PUBLIC_SUPABASE_PUBLISHABLE_KEY`:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
index 6420a59f1390e..54142ac73c5b6 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/flutter.mdx
@@ -58,7 +58,7 @@ hideToc: true
Open `lib/main.dart` and edit the main function to initialize Supabase using your project URL and public API (anon) key:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
index d85823d199822..983c5086099e5 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/hono.mdx
@@ -50,7 +50,7 @@ hideToc: true
Lastly, [enable anonymous sign-ins](https://supabase.com/dashboard/project/_/settings/auth) in the Auth settings.
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
index 91f73e5616ac3..9424fd47d9f77 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/ios-swiftui.mdx
@@ -42,7 +42,7 @@ hideToc: true
Create a new `Supabase.swift` file add a new Supabase instance using your project URL and public API (anon) key:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
index 38c857552c379..59f7b6cf283bc 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/kotlin.mdx
@@ -79,7 +79,7 @@ hideToc: true
Replace the `supabaseUrl` and `supabaseKey` with your own:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
index 70277ee447ef3..6db673183c3d2 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/nextjs.mdx
@@ -40,7 +40,7 @@ hideToc: true
Rename `.env.example` to `.env.local` and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
index cad739e7e8624..5524606a613ea 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/nuxtjs.mdx
@@ -56,7 +56,7 @@ hideToc: true
Create a `.env` file and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
index 1e15500c1518c..1bf67565651f1 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/reactjs.mdx
@@ -56,7 +56,7 @@ hideToc: true
Create a `.env.local` file and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
index da4656c9f4276..a6cb4ff632ed0 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/refine.mdx
@@ -78,7 +78,7 @@ hideToc: true
You now have to update the `supabaseClient` with the `SUPABASE_URL` and `SUPABASE_KEY` of your Supabase API. The `supabaseClient` is used in auth provider and data provider methods that allow the refine app to connect to your Supabase backend.
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
index 0646a93b22ad8..10aa736884a14 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/solidjs.mdx
@@ -56,7 +56,7 @@ hideToc: true
Create a `.env.local` file and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
index f71f5bda2b84b..c5b20f469cb02 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/sveltekit.mdx
@@ -57,7 +57,7 @@ hideToc: true
Create a `.env` file at the root of your project and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
index f98c7b3556252..2db179b77f4c4 100644
--- a/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
+++ b/apps/docs/content/guides/getting-started/quickstarts/vue.mdx
@@ -56,7 +56,7 @@ hideToc: true
Create a `.env.local` file and populate with your Supabase connection variables:
-
+
diff --git a/apps/docs/content/guides/telemetry/log-drains.mdx b/apps/docs/content/guides/telemetry/log-drains.mdx
index 7b1920a19557c..be5a1cd18cd9e 100644
--- a/apps/docs/content/guides/telemetry/log-drains.mdx
+++ b/apps/docs/content/guides/telemetry/log-drains.mdx
@@ -85,7 +85,7 @@ Create a HTTP drain under the [Project Settings > Log Drains](https://supabase.c
- Under URL, set it to your edge function URL `https://[PROJECT REF].supabase.co/functions/v1/hello-world`
- Under Headers, set the `Authorization: Bearer [ANON KEY]`
-
+
From c0041e0c28216c633ac53cef66f790e3a0ad9f43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?=
Date: Tue, 9 Sep 2025 15:40:14 +0800
Subject: [PATCH 09/13] feat: support additional tax ids (#38542)
---
.../BillingCustomerData/TaxID.constants.ts | 205 ++++++++++++------
1 file changed, 138 insertions(+), 67 deletions(-)
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts
index 290af4077ed1f..b34098174b306 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts
+++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/TaxID.constants.ts
@@ -10,13 +10,13 @@ export interface TaxId {
// Commented out countries are not currently supported by Orb API
export const TAX_IDS: TaxId[] = [
- /*{
+ {
name: 'AL TIN',
type: 'al_tin',
country: 'Albania',
placeholder: 'J12345678N',
countryIso2: 'AL',
- },*/
+ },
{
name: 'AE TRN',
type: 'ae_trn',
@@ -39,13 +39,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: 'A-123456-Z',
countryIso2: 'AD',
},
- /*{
+ {
name: 'AO TIN',
type: 'ao_tin',
country: 'Angola',
placeholder: '5123456789',
countryIso2: 'AO',
- },*/
+ },
{
name: 'AR CUIT',
type: 'ar_cuit',
@@ -53,7 +53,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: '12-3456789-01',
countryIso2: 'AR',
},
- /*{
+ {
name: 'AM TIN',
type: 'am_tin',
country: 'Armenia',
@@ -66,7 +66,7 @@ export const TAX_IDS: TaxId[] = [
country: 'Aruba',
placeholder: '12345678',
countryIso2: 'AW',
- },*/
+ },
{
name: 'AU ABN',
type: 'au_abn',
@@ -81,7 +81,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789123',
countryIso2: 'AU',
},
- /*{
+ {
name: 'AZ TIN',
type: 'az_tin',
country: 'Azerbaijan',
@@ -94,7 +94,7 @@ export const TAX_IDS: TaxId[] = [
country: 'Bahamas',
placeholder: '123.456.789',
countryIso2: 'BS',
- },*/
+ },
{
name: 'BH VAT',
type: 'bh_vat',
@@ -102,7 +102,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789012345',
countryIso2: 'BH',
},
- /*{
+ {
name: 'BD BIN',
type: 'bd_bin',
country: 'Bangladesh',
@@ -116,13 +116,6 @@ export const TAX_IDS: TaxId[] = [
placeholder: '1123456789012',
countryIso2: 'BB',
},
- {
- name: 'BY TIN',
- type: 'by_tin',
- country: 'Belarus',
- placeholder: '123456789',
- countryIso2: 'BY',
- },*/
{
name: 'BE VAT',
type: 'eu_vat',
@@ -131,13 +124,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'BE',
countryIso2: 'BE',
},
- /*{
+ {
name: 'BJ IFU',
type: 'bj_ifu',
country: 'Benin',
placeholder: '1234567890123',
countryIso2: 'BJ',
- },*/
+ },
{
name: 'BO TIN',
type: 'bo_tin',
@@ -145,13 +138,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789',
countryIso2: 'BO',
},
- /*{
+ {
name: 'BA TIN',
type: 'ba_tin',
country: 'Bosnia & Herzegovina',
placeholder: '123456789012',
countryIso2: 'BA',
- },*/
+ },
{
name: 'BG VAT',
type: 'eu_vat',
@@ -160,6 +153,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'BG',
countryIso2: 'BG',
},
+ {
+ name: 'BG UIC',
+ type: 'bg_uic',
+ country: 'Bulgaria',
+ placeholder: '123456789',
+ countryIso2: 'BG',
+ },
{
name: 'BR CNPJ',
type: 'br_cnpj',
@@ -174,7 +174,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123.456.789-87',
countryIso2: 'BR',
},
- /*{
+ {
name: 'BF IFU',
type: 'bf_ifu',
country: 'Burkina Faso',
@@ -194,7 +194,7 @@ export const TAX_IDS: TaxId[] = [
country: 'Cameroon',
placeholder: 'M123456789000L',
countryIso2: 'CM',
- },*/
+ },
{
name: 'CA BN',
type: 'ca_bn',
@@ -225,7 +225,7 @@ export const TAX_IDS: TaxId[] = [
},
{
name: 'CA PST-SK',
- type: 'ca_pst_mb',
+ type: 'ca_pst_sk',
country: 'Canada',
placeholder: '1234567',
countryIso2: 'CA',
@@ -237,13 +237,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '1234567890TQ1234',
countryIso2: 'CA',
},
- /*{
+ {
name: 'CV NIF',
type: 'cv_nif',
country: 'Cape Verde',
placeholder: '213456789',
countryIso2: 'CV',
- },*/
+ },
{
name: 'CH VAT',
type: 'ch_vat',
@@ -252,6 +252,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'CHE',
countryIso2: 'CH',
},
+ {
+ name: 'CH UID',
+ type: 'ch_uid',
+ country: 'Switzerland',
+ placeholder: 'CHE-123.456.789 HR ',
+ countryIso2: 'CH',
+ },
{
name: 'CL TIN',
type: 'cl_tin',
@@ -273,13 +280,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123.456.789-0',
countryIso2: 'CO',
},
- /*{
+ {
name: 'CD NIF',
type: 'cd_nif',
country: 'Congo (DRC)',
placeholder: 'A0123456M',
countryIso2: 'CD',
- },*/
+ },
{
name: 'CR TIN',
type: 'cr_tin',
@@ -311,6 +318,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'DE',
countryIso2: 'DE',
},
+ {
+ name: 'DE STN',
+ type: 'de_stn',
+ country: 'Germany',
+ placeholder: '1234567890',
+ countryIso2: 'DE',
+ },
{
name: 'DK VAT',
type: 'eu_vat',
@@ -347,13 +361,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '1234-567890-123-4',
countryIso2: 'SV',
},
- /*{
+ {
name: 'ET TIN',
type: 'et_tin',
country: 'Ethiopia',
placeholder: '1234567890',
countryIso2: 'ET',
- },*/
+ },
{
name: 'EE VAT',
type: 'eu_vat',
@@ -377,13 +391,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'ES',
countryIso2: 'ES',
},
- /*{
+ {
name: 'SR FIN',
type: 'sr_fin',
country: 'Suriname',
placeholder: '1234567890',
countryIso2: 'SR',
- },*/
+ },
{
name: 'FI VAT',
type: 'eu_vat',
@@ -408,6 +422,14 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'GB',
countryIso2: 'GB',
},
+ {
+ name: 'UK VAT',
+ type: 'gb_vat',
+ country: 'United Kingdom',
+ placeholder: 'GB123456789',
+ vatPrefix: 'GB',
+ countryIso2: 'GB',
+ },
{
name: 'GE VAT',
type: 'ge_vat',
@@ -423,13 +445,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: 'EL123456789',
vatPrefix: 'EL',
},
- /*{
+ {
name: 'GN NIF',
type: 'gn_nif',
country: 'Guinea',
placeholder: '123456789',
countryIso2: 'GN',
- },*/
+ },
{
name: 'HK BR',
type: 'hk_br',
@@ -445,6 +467,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'HR',
countryIso2: 'HR',
},
+ {
+ name: 'HR OIB',
+ type: 'hr_oib',
+ country: 'Croatia',
+ placeholder: '12345678901',
+ countryIso2: 'HR',
+ },
{
name: 'HU VAT',
type: 'eu_vat',
@@ -453,6 +482,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'HU',
countryIso2: 'HU',
},
+ {
+ name: 'HU TIN',
+ type: 'hu_tin',
+ country: 'Hungary',
+ placeholder: '12345678-1-23',
+ countryIso2: 'HU',
+ },
{
name: 'ID NPWP',
type: 'id_npwp',
@@ -498,11 +534,11 @@ export const TAX_IDS: TaxId[] = [
countryIso2: 'IT',
},
{
- name: 'JP CN',
- type: 'jp_cn',
- country: 'Japan',
- placeholder: '1234567891234',
- countryIso2: 'JP',
+ name: 'KE PIN',
+ type: 'ke_pin',
+ country: 'Kenya',
+ placeholder: 'P000111111A',
+ countryIso2: 'KE',
},
{
name: 'KZ BIN',
@@ -512,19 +548,19 @@ export const TAX_IDS: TaxId[] = [
countryIso2: 'KZ',
},
{
- name: 'KE PIN',
- type: 'ke_pin',
- country: 'Kenya',
- placeholder: 'P000111111A',
- countryIso2: 'KE',
- },
- /*{
name: 'KG TIN',
type: 'kg_tin',
country: 'Kyrgyzstan',
placeholder: '12345678901234',
countryIso2: 'KG',
- },*/
+ },
+ {
+ name: 'JP CN',
+ type: 'jp_cn',
+ country: 'Japan',
+ placeholder: '1234567891234',
+ countryIso2: 'JP',
+ },
{
name: 'JP RN',
type: 'jp_rn',
@@ -532,6 +568,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '12345',
countryIso2: 'JP',
},
+ {
+ name: 'JP TRN',
+ type: 'jp_trn',
+ country: 'Japan',
+ placeholder: 'T1234567891234',
+ countryIso2: 'JP',
+ },
{
name: 'KR BRN',
type: 'kr_brn',
@@ -546,6 +589,14 @@ export const TAX_IDS: TaxId[] = [
placeholder: 'CHE123456789',
countryIso2: 'LI',
},
+ {
+ name: 'LI VAT',
+ type: 'li_vat',
+ country: 'Liechtenstein',
+ placeholder: '12345',
+ vatPrefix: 'LI',
+ countryIso2: 'LI',
+ },
{
name: 'LT VAT',
type: 'eu_vat',
@@ -562,13 +613,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'LU',
countryIso2: 'LU',
},
- /*{
+ {
name: 'LA TIN',
type: 'la_tin',
country: 'Laos',
placeholder: '123456789-000',
countryIso2: 'LA',
- },*/
+ },
{
name: 'LV VAT',
type: 'eu_vat',
@@ -585,13 +636,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'MT',
countryIso2: 'MT',
},
- /*{
+ {
name: 'MR NIF',
type: 'mr_nif',
country: 'Mauritania',
placeholder: '12345678',
countryIso2: 'MR',
- },*/
+ },
{
name: 'MX RFC',
type: 'mx_rfc',
@@ -599,7 +650,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: 'ABC010203AB9',
countryIso2: 'MX',
},
- /* {
+ {
name: 'MD VAT',
type: 'md_vat',
country: 'Moldova',
@@ -619,7 +670,7 @@ export const TAX_IDS: TaxId[] = [
country: 'Morocco',
placeholder: '12345678',
countryIso2: 'MA',
- },*/
+ },
{
name: 'MY FRP',
type: 'my_frp',
@@ -641,13 +692,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: 'A12-3456-78912345',
countryIso2: 'MY',
},
- /*{
+ {
name: 'NP PAN',
type: 'np_pan',
country: 'Nepal',
placeholder: '123456789',
countryIso2: 'NP',
- },*/
+ },
{
name: 'NL VAT',
type: 'eu_vat',
@@ -663,13 +714,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '12345678-0001',
countryIso2: 'NG',
},
- /*{
+ {
name: 'MK VAT',
type: 'mk_vat',
country: 'North Macedonia',
placeholder: 'MK1234567890123',
countryIso2: 'MK',
- },*/
+ },
{
name: 'nz_gst',
type: 'nz_gst',
@@ -684,6 +735,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789MVA',
countryIso2: 'NO',
},
+ {
+ name: 'NO VOEC',
+ type: 'no_voec',
+ country: 'Norway',
+ placeholder: '1234567 ',
+ countryIso2: 'NO',
+ },
{
name: 'OM VAT',
type: 'om_vat',
@@ -736,6 +794,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'RO',
countryIso2: 'RO',
},
+ {
+ name: 'RO TIN',
+ type: 'ro_tin',
+ country: 'Romania',
+ placeholder: '1234567890123',
+ countryIso2: 'RO',
+ },
{
name: 'RU INN',
type: 'ru_inn',
@@ -757,13 +822,13 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789012345',
countryIso2: 'SA',
},
- /*{
+ {
name: 'SN NINEA',
type: 'sn_ninea',
country: 'Senegal',
placeholder: '12345672A2',
countryIso2: 'SN',
- },*/
+ },
{
name: 'RS PIB',
type: 'rs_pib',
@@ -801,6 +866,13 @@ export const TAX_IDS: TaxId[] = [
vatPrefix: 'SI',
countryIso2: 'SI',
},
+ {
+ name: 'SI TIN',
+ type: 'si_tin',
+ country: 'Slovenia',
+ placeholder: '12345678',
+ countryIso2: 'SI',
+ },
{
name: 'SK VAT',
type: 'eu_vat',
@@ -830,7 +902,7 @@ export const TAX_IDS: TaxId[] = [
placeholder: '12345678',
countryIso2: 'TW',
},
- /* {
+ {
name: 'TJ TIN',
type: 'tj_tin',
country: 'Tajikistan',
@@ -843,14 +915,14 @@ export const TAX_IDS: TaxId[] = [
country: 'Tanzania',
placeholder: '12345678A',
countryIso2: 'TZ',
- },*/
- /*{
+ },
+ {
name: 'UG TIN',
type: 'ug_tin',
country: 'Uganda',
placeholder: '0123456789',
countryIso2: 'UG',
- },*/
+ },
{
name: 'UA VAT',
type: 'ua_vat',
@@ -887,20 +959,20 @@ export const TAX_IDS: TaxId[] = [
placeholder: '123456789012',
countryIso2: 'UY',
},
- /* {
+ {
name: 'UZ TIN',
type: 'uz_tin',
country: 'Uzbekistan',
placeholder: '123456789',
countryIso2: 'UZ',
},
- {
+ {
name: 'UZ VAT',
type: 'uz_vat',
country: 'Uzbekistan',
placeholder: '123456789012',
countryIso2: 'UZ',
- },*/
+ },
{
name: 'VE RIF',
type: 've_rif',
@@ -915,7 +987,6 @@ export const TAX_IDS: TaxId[] = [
placeholder: '1234567890',
countryIso2: 'VN',
},
- /*
{
name: 'ZM TIN',
type: 'zm_tin',
@@ -929,5 +1000,5 @@ export const TAX_IDS: TaxId[] = [
country: 'Zimbabwe',
placeholder: '1234567890',
countryIso2: 'ZW',
- },*/
+ },
]
From b27ad019ce383bef35bb6f310f602bf4aeb10357 Mon Sep 17 00:00:00 2001
From: Joshen Lim
Date: Tue, 9 Sep 2025 15:52:45 +0800
Subject: [PATCH 10/13] (Clean up) Remove secrets:pull command (#38541)
Remove secrets:pull command
---
apps/studio/package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/apps/studio/package.json b/apps/studio/package.json
index 33def8d179e1d..4320f6d587b29 100644
--- a/apps/studio/package.json
+++ b/apps/studio/package.json
@@ -5,7 +5,6 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev --turbopack -p 8082",
- "dev:secrets:pull": "AWS_PROFILE=supa-dev node ../../scripts/getSecrets.js -n local/studio",
"build": "next build && ./../../scripts/upload-static-assets.sh",
"start": "next start",
"lint": "next lint",
From 336788b1208fd52395f5d927316f2bf9f78e5811 Mon Sep 17 00:00:00 2001
From: Chris Chinchilla
Date: Tue, 9 Sep 2025 10:06:33 +0200
Subject: [PATCH 11/13] docs: Correct key usage in edge function docs (#38545)
Correct keys
---
apps/docs/content/guides/functions/auth.mdx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/docs/content/guides/functions/auth.mdx b/apps/docs/content/guides/functions/auth.mdx
index 7e8959a6368e6..ca91575b35ca0 100644
--- a/apps/docs/content/guides/functions/auth.mdx
+++ b/apps/docs/content/guides/functions/auth.mdx
@@ -25,7 +25,7 @@ import { createClient } from 'npm:@supabase/supabase-js@2'
Deno.serve(async (req: Request) => {
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
- Deno.env.get('SUPABASE_PUBLISHABLE_KEY') ?? '',
+ Deno.env.get('SUPABASE_ANON_KEY') ?? '',
// Create client with Auth context of the user that called the function.
// This way your row-level-security (RLS) policies are applied.
{
From 1a78aecb053574e6f3a31e36fd1ab13be171440d Mon Sep 17 00:00:00 2001
From: Han Qiao
Date: Tue, 9 Sep 2025 17:00:12 +0800
Subject: [PATCH 12/13] fix: estimate data branch cost before creation (#38511)
* fix: estimate data branch cost before creation
* chore: comments to clarify conditionals
* Use addons endpoint to check for compute size instead
* Revert branchComputeSize changes
* Fix
* chore: update compute size only on data branch
* chore: replace slow query with disk size estimate
* Add loading and error states for disk attr, and update copy in create branch modal
* Update copy to improve transparency on billing
* Tiny fix
* Nit
---------
Co-authored-by: Joshen Lim
---
.../BranchManagement.utils.ts | 66 ++++
.../BranchManagement/CreateBranchModal.tsx | 324 ++++++++++++------
.../data/branches/branch-create-mutation.ts | 10 +-
3 files changed, 286 insertions(+), 114 deletions(-)
create mode 100644 apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts
diff --git a/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts b/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts
new file mode 100644
index 0000000000000..e1f23edabf531
--- /dev/null
+++ b/apps/studio/components/interfaces/BranchManagement/BranchManagement.utils.ts
@@ -0,0 +1,66 @@
+import { DiskAttributesData } from 'data/config/disk-attributes-query'
+import { DesiredInstanceSize, instanceSizeSpecs } from 'data/projects/new-project.constants'
+import {
+ DISK_LIMITS,
+ DISK_PRICING,
+ DiskType,
+ PLAN_DETAILS,
+} from '../DiskManagement/ui/DiskManagement.constants'
+
+// Ref: https://supabase.com/docs/guides/platform/compute-and-disk
+const maxDiskForCompute = new Map([
+ [10, instanceSizeSpecs.micro],
+ [50, instanceSizeSpecs.small],
+ [100, instanceSizeSpecs.medium],
+ [200, instanceSizeSpecs.large],
+ [500, instanceSizeSpecs.xlarge],
+ [1_000, instanceSizeSpecs['2xlarge']],
+ [2_000, instanceSizeSpecs['4xlarge']],
+ [4_000, instanceSizeSpecs['8xlarge']],
+ [6_000, instanceSizeSpecs['12xlarge']],
+ [10_000, instanceSizeSpecs['16xlarge']],
+])
+
+export const estimateComputeSize = (
+ projectDiskSize: number,
+ branchComputeSize?: DesiredInstanceSize
+) => {
+ if (branchComputeSize) {
+ return instanceSizeSpecs[branchComputeSize]
+ }
+ // Fallback to estimating based on volume size
+ for (const [disk, compute] of maxDiskForCompute) {
+ if (projectDiskSize <= disk) {
+ return compute
+ }
+ }
+ return instanceSizeSpecs['24xlarge']
+}
+
+export const estimateDiskCost = (disk: DiskAttributesData['attributes']) => {
+ const diskType = disk.type as DiskType
+
+ const pricing = DISK_PRICING[diskType]
+ const includedGB = PLAN_DETAILS['pro'].includedDiskGB[diskType]
+ const priceSize = Math.max(disk.size_gb - includedGB, 0) * pricing.storage
+ const includedIOPS = DISK_LIMITS[diskType].includedIops
+ const priceIOPS = Math.max(disk.iops - includedIOPS, 0) * pricing.iops
+
+ const priceThroughput =
+ diskType === DiskType.GP3 && 'throughput_mbps' in disk
+ ? Math.max(disk.throughput_mbps - DISK_LIMITS[DiskType.GP3].includedThroughput, 0) *
+ DISK_PRICING[DiskType.GP3].throughput
+ : 0
+
+ return {
+ total: priceSize + priceIOPS + priceThroughput,
+ size: priceSize,
+ iops: priceIOPS,
+ throughput: priceThroughput,
+ }
+}
+
+export const estimateRestoreTime = (disk: DiskAttributesData['attributes']) => {
+ // This is interpolated from real restore time
+ return (720 / 21000) * disk.size_gb + 3
+}
diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx
index 9fb2103792bf8..e6c183d0bf073 100644
--- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx
+++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx
@@ -1,4 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod'
+import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { DatabaseZap, DollarSign, GitMerge, Github, Loader2 } from 'lucide-react'
import Image from 'next/image'
@@ -9,20 +10,21 @@ import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'
-import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useFlag, useParams } from 'common'
import { useIsBranching2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { BranchingPITRNotice } from 'components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
+import { InlineLink, InlineLinkClassName } from 'components/ui/InlineLink'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
import { useBranchesQuery } from 'data/branches/branches-query'
+import { useDiskAttributesQuery } from 'data/config/disk-attributes-query'
import { useCheckGithubBranchValidity } from 'data/integrations/github-branch-check-query'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
-import { useCloneBackupsQuery } from 'data/projects/clone-query'
import { projectKeys } from 'data/projects/keys'
+import { DesiredInstanceSize, instanceSizeSpecs } from 'data/projects/new-project.constants'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
@@ -46,9 +48,18 @@ import {
Input_Shadcn_,
Label_Shadcn_ as Label,
Switch,
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
cn,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
+import {
+ estimateComputeSize,
+ estimateDiskCost,
+ estimateRestoreTime,
+} from './BranchManagement.utils'
export const CreateBranchModal = () => {
const { ref } = useParams()
@@ -60,90 +71,19 @@ export const CreateBranchModal = () => {
const gitlessBranching = useIsBranching2Enabled()
const allowDataBranching = useFlag('allowDataBranching')
- // [Joshen] This is meant to be short lived while we're figuring out how to control
- // requests to this endpoint. Kill switch in case we need to stop the requests
- const disableBackupsCheck = useFlag('disableBackupsCheckInCreatebranchmodal')
-
- const isProPlanAndUp = selectedOrg?.plan?.id !== 'free'
- const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
-
- const isBranch = projectDetails?.parent_project_ref !== undefined
- const projectRef =
- projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined
-
- const {
- data: connections,
- error: connectionsError,
- isLoading: isLoadingConnections,
- isSuccess: isSuccessConnections,
- isError: isErrorConnections,
- } = useGitHubConnectionsQuery({
- organizationId: selectedOrg?.id,
- })
-
- const { data: branches } = useBranchesQuery({ projectRef })
- const { data: addons } = useProjectAddonsQuery({ projectRef })
- const hasPitrEnabled =
- (addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined
- const { mutateAsync: checkGithubBranchValidity, isLoading: isChecking } =
- useCheckGithubBranchValidity({
- onError: () => {},
- })
- const {
- data: cloneBackups,
- error: cloneBackupsError,
- isLoading: isLoadingCloneBackups,
- } = useCloneBackupsQuery(
- { projectRef },
- {
- // [Joshen] Only trigger this request when the modal is opened
- enabled: showCreateBranchModal && !disableBackupsCheck,
- }
- )
- const targetVolumeSizeGb = cloneBackups?.target_volume_size_gb ?? 0
- const noPhysicalBackups = cloneBackupsError?.message.startsWith(
- 'Physical backups need to be enabled'
- )
-
- const { mutate: sendEvent } = useSendEventMutation()
-
- const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({
- onSuccess: async (data) => {
- toast.success(`Successfully created preview branch "${data.name}"`)
- if (projectRef) {
- await Promise.all([queryClient.invalidateQueries(projectKeys.detail(projectRef))])
- }
- sendEvent({
- action: 'branch_create_button_clicked',
- properties: {
- branchType: data.persistent ? 'persistent' : 'preview',
- gitlessBranching,
- },
- groups: {
- project: ref ?? 'Unknown',
- organization: selectedOrg?.slug ?? 'Unknown',
- },
- })
-
- setShowCreateBranchModal(false)
- router.push(`/project/${data.project_ref}`)
- },
- onError: (error) => {
- toast.error(`Failed to create branch: ${error.message}`)
- },
- })
const { can: canCreateBranch } = useAsyncCheckProjectPermissions(
PermissionAction.CREATE,
'preview_branches'
)
- const githubConnection = connections?.find((connection) => connection.project.ref === projectRef)
-
- // Fetch production/default branch to inspect git_branch linkage
- const prodBranch = branches?.find((branch) => branch.is_default)
+ const isProPlanAndUp = selectedOrg?.plan?.id !== 'free'
+ const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
- const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? []
+ const isBranch = projectDetails?.parent_project_ref !== undefined
+ const projectRef =
+ projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined
+ const noPhysicalBackups = !projectDetails?.is_physical_backups_enabled
const formId = 'create-branch-form'
const FormSchema = z
@@ -193,15 +133,95 @@ export const CreateBranchModal = () => {
})
const withData = form.watch('withData')
- const canSubmit = !isCreating && !isChecking
+ const {
+ data: connections,
+ error: connectionsError,
+ isLoading: isLoadingConnections,
+ isSuccess: isSuccessConnections,
+ isError: isErrorConnections,
+ } = useGitHubConnectionsQuery(
+ { organizationId: selectedOrg?.id },
+ { enabled: showCreateBranchModal }
+ )
+
+ const { data: branches } = useBranchesQuery({ projectRef })
+ const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery(
+ { projectRef },
+ { enabled: showCreateBranchModal }
+ )
+ const computeAddon = addons?.selected_addons.find((addon) => addon.type === 'compute_instance')
+ const computeSize = !!computeAddon
+ ? (computeAddon.variant.identifier.split('ci_')[1] as DesiredInstanceSize)
+ : undefined
+ const hasPitrEnabled =
+ (addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined
+
+ const {
+ data: disk,
+ isLoading: isLoadingDiskAttr,
+ isError: isErrorDiskAttr,
+ } = useDiskAttributesQuery({ projectRef }, { enabled: showCreateBranchModal && withData })
+ const projectDiskAttributes = disk?.attributes ?? {
+ type: 'gp3',
+ size_gb: 0,
+ iops: 0,
+ throughput_mbps: 0,
+ }
+ // Branch disk is oversized to include backup files, it should be scaled back eventually.
+ const branchDiskAttributes = {
+ ...projectDiskAttributes,
+ // [Joshen] JFYI for Qiao - this multiplier may eventually be dropped
+ size_gb: Math.round(projectDiskAttributes.size_gb * 1.5),
+ }
+ const branchComputeSize = estimateComputeSize(projectDiskAttributes.size_gb, computeSize)
+ const estimatedDiskCost = estimateDiskCost(branchDiskAttributes)
+
+ const { mutate: sendEvent } = useSendEventMutation()
+
+ const { mutateAsync: checkGithubBranchValidity, isLoading: isCheckingGHBranchValidity } =
+ useCheckGithubBranchValidity({
+ onError: () => {},
+ })
+
+ const { mutate: createBranch, isLoading: isCreatingBranch } = useBranchCreateMutation({
+ onSuccess: async (data) => {
+ toast.success(`Successfully created preview branch "${data.name}"`)
+ if (projectRef) {
+ await Promise.all([queryClient.invalidateQueries(projectKeys.detail(projectRef))])
+ }
+ sendEvent({
+ action: 'branch_create_button_clicked',
+ properties: {
+ branchType: data.persistent ? 'persistent' : 'preview',
+ gitlessBranching,
+ },
+ groups: {
+ project: ref ?? 'Unknown',
+ organization: selectedOrg?.slug ?? 'Unknown',
+ },
+ })
+
+ setShowCreateBranchModal(false)
+ router.push(`/project/${data.project_ref}`)
+ },
+ onError: (error) => {
+ toast.error(`Failed to create branch: ${error.message}`)
+ },
+ })
+
+ // Fetch production/default branch to inspect git_branch linkage
+ const githubConnection = connections?.find((connection) => connection.project.ref === projectRef)
+ const prodBranch = branches?.find((branch) => branch.is_default)
+ const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? []
+
const isDisabled =
+ !canCreateBranch ||
+ !isSuccessAddons ||
!isSuccessConnections ||
- isCreating ||
- !canSubmit ||
- isChecking ||
- (!gitlessBranching && !githubConnection) ||
promptProPlanUpgrade ||
- !canCreateBranch
+ (!gitlessBranching && !githubConnection) ||
+ isCreatingBranch ||
+ isCheckingGHBranchValidity
const onSubmit = (data: z.infer) => {
if (!projectRef) return console.error('Project ref is required')
@@ -209,6 +229,7 @@ export const CreateBranchModal = () => {
projectRef,
branchName: data.branchName,
is_default: false,
+ ...(data.withData ? { desired_instance_size: computeSize } : {}),
...(data.gitBranchName ? { gitBranch: data.gitBranchName } : {}),
...(allowDataBranching ? { withData: data.withData } : {}),
})
@@ -231,9 +252,7 @@ export const CreateBranchModal = () => {
size="large"
hideClose
onOpenAutoFocus={(e) => {
- if (promptProPlanUpgrade) {
- e.preventDefault()
- }
+ if (promptProPlanUpgrade) e.preventDefault()
}}
>
@@ -315,7 +334,9 @@ export const CreateBranchModal = () => {
/>
- {isChecking && }
+ {isCheckingGHBranchValidity && (
+
+ )}
@@ -363,7 +384,7 @@ export const CreateBranchModal = () => {
label={
<>
Include data
- {!disableBackupsCheck && (isLoadingCloneBackups || noPhysicalBackups) && (
+ {noPhysicalBackups && (
Requires PITR
@@ -376,9 +397,7 @@ export const CreateBranchModal = () => {
>
@@ -406,18 +425,91 @@ export const CreateBranchModal = () => {
-
- Data branch takes longer time to create
-
-
- Since your target database volume size is{' '}
- {targetVolumeSizeGb} GB, creating a
- data branch is estimated to take around{' '}
-
- {Math.round((720 / 21000) * targetVolumeSizeGb) + 3} minutes
-
- .
-
+ {isLoadingDiskAttr ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+ {isErrorDiskAttr ? (
+ <>
+
+ Branch disk size will incur additional cost per month
+
+
+ The additional cost and time taken to create a data branch is relative
+ to the size of your database. We are unable to provide an estimate as
+ we were unable to retrieve your project's disk configuration
+
+ >
+ ) : (
+ <>
+
+ Branch disk size is billed at ${estimatedDiskCost.total.toFixed(2)}{' '}
+ per month
+
+
+ Creating a data branch will take about{' '}
+
+ {estimateRestoreTime(branchDiskAttributes).toFixed()} minutes
+ {' '}
+ and costs{' '}
+
+ ${estimatedDiskCost.total.toFixed(2)}
+ {' '}
+ per month based on your current target database volume size of{' '}
+ {branchDiskAttributes.size_gb} GB and your{' '}
+
+
+
+ project's disk configuration
+
+
+
+
+
Disk type:
+
+ {branchDiskAttributes.type.toUpperCase()}
+
+
+
+
Targer disk size:
+
{branchDiskAttributes.size_gb} GB
+
(${estimatedDiskCost.size.toFixed(2)})
+
+
+
IOPs:
+
{branchDiskAttributes.iops} IOPS
+
(${estimatedDiskCost.iops.toFixed(2)})
+
+ {'throughput_mbps' in branchDiskAttributes && (
+
+
Throughput:
+
+ {branchDiskAttributes.throughput_mbps} MB/s
+
+
(${estimatedDiskCost.throughput.toFixed(2)})
+
+ )}
+
+ More info in{' '}
+ setShowCreateBranchModal(false)}
+ className="pointer-events-auto"
+ href={`/project/${ref}/settings/compute-and-disk`}
+ >
+ Compute and Disk
+
+
+
+
+ .
+
+ >
+ )}
+ >
+ )}
)}
@@ -439,7 +531,7 @@ export const CreateBranchModal = () => {
{prodBranch?.git_branch ? (
<>
When this branch is merged to{' '}
-
{prodBranch.git_branch},
+
{prodBranch.git_branch} ,
migrations will be deployed to production. Otherwise, migrations only run
on preview branches.
>
@@ -462,9 +554,21 @@ export const CreateBranchModal = () => {
-
Branches are billed $0.01344 per hour
+
+ Branch compute is billed at $
+ {withData ? branchComputeSize.priceHourly : instanceSizeSpecs.micro.priceHourly}{' '}
+ per hour
+
- This cost will continue for as long as the branch has not been removed.
+ {withData ? (
+ <>
+ {branchComputeSize.label} compute
+ size is automatically selected to match your production branch. You may
+ downgrade after creation or pause the branch when not in use to save cost.
+ >
+ ) : (
+ <>This cost will continue for as long as the branch has not been removed.>
+ )}