diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 1c5b6ea43bc..3eed078f08f 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -3,4 +3,5 @@
/apps/studio/ @supabase/Dashboard
/apps/www/ @supabase/Website
+/apps/www/_blog
/docker/ @supabase/cli
diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml
new file mode 100644
index 00000000000..8e8c46480f8
--- /dev/null
+++ b/.github/workflows/lint-prose.yml
@@ -0,0 +1,23 @@
+name: reviewdog
+on:
+ pull_request:
+ paths:
+ - '.vale.ini'
+ - 'vale/**'
+ - 'apps/docs/content/**'
+
+jobs:
+ vale:
+ name: runner / vale
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ sparse-checkout: |
+ .vale.ini
+ vale
+ apps/docs/content
+ - uses: errata-ai/vale-action@reviewdog
+ with:
+ files: apps/docs/content
+ fail_on_error: true
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 6e3c4d377e4..fde83f5826d 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -39,6 +39,8 @@ jobs:
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test:playwright
+ # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable.
+ continue-on-error: true
# - uses: actions/upload-artifact@v3
# if: always()
# with:
diff --git a/.vale.ini b/.vale.ini
new file mode 100644
index 00000000000..6b721717743
--- /dev/null
+++ b/.vale.ini
@@ -0,0 +1,12 @@
+StylesPath = './vale/styles'
+MinAlertLevel = 'suggestion'
+
+Vocab = Supabase
+
+[formats]
+mdx = md
+
+[*]
+BasedOnStyles = Vale, Custom
+BlockIgnores = (?s) *(.*?)
+TokenIgnores = (href=".*?")
diff --git a/apps/database-new/package.json b/apps/database-new/package.json
index 8c7e69955d4..c198c0d1858 100644
--- a/apps/database-new/package.json
+++ b/apps/database-new/package.json
@@ -21,7 +21,7 @@
"dayjs": "^1.11.10",
"eslint-config-next": "^14.1.0",
"lodash": "^4.17.21",
- "lucide-react": "^0.292.0",
+ "lucide-react": "^0.338.0",
"next": "^14.0.3",
"openai": "^4.17.0",
"react": "^18.2.0",
diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md
index cc5845c1cec..fa482554221 100644
--- a/apps/docs/CONTRIBUTING.md
+++ b/apps/docs/CONTRIBUTING.md
@@ -70,7 +70,7 @@ They shouldn't include:
## Repo organization
-Most docs pages are contained in the `apps/docs/pages` directory. Some docs sections are federated from other repositories, for example [`pg_graphql`](https://github.com/supabase/pg_graphql/tree/master/docs). Reference docs are generated from spec files in the `spec` directory.
+Most docs pages are contained in the `apps/docs/content` directory. Some docs sections are federated from other repositories, for example [`pg_graphql`](https://github.com/supabase/pg_graphql/tree/master/docs). Reference docs are generated from spec files in the `spec` directory.
You can usually identify a federated or reference doc because it uses a Next.js dynamic route (for example, `[[...slug]].tsx`). Look for the spec file import or the repo definition to find the content location.
@@ -98,43 +98,16 @@ The Supabase docs use [MDX](https://mdxjs.com/). Guides are written in unstructu
Adding a new guide requires:
-- A layout
-- Metadata
-- A page export
+- YAML frontmatter
- A navigation entry (in a separate file)
-```js
-// required import
-import Layout from '~/layouts/DefaultGuideLayout'
+Frontmatter looks like this. `title` is mandatory. There are also optional properties that you can use to control the page display, including `subtitle`, `tocVideo`, and `hideToc`.
-
-/*
- * required metadata fields
- * title: required - page title
- * description: required - page description
- *
- * optional metadata fields
- * id: optional - not currently being used
- * subtitle: optional - displayed below the title
- * sidebar_label: optional - if you want the sidebar text to be different from the title
- * hide_table_of_contents: optional - hides the table of contents on the right side
- * tocVideo: optional - a YouTube slug, displays the video above the table of contents
- */
-export const meta = {
- id: 'nextjs-server-components',
- title: 'Supabase Auth with Next.js Server Components',
- description:
- 'Authentication helpers for creating an authenticated Supabase client in Next.js 13 app directory Server Components.',
- sidebar_label: 'Next.js Server Components',
-}
-
-// content
-You can use **Markdown syntax**.
-
-// required export
-export const Page = ({ children }) =>
-
-export default Page
+```yaml
+---
+title: How to connect to Supabase
+hideToc: true
+---
```
The navigation is defined in [`NavigationMenu.constants.ts`](https://github.com/supabase/supabase/blob/master/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts).
@@ -305,13 +278,12 @@ The `queryGroup` param is optional. It lets you link directly to a tab by using
Include videos as TOC (Table of Contents) videos rather than putting them in the main text.
-You can define a TOC video in the page metadata:
+You can define a TOC video in the page frontmatter:
-```js
-export const meta = {
- ...
- tocVideo: 'rzglqRdZUQE',
-}
+```yaml
+---
+tocVideo: 'rzglqRdZUQE',
+---
```
## Styling, formatting, and grammar
diff --git a/apps/docs/components/IPv4DeprecationBanner.tsx b/apps/docs/components/IPv4DeprecationBanner.tsx
deleted file mode 100644
index f44662e2069..00000000000
--- a/apps/docs/components/IPv4DeprecationBanner.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import Link from 'next/link'
-import { memo } from 'react'
-
-import { Button, IconExternalLink, cn } from 'ui'
-
-export const IPv4DeprecationBanner = memo(() => {
- return (
-
-
- Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024{' '}
-
- Learn more
-
-
- )
-})
-IPv4DeprecationBanner.displayName = 'IPv4DeprecationBanner'
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx
index 984db7537a1..525fb496f1e 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx
@@ -1,13 +1,45 @@
-import { useRouter } from 'next/router'
-import { memo, useEffect } from 'react'
-import { menuState, useMenuLevelId } from '~/hooks/useMenuState'
+import { memo } from 'react'
import NavigationMenuHome from './HomeMenu'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
+enum MenuId {
+ Home = 'home',
+ GettingStarted = 'gettingstarted',
+ Database = 'database',
+ Api = 'api',
+ Graphql = 'graphql',
+ Auth = 'auth',
+ Functions = 'functions',
+ Realtime = 'realtime',
+ Storage = 'storage',
+ Ai = 'ai',
+ Platform = 'platform',
+ Resources = 'resources',
+ SelfHosting = 'self_hosting',
+ Integrations = 'integrations',
+ Cli = 'supabase_cli',
+ RefJavaScriptV1 = 'reference_javascript_v1',
+ RefJavaScriptV2 = 'reference_javascript_v2',
+ RefDartV1 = 'reference_dart_v1',
+ RefDartV2 = 'reference_dart_v2',
+ RefCSharpV0 = 'reference_csharp_v0',
+ RefPythonV2 = 'reference_python_v2',
+ RefSwiftV1 = 'reference_swift_v1',
+ RefSwiftV2 = 'reference_swift_v2',
+ RefKotlinV1 = 'reference_kotlin_v1',
+ RefKotlinV2 = 'reference_kotlin_v2',
+ RefCli = 'reference_cli',
+ RefApi = 'reference_api',
+ SelfHostingAuth = 'reference_self_hosting_auth',
+ SelfHostingStorage = 'reference_self_hosting_storage',
+ SelfHostingRealtime = 'reference_self_hosting_realtime',
+ SelfHostingAnalytics = 'reference_self_hosting_analytics',
+ SelfHostingFunctions = 'reference_self_hosting_functions',
+}
+
interface BaseMenu {
- id: string
- path: string
+ id: MenuId
type: string
}
@@ -21,6 +53,7 @@ interface GuideMenu extends BaseMenu {
interface ReferenceMenu extends BaseMenu {
type: 'reference'
+ path: string
commonSectionsFile: string
specFile?: string
}
@@ -29,211 +62,181 @@ type Menu = HomeMenu | GuideMenu | ReferenceMenu
const menus: Menu[] = [
{
- id: 'home',
- path: '/',
+ id: MenuId.Home,
type: 'home',
},
{
- id: 'gettingstarted',
- path: '/guides/getting-started',
+ id: MenuId.GettingStarted,
type: 'guide',
},
{
- id: 'database',
- path: '/guides/database',
+ id: MenuId.Database,
type: 'guide',
},
{
- id: 'api',
- path: '/guides/api',
+ id: MenuId.Api,
type: 'guide',
},
{
- id: 'graphql',
- path: '/guides/graphql',
+ id: MenuId.Graphql,
type: 'guide',
},
{
- id: 'auth',
- path: '/guides/auth',
+ id: MenuId.Auth,
type: 'guide',
},
{
- id: 'auth',
- path: '/learn/auth-deep-dive',
+ id: MenuId.Functions,
type: 'guide',
},
{
- id: 'functions',
- path: '/guides/functions',
+ id: MenuId.Realtime,
type: 'guide',
},
{
- id: 'realtime',
- path: '/guides/realtime',
+ id: MenuId.Storage,
type: 'guide',
},
{
- id: 'storage',
- path: '/guides/storage',
+ id: MenuId.Ai,
type: 'guide',
},
{
- id: 'ai',
- path: '/guides/ai',
+ id: MenuId.Platform,
type: 'guide',
},
{
- id: 'platform',
- path: '/guides/platform',
+ id: MenuId.Resources,
type: 'guide',
},
{
- id: 'resources',
- path: '/guides/resources',
+ id: MenuId.SelfHosting,
type: 'guide',
},
{
- id: 'self_hosting',
- path: '/guides/self-hosting',
+ id: MenuId.Integrations,
type: 'guide',
},
{
- id: 'integrations',
- path: '/guides/integrations',
+ id: MenuId.Cli,
type: 'guide',
},
{
- id: 'supabase_cli',
- // TODO: Add path '/guides/cli/config'
- path: '/guides/cli',
- type: 'guide',
- },
- {
- id: 'reference_javascript_v1',
- path: '/reference/javascript/v1',
+ id: MenuId.RefJavaScriptV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_js_v1.yml',
type: 'reference',
+ path: '/reference/javascript/v1',
},
{
- id: 'reference_javascript_v2',
- path: '/reference/javascript',
+ id: MenuId.RefJavaScriptV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_js_v2.yml',
type: 'reference',
+ path: '/reference/javascript',
},
{
- id: 'reference_dart_v1',
- path: '/reference/dart/v1',
+ id: MenuId.RefDartV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_dart_v1.yml',
type: 'reference',
+ path: '/reference/dart/v1',
},
{
- id: 'reference_dart_v2',
- path: '/reference/dart',
+ id: MenuId.RefDartV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_dart_v2.yml',
type: 'reference',
+ path: '/reference/dart',
},
{
- id: 'reference_csharp_v0',
- path: '/reference/csharp',
+ id: MenuId.RefCSharpV0,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_csharp_v0.yml',
type: 'reference',
+ path: '/reference/csharp',
},
{
- id: 'reference_python_v2',
- path: '/reference/python',
+ id: MenuId.RefPythonV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_py_v2.yml',
type: 'reference',
+ path: '/reference/python',
},
{
- id: 'reference_swift_v1',
- path: '/reference/swift',
+ id: MenuId.RefSwiftV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_swift_v1.yml',
type: 'reference',
+ path: '/reference/swift',
},
{
- id: 'reference_swift_v2',
- path: '/reference/swift',
+ id: MenuId.RefSwiftV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_swift_v2.yml',
type: 'reference',
+ path: '/reference/swift',
},
{
- id: 'reference_kotlin_v1',
- path: '/reference/kotlin/v1',
+ id: MenuId.RefKotlinV1,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_kt_v1.yml',
type: 'reference',
+ path: '/reference/kotlin/v1',
},
{
- id: 'reference_kotlin_v2',
- path: '/reference/kotlin',
+ id: MenuId.RefKotlinV2,
commonSectionsFile: 'common-client-libs-sections.json',
specFile: 'supabase_kt_v2.yml',
type: 'reference',
+ path: '/reference/kotlin',
},
{
- id: 'reference_cli',
- path: '/reference/cli',
+ id: MenuId.RefCli,
commonSectionsFile: 'common-cli-sections.json',
type: 'reference',
+ path: '/reference/cli',
},
{
- id: 'reference_api',
- path: '/reference/api',
+ id: MenuId.RefApi,
commonSectionsFile: 'common-api-sections.json',
type: 'reference',
+ path: '/reference/api',
},
{
- id: 'reference_self_hosting_auth',
- path: '/reference/self-hosting-auth',
+ id: MenuId.SelfHostingAuth,
commonSectionsFile: 'common-self-hosting-auth-sections.json',
type: 'reference',
+ path: '/reference/self-hosting-auth',
},
{
- id: 'reference_self_hosting_storage',
- path: '/reference/self-hosting-storage',
+ id: MenuId.SelfHostingStorage,
commonSectionsFile: 'common-self-hosting-storage-sections.json',
type: 'reference',
+ path: '/reference/self-hosting-storage',
},
{
- id: 'reference_self_hosting_realtime',
- path: '/reference/self-hosting-realtime',
+ id: MenuId.SelfHostingRealtime,
commonSectionsFile: 'common-self-hosting-realtime-sections.json',
type: 'reference',
+ path: '/reference/self-hosting-realtime',
},
{
- id: 'reference_self_hosting_analytics',
- path: '/reference/self-hosting-analytics',
+ id: MenuId.SelfHostingAnalytics,
commonSectionsFile: 'common-self-hosting-analytics-sections.json',
type: 'reference',
+ path: '/reference/self-hosting-analytics',
},
{
- id: 'reference_self_hosting_functions',
- path: '/reference/self-hosting-functions',
+ id: MenuId.SelfHostingFunctions,
commonSectionsFile: 'common-self-hosting-functions-sections.json',
type: 'reference',
+ path: '/reference/self-hosting-functions',
},
]
-function getMenuById(id: string) {
- return menus.find((menu) => menu.id === id)
-}
-
-function getMenuByUrl(basePath: string, url: string) {
- // If multiple matches, choose the menu with the longest path
- const [menu] = menus
- .filter(({ path }) => url.startsWith(`${basePath}${path}`))
- .sort((a, b) => b.path.length - a.path.length)
-
- return menu
+function getMenuById(id: MenuId) {
+ return menus.find((menu) => menu.id === id) ?? menus.find((menu) => menu.id === MenuId.Home)
}
function getMenuElement(menu: Menu) {
@@ -257,29 +260,12 @@ function getMenuElement(menu: Menu) {
}
}
-const NavigationMenu = () => {
- const router = useRouter()
-
- useEffect(() => {
- function handleRouteChange(url: string) {
- const menu = getMenuByUrl(router.basePath, url)
- if (menu) {
- menuState.setMenuLevelId(menu.id)
- }
- }
-
- handleRouteChange(router.basePath + router.asPath)
- // Listen for page changes after a navigation or when the query changes
- router.events.on('routeChangeComplete', handleRouteChange)
- return () => {
- router.events.off('routeChangeComplete', handleRouteChange)
- }
- }, [router.asPath, router.basePath, router.events])
-
- const level = useMenuLevelId()
+const NavigationMenu = ({ menuId }: { menuId: MenuId }) => {
+ const level = menuId
const menu = getMenuById(level)
return getMenuElement(menu)
}
+export { MenuId }
export default memo(NavigationMenu)
diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx
index e100cfbcb20..a633824d45f 100644
--- a/apps/docs/components/reference/RefSectionHandler.tsx
+++ b/apps/docs/components/reference/RefSectionHandler.tsx
@@ -2,6 +2,7 @@ import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
+import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import RefEducationSection from '~/components/reference/RefEducationSection'
import RefFunctionSection from '~/components/reference/RefFunctionSection'
@@ -10,6 +11,7 @@ import ApiOperationSection from './ApiOperationSection'
import CliCommandSection from './CLICommandSection'
import OldVersionAlert from './OldVersionAlert'
import { IAPISpec, ICommonSection, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
+import { MainSkeleton } from '~/layouts/MainSkeleton'
interface RefSectionHandlerProps {
sections: ICommonSection[]
@@ -18,6 +20,7 @@ interface RefSectionHandlerProps {
pageProps: { docs: IRefStaticDoc[] }
type: 'client-lib' | 'cli' | 'api'
isOldVersion?: boolean
+ menuId: MenuId
}
const RefSectionHandler = (props: RefSectionHandlerProps) => {
@@ -75,53 +78,55 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => {
- {props.isOldVersion && }
-
- {props.sections.map((section, i) => {
- const sectionType = section.type
- switch (sectionType) {
- case 'markdown':
- const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id)
+
+ {props.isOldVersion && }
+
+ {props.sections.map((section, i) => {
+ const sectionType = section.type
+ switch (sectionType) {
+ case 'markdown':
+ const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id)
- return (
-
- )
- case 'function':
- return (
-
- )
- case 'cli-command':
- return (
-
- )
- case 'operation':
- return (
-
- )
- default:
- throw new Error(`Unknown common section type '${sectionType}'`)
- }
- })}
-
+ return (
+
+ )
+ case 'function':
+ return (
+
+ )
+ case 'cli-command':
+ return (
+
+ )
+ case 'operation':
+ return (
+
+ )
+ default:
+ throw new Error(`Unknown common section type '${sectionType}'`)
+ }
+ })}
+
+
>
)
}
diff --git a/apps/docs/content/guides/auth/social-login/auth-apple.mdx b/apps/docs/content/guides/auth/social-login/auth-apple.mdx
index 1bc10b0de90..daa141be5f0 100644
--- a/apps/docs/content/guides/auth/social-login/auth-apple.mdx
+++ b/apps/docs/content/guides/auth/social-login/auth-apple.mdx
@@ -240,15 +240,16 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app,
1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._)
2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_.
-
-
- If you're building a native app only, you do not need to configure the OAuth settings.
-
-
-
## Apple sign in on Android, Web, Windows and Linux
For platforms that doesn't support native Apple sign in, you can use the `signInWithOAuth()` method to perform the Apple sign in.
+
+
+
+ Do **NOT** follow the Android or Web setup instructions on [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package README for these platforms. sign_in_with_apple package is not used for performing Apple sign-in on non-Apple platforms for Supabase.
+
+
+
This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter).
```dart
diff --git a/apps/docs/content/guides/auth/social-login/auth-google.mdx b/apps/docs/content/guides/auth/social-login/auth-google.mdx
index 38f6936b717..6279925e6f3 100644
--- a/apps/docs/content/guides/auth/social-login/auth-google.mdx
+++ b/apps/docs/content/guides/auth/social-login/auth-google.mdx
@@ -16,7 +16,7 @@ There are three general ways to use Sign in with Google, depending on the applic
- Sign in on the web or in web-based apps
- Using an OAuth flow initiated by Supabase Auth using [Google Identity Authorization with OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server).
- Using a [personalized sign-in button](https://developers.google.com/identity/gsi/web/guides/personalized-button) or [One Tap](https://developers.google.com/identity/gsi/web/guides/display-google-one-tap) and [automatic sign-in](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out) for users already logged in to their Google account.
-- Sign in in native Android apps and Chrome extensions
+- Sign in in native Android apps and Chrome extensions.
In some cases you're able to use the OAuth flow within web-based native apps such as with React Native, Expo or other similar frameworks. It is best practice to use native Sign in with Google capabilities whenever possible.
@@ -232,7 +232,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
- ## Using native sign in with Google in Flutter
+ ## Using native sign-in with Google on Flutter
Native Google sign in with Supabase is done through the [google_sign_in](https://pub.dev/packages/google_sign_in) package for iOS and Android. Follow the instructions on the [google_sign_in](https://pub.dev/packages/google_sign_in) package for plugin specific settings.
@@ -301,11 +301,114 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
- Unlike the OAuth flow which requires the use of a web browser, the native Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developers.google.com/android/reference/com/google/android/gms/auth/api/identity/package-summary) to prompt the user for consent. Note that native sign-in has been rebranded as _One Tap sign-in on Android_ by Google, which you should not confuse with _One Tap sign in for web_, as mentioned below.
+ ## Using Google sign-in on Android
+
+ The Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developer.android.com/training/sign-in/credential-manager) to prompt the user for consent.
When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth.
- When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`.
+ **Note:** You have to create OAuth client IDs for both a Web and Android application. The Web client ID is the one used in your Android app.
+
+ Add the following dependencies to your app. You can find the latest version of `credentials` [here](https://developer.android.com/jetpack/androidx/releases/credentials) and `googleid` [here](https://developers.google.com/identity/android-credential-manager/releases).
+
+ ```kotlin
+ implementation("androidx.credentials:credentials:")
+ implementation ("com.google.android.libraries.identity.googleid:googleid:")
+
+ // optional - needed for credentials support from play services, for devices running
+ // Android 13 and below.
+ implementation("androidx.credentials:credentials-play-services-auth:")
+ ```
+
+ Add the following proguard rules to your `proguard-rules.pro` file:
+
+ ```proguard
+ -if class androidx.credentials.CredentialManager
+ -keep class androidx.credentials.playservices.** {
+ *;
+ }
+ ```
+
+ Follow the code sample below to implement native Google sign-in with Supabase using Credential Manager in your Android app.
+
+ ```kotlin
+ @Composable
+ fun GoogleSignInButton() {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+
+ val onClick: () -> Unit = {
+ val credentialManager = CredentialManager.create(context)
+
+ // Generate a nonce and hash it with sha-256
+ // Providing a nonce is optional but recommended
+ val rawNonce = UUID.randomUUID().toString() // Generate a random String. UUID should be sufficient, but can also be any other random string.
+ val bytes = rawNonce.toString().toByteArray()
+ val md = MessageDigest.getInstance("SHA-256")
+ val digest = md.digest(bytes)
+ val hashedNonce = digest.fold("") { str, it -> str + "%02x".format(it) } // Hashed nonce to be passed to Google sign-in
+
+
+ val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
+ .setFilterByAuthorizedAccounts(false)
+ .setServerClientId("WEB_GOOGLE_CLIENT_ID")
+ .setNonce(hashedNonce) // Provide the nonce if you have one
+ .build()
+
+ val request: GetCredentialRequest = GetCredentialRequest.Builder()
+ .addCredentialOption(googleIdOption)
+ .build()
+
+ coroutineScope.launch {
+ try {
+ val result = credentialManager.getCredential(
+ request = request,
+ context = context,
+ )
+
+ val googleIdTokenCredential = GoogleIdTokenCredential
+ .createFrom(result.credential.data)
+
+ val googleIdToken = googleIdTokenCredential.idToken
+
+ supabase.auth.signInWith(IDToken) {
+ idToken = googleIdToken
+ provider = Google
+ nonce = rawNonce
+ }
+
+ // Handle successful sign-in
+ } catch (e: GetCredentialException) {
+ // Handle GetCredentialException thrown by `credentialManager.getCredential()`
+ } catch (e: GoogleIdTokenParsingException) {
+ // Handle GoogleIdTokenParsingException thrown by `GoogleIdTokenCredential.createFrom()`
+ } catch (e: RestException) {
+ // Handle RestException thrown by Supabase
+ } catch (e: Exception) {
+ // Handle unknown exceptions
+ }
+ }
+ }
+
+ Button(
+ onClick = onClick,
+ ) {
+ Text("Sign in with Google")
+ }
+ }
+ ```
+
+ ### Configuration [#android-configuration]
+
+ 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. You need two client IDs, a web client ID and Android client ID. When creating a new Android OAuth client ID, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app.
+ 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
+ 3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_.
+
+ Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in.
+
+ ## Using Google sign-in with Kotlin Multiplatform
+
+ When using [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`.
Alternatively, you can have a look at [Google One Tap](https://developers.google.com/identity/one-tap/android/overview), just login with the IdToken after you receive it.
**Initialize the Supabase Client**
@@ -343,7 +446,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
}
```
- ### Configuration [#kotlin-configuration-native-app]
+ ### Configuration [#kotlin-multiplatform-configuration]
1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for.
@@ -353,8 +456,6 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf
2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service.
3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_.
- Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in.
-
diff --git a/apps/docs/content/guides/functions/dart-edge.mdx b/apps/docs/content/guides/functions/dart-edge.mdx
index 32f89e469a4..98b45295dfe 100644
--- a/apps/docs/content/guides/functions/dart-edge.mdx
+++ b/apps/docs/content/guides/functions/dart-edge.mdx
@@ -2,20 +2,16 @@
id: 'functions-dart-edge'
title: 'Dart Edge'
description: 'Write your functions using Dart.'
+tocVideo: '53jhgrDONW4'
---
[Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase.
-> ⚠️ WARNING: Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues.
+
-
-
-
+Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues.
+
+
## Pre requirements
@@ -122,10 +118,29 @@ void main() {
### Run locally
+Create a `edge.yaml` file if you don't have one yet in the root of your project. Then, add the following configuration to specify the path to your Supabase project.
+
+```yaml
+supabase:
+ project_path: './supabase'
+```
+
+Also, currently Dart Edge doesn't work with the latest versions of Flutter. If you do have the latest version of Flutter on your machine, you can use [FVM](https://fvm.app/) to use an older version of Flutter for your Dart Edge project.
+
+```bash
+fvm use 3.7.12 -f
+```
+
+You can read more about how to configure FVM (Flutter
+Version Management) for your project in the [FVM documentation](https://fvm.app/documentation/getting-started).
+
Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code.
```bash
edge build supabase_functions --dev
+
+# Or with FVM
+fvm dart pub run edge build supabase_functions --dev
```
With docker running, start your local Supabase with the following command
diff --git a/apps/docs/content/guides/platform/ipv4-address.mdx b/apps/docs/content/guides/platform/ipv4-address.mdx
index 2a15dc1332e..5d5c1038df8 100644
--- a/apps/docs/content/guides/platform/ipv4-address.mdx
+++ b/apps/docs/content/guides/platform/ipv4-address.mdx
@@ -1,11 +1,9 @@
---
-title: 'IPv4 Address'
+title: 'Dedicated IPv4 Address'
description: 'Attach an IPv4 address to your database'
subtitle: 'Attach an IPv4 address to your database'
---
-From 26th January 2024, we will begin migrating projects to IPv6. To read more about why we are doing this migration, read [this blog post](https://supabase.com/blog/ipv6) and [this discussion](https://github.com/orgs/supabase/discussions/17817) for how to prepare for the migration. As a summary, AWS will [start charging for IPv4 addresses](https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/).
-
The add-on is $4 per month - we're passing on the AWS costs to you. The add-on is available for all paid plans.
## When is this add-on required?
@@ -25,7 +23,7 @@ When you purchase this add-on, your project won't be migrated to IPv6. PGBouncer
## Enabling the add-on
-You can now enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page.
+You can enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page.
Note that there might be some downtime when toggling the add-on since some DNS clients might have
@@ -33,6 +31,10 @@ You can now enable the IPv4 add-on in your project [add-on settings](https://sup
Generally, this should be less than a minute.
-
+## Caveats
-
+For regular use-cases like a restart or compute size changes, the IPv4 address remains unchanged.
+However, there are cases where maintaining the same IPv4 address isn't guaranteed. These include:
+
+- If the project gets paused and unpaused
+- If the add-on gets removed and added again
diff --git a/apps/docs/content/guides/platform/maturity-model.mdx b/apps/docs/content/guides/platform/maturity-model.mdx
index 1a4f414063e..8bcad22377b 100644
--- a/apps/docs/content/guides/platform/maturity-model.mdx
+++ b/apps/docs/content/guides/platform/maturity-model.mdx
@@ -22,12 +22,14 @@ Resources:
## In production
-Once your application is live, you should never change your database using the Dashboard. Supabase provides various [access levels](https://supabase.com/docs/guides/platform/access-control) for the Dashboard that can help enforce this. Some other important things to consider at this point include:
+Once your application is live, you should never change your database using the Dashboard - everything should be done with [Migrations](/docs/guides/cli/managing-environments#create-a-new-migration). Some other important things to consider at this point include:
-- Read the [Production Checklist](/docs/guides/platform/going-into-prod).
-- Make your team aware of the [Shared Responsibilities](/docs/guides/platform/shared-responsibility-model) between your organization and Supabase.
-- Design a [safe workflow](https://supabase.com/docs/guides/platform/shared-responsibility-model#you-decide-your-own-workflow) for managing your database. We strongly recommend running [multiple environments](/docs/guides/cli/managing-environments) as part of your development workflow (`local` -> `staging` -> `prod`).
+- The Dashboard has various [access levels](/docs/guides/platform/access-control) that can prevent changes being made via the UI.
+- Design a [safe workflow](/docs/guides/platform/shared-responsibility-model#you-decide-your-own-workflow) for managing your database. We strongly recommend running [multiple environments](/docs/guides/cli/managing-environments) as part of your development workflow (`local` -> `staging` -> `prod`).
+- Do not share any production passwords with your team, _especially_ your `postgres` password. All changes should be made via version-controlled migrations which run via a bastion host or a CI platform (like [GitHub Actions](/docs/guides/cli/managing-environments#configure-github-actions). If you use GitHub Actions, use [approval workflows](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments) to prevent any migrations being run accidentally.
+- Restrict production access to your database using [Network Restrictions](/docs/guides/platform/network-restrictions).
- As your database to grows, we strongly recommend moving to [Point-in-Time Recovery](/docs/guides/platform/backups#point-in-time-recovery). This is safer and has less impact on your database performance during maintenance windows.
+- Read the [Production Checklist](/docs/guides/platform/going-into-prod) and familiarize your team with the [Shared Responsibilities](/docs/guides/platform/shared-responsibility-model) between your organization and Supabase.
Resources:
diff --git a/apps/docs/content/guides/platform/read-replicas.mdx b/apps/docs/content/guides/platform/read-replicas.mdx
index d8ef2b741a5..29f048a7a1b 100644
--- a/apps/docs/content/guides/platform/read-replicas.mdx
+++ b/apps/docs/content/guides/platform/read-replicas.mdx
@@ -135,16 +135,22 @@ All settings made through the dashboard will be propagated across all databases
A project needs to satisfy these requirements to spin up a read replica:
1. Running on AWS.
- - Support for projects on Fly is in progress.
-2. Running on at least a small compute.
-3. PostgreSQL 15.
- - For projects running on older versions of PostgreSQL, you can initiate self-serve upgrades with pg_upgrade. For details, see the [upgrade guide](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade).
+ - Support for projects on Fly.io will be added at a later date.
+1. Running on at least a [Small compute add-on](/docs/guides/platform/compute-add-ons).
+1. Using [physical backups](/docs/guides/platform/backups#point-in-time-recovery)
+ - Physical backups are automatically enabled if using [PITR](/docs/guides/platform/backups#point-in-time-recovery)
+1. Running on Postgres 15.
+ - For projects running on older versions of PostgreSQL, you will need to [upgrade to the latest platform version](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade).
### Read replicas need to be brought down before starting the following procedures
The following procedures require all read replicas for a project to be brought down before they can be started:
-1. Upgrading a project via pg_upgrade.
-2. Performing data restoration.
+1. [Upgrading a project](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade)
+1. Performing [data restoration](/docs/guides/platform/backups#pitr-restoration-process).
-Changes made to the primary database during these operations heavily disrupt replication and potentially leave active read replicas in unstable states. Once these operations complete, read replicas can be spun back up.
+Once these operations complete, read replicas can be spun back up.
+
+### Compute Add-ons
+
+In order to avoid replicas being unable to keep up with activity on the primaries they're following, Read Replicas are currently placed on the same Compute Add-on as the primary they're following.
diff --git a/apps/docs/hooks/useMenuState.ts b/apps/docs/hooks/useMenuState.ts
index 52977307542..9cbcba155e7 100644
--- a/apps/docs/hooks/useMenuState.ts
+++ b/apps/docs/hooks/useMenuState.ts
@@ -7,11 +7,6 @@ export const menuState = proxy({
setMenuActiveRefId: (value) => {
menuState.menuActiveRefId = value
},
- menuLevelId: 'home',
- setMenuLevelId: (value) => {
- menuState.menuMobileOpen = false
- menuState.menuLevelId = value
- },
menuMobileOpen: false,
setMenuMobileOpen: (value) => {
menuState.menuMobileOpen = value
@@ -21,9 +16,6 @@ export const menuState = proxy({
export const useMenuActiveRefId = () => {
return useSnapshot(menuState).menuActiveRefId
}
-export const useMenuLevelId = () => {
- return useSnapshot(menuState).menuLevelId
-}
export const useMenuMobileOpen = () => {
return useSnapshot(menuState).menuMobileOpen
diff --git a/apps/docs/internals/generate-sitemap.mjs b/apps/docs/internals/generate-sitemap.mjs
index c82e2709deb..7d099056df7 100644
--- a/apps/docs/internals/generate-sitemap.mjs
+++ b/apps/docs/internals/generate-sitemap.mjs
@@ -45,7 +45,6 @@ async function generate() {
${`https://supabase.com/docs/${path}`}weekly
- 0.5
`
})
diff --git a/apps/docs/layouts/DefaultGuideLayout.tsx b/apps/docs/layouts/DefaultGuideLayout.tsx
index 9bda33bb1df..08a632fbc9f 100644
--- a/apps/docs/layouts/DefaultGuideLayout.tsx
+++ b/apps/docs/layouts/DefaultGuideLayout.tsx
@@ -1,5 +1,6 @@
import { FC } from 'react'
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
+import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import GuideLayout from './guides'
interface Props {
@@ -16,6 +17,7 @@ interface Props {
hideToc?: boolean
currentPage?: string
editLink?: string
+ menuId: MenuId
}
const Layout: FC = (props) => {
diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx
index b672c43f4a6..6bfc53a2564 100644
--- a/apps/docs/layouts/DefaultLayout.tsx
+++ b/apps/docs/layouts/DefaultLayout.tsx
@@ -5,6 +5,7 @@ import { useRouter } from 'next/router'
import { FC, PropsWithChildren } from 'react'
import components from '~/components'
import TableOfContents from '~/components/TableOfContents'
+import { MainSkeleton } from './MainSkeleton'
interface Props {
meta: {
diff --git a/apps/docs/layouts/HomeLayout.tsx b/apps/docs/layouts/HomeLayout.tsx
index 30d8627a622..9f5e7d46320 100644
--- a/apps/docs/layouts/HomeLayout.tsx
+++ b/apps/docs/layouts/HomeLayout.tsx
@@ -4,9 +4,11 @@ import Head from 'next/head'
import { useRouter } from 'next/router'
import { FC } from 'react'
import components from '~/components'
+import HomePageCover from '~/components/HomePageCover'
+import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TableOfContents from '~/components/TableOfContents'
-import HomePageCover from '../components/HomePageCover'
import { LayoutMainContent } from './DefaultLayout'
+import { MainSkeleton } from './MainSkeleton'
interface Props {
meta: {
@@ -61,26 +63,28 @@ const HomeLayout: FC = (props: Props) => {
},
}}
/>
-
-
-
-
+
+ {hasReadReplicas
+ ? 'You will need to remove all read replicas first to trigger a PITR recovery'
+ : !canTriggerPhysicalBackup
+ ? 'You need additional permissions to trigger a PITR recovery'
+ : null}
+
+