diff --git a/DEVELOPERS.md b/DEVELOPERS.md
index 26618c6f400..505029ef2e6 100644
--- a/DEVELOPERS.md
+++ b/DEVELOPERS.md
@@ -132,6 +132,26 @@ Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/
---
+### Federated docs
+
+We support "federating" docs, meaning doc content can come directly from external repos other than [`supabase/supabase`](https://github.com/supabase/supabase).
+
+- It's great for things like client libs who have their own set of docs that we don't want to duplicate on the official Supabase docs (eg. [`supabase/vecs`](https://github.com/supabase/vecs)).
+- No duplication or manual steps required - fetches and generates automatically as part of the docs build pipeline
+- It's flexible - you can "embed" external docs nearly anywhere at any level in Supabase docs, but they will feel native
+- If you are maintaining a repo containing docs that you think could also live in Supabase docs, feel free to create an issue and we can work together to integrate
+
+Federated docs work using Next.js's build pipeline. We use `getStaticProps()` to fetch remote documentation (ie. markdown) at build time which is processed and passed to the respective page within the docs.
+
+See the [Vecs Python source code](https://github.com/supabase/supabase/blob/master/apps/docs/pages/guides/ai/python/%5Bslug%5D.tsx) to see how we do this for [`supabase/vecs`](https://github.com/supabase/vecs). Use this as a starting point for federating other docs.
+
+Some things to consider:
+
+- Links will often need to be transformed. For example if you are bringing in external markdown content, they may contain relative links that may not translate 1-to-1 after rendering in the Supabase docs. Use the [Link Transform](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/rehypeLinkTransform.ts) rehype plugin to transform links.
+- External markdown may contain syntax extensions that Supabase docs don't understand by default (eg. [mkdocs-material extensions](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown)). We've built a few remark plugins to support these extensions (eg. [MkDocs Admonition](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/remarkAdmonition.ts)). If there is a markdown extension that you need that isn't built yet, feel free to open an issue and we can work together to create it.
+
+---
+
## Community channels
If you are stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help!
diff --git a/apps/docs/components/AppleSecretGenerator.tsx b/apps/docs/components/AppleSecretGenerator.tsx
index 7bfeed67dcc..4029b1d8770 100644
--- a/apps/docs/components/AppleSecretGenerator.tsx
+++ b/apps/docs/components/AppleSecretGenerator.tsx
@@ -1,6 +1,5 @@
import { useState } from 'react'
-import { Input, Button } from 'ui'
-import Admonition from '~/components/Admonition'
+import { Admonition, Button, Input } from 'ui'
function base64URL(value: string) {
return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_')
diff --git a/apps/docs/components/Frameworks.tsx b/apps/docs/components/Frameworks.tsx
index 94464c4c395..2b88f604748 100644
--- a/apps/docs/components/Frameworks.tsx
+++ b/apps/docs/components/Frameworks.tsx
@@ -37,6 +37,14 @@ const Frameworks = () => {
},
href: '/reference/javascript/installing#javascript',
},
+ {
+ name: 'Kotlin',
+ logo: {
+ light: '/docs/img/icons/kotlin-icon.svg',
+ dark: '/docs/img/icons/kotlin-icon.svg',
+ },
+ href: '/guides/with-kotlin',
+ },
{
name: 'Next.js',
logo: {
diff --git a/apps/docs/components/MDX/auth_helpers_faq.mdx b/apps/docs/components/MDX/auth_helpers_faq.mdx
deleted file mode 100644
index 0f9a289d784..00000000000
--- a/apps/docs/components/MDX/auth_helpers_faq.mdx
+++ /dev/null
@@ -1,31 +0,0 @@
-### Fetch requests to API endpoints aren't showing the session
-
-You must pass along the cookie header with the fetch request in order for your API endpoint to get access to the cookie from this request.
-
-```ts
-const res = await fetch('http://localhost/contact', {
- headers: {
- cookie: headers().get('cookie') as string,
- },
-})
-```
-
-### Performing administration tasks on the server side with the `service_role` `secret`
-
-By default, the auth-helpers do not permit the use of the `service_role` `secret`. This restriction is in place to prevent the accidental exposure of your `service_role` `secret` to the public. Since the auth-helpers function on both the server and client side, it becomes challenging to separate the key specifically for client-side usage.
-
-However, there is a solution. You can create a separate Supabase client using the `createClient` method from `@supabase/supabase-js` and provide it with the `service_role` `secret`. In a server environment, you will also need to disable certain properties to ensure proper functionality.
-
-By implementing this approach, you can safely utilize the `service_role` `secret` without compromising security or exposing sensitive information to the public.
-
-```ts
-import { createClient } from '@supabase/supabase-js'
-
-const supabase = createClient(supabaseUrl, serviceRoleSecret, {
- auth: {
- persistSession: false,
- autoRefreshToken: false,
- detectSessionInUrl: false,
- },
-})
-```
diff --git a/apps/docs/components/MDX/kotlin_project_setup.mdx b/apps/docs/components/MDX/kotlin_project_setup.mdx
new file mode 100644
index 00000000000..fbf2e6ebd09
--- /dev/null
+++ b/apps/docs/components/MDX/kotlin_project_setup.mdx
@@ -0,0 +1,53 @@
+import ProductManagementSQLTemplate from './product_management_sql_template.mdx'
+import { Tabs } from 'ui'
+export const TabPanel = Tabs.Panel
+
+## Project setup
+
+Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase and then creating a "schema" inside the database.
+
+### Create a project
+
+1. [Create a new project](https://app.supabase.com) in the Supabase Dashboard.
+1. Enter your project details.
+1. Wait for the new database to launch.
+
+### Set up the database schema
+
+Now we are going to set up the database schema. You can just copy/paste the SQL from below and run it yourself.
+
+
+{/*
+
+1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
+2. Click **Product Management**.
+3. Click **Run**.
+
+ */}
+
+
+
+
+
+
+
+### Get the API Keys
+
+Now that you've created some database tables, you are ready to insert data using the auto-generated API.
+We just need to get the Project URL and `anon` key from the API settings.
+
+1. Go to the [API Settings](https://app.supabase.com/project/_/settings/api) page in the Dashboard.
+2. Find your Project `URL`, `anon`, and `service_role` keys on this page.
+
+### Set up Google Authentication
+
+From the [Google Console](https://console.developers.google.com/apis/library), create a new project and add OAuth2 credentials.
+
+
+
+In your [Supabase Auth settings](https://app.supabase.com/project/_/auth/providers) enable Google as a provider and set the required credentials as outlined in the [auth docs](/docs/guides/auth/social-login/auth-google).
diff --git a/apps/docs/components/MDX/product_management_sql_template.mdx b/apps/docs/components/MDX/product_management_sql_template.mdx
new file mode 100644
index 00000000000..e54e89e1ca4
--- /dev/null
+++ b/apps/docs/components/MDX/product_management_sql_template.mdx
@@ -0,0 +1,35 @@
+```sql
+-- Create a table for public profiles
+
+create table
+ public.products (
+ id uuid not null default gen_random_uuid (),
+ name text not null,
+ price real not null,
+ image text null,
+ constraint products_pkey primary key (id)
+ ) tablespace pg_default;
+
+-- Set up Storage!
+insert into storage.buckets (id, name)
+ values ('Product Image', 'Product Image');
+
+-- Set up access controls for storage.
+-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
+CREATE POLICY "Enable read access for all users" ON "storage"."objects"
+AS PERMISSIVE FOR SELECT
+TO public
+USING (true)
+
+CREATE POLICY "Enable insert for all users" ON "storage"."objects"
+AS PERMISSIVE FOR INSERT
+TO authenticated, anon
+WITH CHECK (true)
+
+CREATE POLICY "Enable update for all users" ON "storage"."objects"
+AS PERMISSIVE FOR UPDATE
+TO public
+USING (true)
+WITH CHECK (true)
+
+```
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index b6d0ec39ca3..8ff54ba58c6 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -60,6 +60,12 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
},
],
[
+ {
+ label: 'Local Dev / CLI',
+ icon: 'reference-cli',
+ href: '/guides/cli',
+ level: 'reference_javascript',
+ },
{
label: 'Platform',
icon: 'platform',
@@ -78,13 +84,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
href: '/guides/self-hosting',
level: 'self_hosting',
},
- {
- label: 'Integrations',
- icon: 'integrations',
- hasLightIcon: true,
- href: '/guides/integrations',
- level: 'integrations',
- },
],
[
{
@@ -131,7 +130,14 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
community: true,
},
{
- label: 'Tools reference',
+ label: 'Tools',
+ },
+ {
+ label: 'Integrations',
+ icon: 'integrations',
+ hasLightIcon: true,
+ href: 'https://supabase.com/partners/integrations',
+ level: 'integrations',
},
{
label: 'Management API',
@@ -139,12 +145,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
href: '/reference/api/introduction',
level: 'reference_javascript',
},
- {
- label: 'Supabase CLI',
- icon: 'reference-cli',
- href: '/guides/cli',
- level: 'reference_javascript',
- },
],
[
{
@@ -206,7 +206,6 @@ export const gettingstarted: NavMenuConstant = {
items: [
{ name: 'Features', url: '/guides/getting-started/features' },
{ name: 'Architecture', url: '/guides/getting-started/architecture' },
- { name: 'Local Development', url: '/guides/getting-started/local-development' },
{
name: 'Framework Quickstarts',
items: [
@@ -215,6 +214,7 @@ export const gettingstarted: NavMenuConstant = {
{ name: 'NuxtJS', url: '/guides/getting-started/quickstarts/nuxtjs' },
{ name: 'RedwoodJS', url: '/guides/getting-started/quickstarts/redwoodjs' },
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter' },
+ { name: 'Android Kotlin', url: '/guides/getting-started/quickstarts/kotlin' },
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit' },
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs' },
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue' },
@@ -277,7 +277,10 @@ export const gettingstarted: NavMenuConstant = {
name: 'Expo',
url: '/guides/getting-started/tutorials/with-expo',
},
-
+ {
+ name: 'Android Kotlin',
+ url: '/guides/getting-started/tutorials/with-kotlin',
+ },
{
name: 'Ionic React',
url: '/guides/getting-started/tutorials/with-ionic-react',
@@ -474,6 +477,7 @@ export const auth = {
url: undefined,
items: [
{ name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha' },
+ { name: 'Configuring Custom SMTP', url: '/guides/auth/auth-smtp' },
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data' },
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' },
{ name: 'Row Level Security', url: '/guides/auth/row-level-security' },
@@ -911,6 +915,7 @@ export const ai: NavMenuConstant = {
{ name: 'Vector columns', url: '/guides/ai/vector-columns' },
{ name: 'Engineering for scale', url: '/guides/ai/engineering-for-scale' },
{ name: 'Choosing Compute Add-on', url: '/guides/ai/choosing-compute-addon' },
+ { name: 'Going to Production', url: '/guides/ai/going-to-prod' },
],
},
{
@@ -970,14 +975,29 @@ export const ai: NavMenuConstant = {
export const supabase_cli: NavMenuConstant = {
icon: 'reference-cli',
- title: 'Supabase CLI',
+ title: 'Local Dev / CLI',
url: '/guides/cli',
items: [
- { name: 'Overview', url: '/guides/cli' },
- { name: 'Managing Environments', url: '/guides/cli/managing-environments' },
+ { name: 'Getting started', url: '/guides/cli' },
+ { name: 'Local Development', url: '/guides/cli/local-development' },
+ { name: 'Managing environments', url: '/guides/cli/managing-environments' },
{
- name: 'Using environment variables in config.toml',
- url: '/guides/cli/using-environment-variables-in-config',
+ name: 'Managing config and secrets',
+ url: '/guides/cli/managing-config',
+ },
+ {
+ name: 'Testing emails locally',
+ url: '/guides/cli/testing-emails',
+ },
+ {
+ name: 'GitHub Action',
+ url: undefined,
+ items: [
+ {
+ name: 'Generate types from your database',
+ url: '/guides/cli/github-action/generating-types',
+ },
+ ],
},
{
name: 'Reference',
@@ -1060,6 +1080,24 @@ export const platform: NavMenuConstant = {
{ name: 'Production Checklist', url: '/guides/platform/going-into-prod' },
],
},
+ {
+ name: 'Integrations',
+ url: undefined,
+ items: [
+ {
+ name: 'Integrations Marketplace',
+ url: '/guides/platform/marketplace',
+ },
+ {
+ name: 'Publish an OAuth App',
+ url: '/guides/platform/oauth-apps/publish-an-oauth-app',
+ },
+ {
+ name: 'Sign in with Supabase',
+ url: '/guides/platform/oauth-apps/authorize-an-oauth-app',
+ },
+ ],
+ },
{
name: 'Troubleshooting',
url: undefined,
@@ -1183,87 +1221,6 @@ export const migrate = {
],
}
-export const integrations: NavMenuConstant = {
- icon: 'integrations',
- title: 'Integrations',
- url: '/guides/integrations',
- items: [
- { name: 'Overview', url: '/guides/integrations/integrations' },
- {
- name: 'OAuth Apps (Beta)',
- url: undefined,
- items: [
- {
- name: 'Publish an OAuth App',
- url: '/guides/integrations/oauth-apps/publish-an-oauth-app',
- },
- {
- name: 'Authorize an OAuth App',
- url: '/guides/integrations/oauth-apps/authorize-an-oauth-app',
- },
- ],
- },
- {
- name: 'Auth',
- url: undefined,
- items: [
- {
- name: 'Auth0',
- url: '/guides/integrations/auth0',
- },
- { name: 'Authsignal', url: '/guides/integrations/authsignal' },
- { name: 'Clerk', url: '/guides/integrations/clerk' },
- { name: 'keyri', url: '/guides/integrations/keyri' },
- { name: 'Passage', url: '/guides/integrations/passage' },
- { name: 'Stytch', url: '/guides/integrations/stytch' },
- { name: 'SuperTokens', url: '/guides/integrations/supertokens' },
- ],
- },
- {
- name: 'Caching / Offline-first',
- url: undefined,
- items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale' }],
- },
- {
- name: 'Developer Tools',
- url: undefined,
- items: [
- { name: 'Cloudflare Workers', url: '/guides/integrations/cloudflare-workers' },
- { name: 'Estuary', url: '/guides/integrations/estuary' },
- { name: 'OpenAI', url: '/guides/ai/examples/openai' },
- { name: 'pgMustard', url: '/guides/integrations/pgmustard' },
- { name: 'Prisma', url: '/guides/integrations/prisma' },
- { name: 'Sequin', url: '/guides/integrations/sequin' },
- { name: 'Snaplet', url: '/guides/integrations/snaplet' },
- { name: 'Vercel', url: '/guides/integrations/vercel' },
- { name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis' },
- { name: 'WeWeb', url: '/guides/integrations/weweb' },
- { name: 'Zuplo', url: '/guides/integrations/zuplo' },
- ],
- },
- {
- name: 'Low-code',
- url: undefined,
- items: [
- { name: 'Appsmith', url: '/guides/integrations/appsmith' },
- { name: 'Bracket', url: '/guides/integrations/bracket' },
- { name: 'DhiWise', url: '/guides/integrations/dhiwise' },
- { name: 'Directus', url: '/guides/integrations/directus' },
- { name: 'Draftbit', url: '/guides/integrations/draftbit' },
- { name: 'FlutterFlow', url: '/guides/integrations/flutterflow' },
- { name: 'Forest Admin', url: '/guides/integrations/forestadmin' },
- { name: 'Plasmic', url: '/guides/integrations/plasmic' },
- { name: 'ILLA', url: '/guides/integrations/illa' },
- ],
- },
- {
- name: 'Messaging',
- url: undefined,
- items: [{ name: 'OneSignal', url: '/guides/integrations/onesignal' }],
- },
- ],
-}
-
export const reference = {
title: 'API Reference',
icon: 'reference',
diff --git a/apps/docs/components/index.tsx b/apps/docs/components/index.tsx
index 2e999d38a66..c837ee2c74b 100644
--- a/apps/docs/components/index.tsx
+++ b/apps/docs/components/index.tsx
@@ -2,7 +2,6 @@ import Link from 'next/link'
import { Alert, Button, CodeBlock, GlassPanel, markdownComponents, Tabs } from 'ui'
import StepHikeCompact from '~/components/StepHikeCompact'
// Common components
-import Admonition from './Admonition'
import ButtonCard from './ButtonCard'
import JwtGenerator from './JwtGenerator'
@@ -22,7 +21,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
import StorageManagement from './MDX/storage_management.mdx'
-import AuthHelpersFAQ from './MDX/auth_helpers_faq.mdx'
+import KotlinProjectSetup from './MDX/kotlin_project_setup.mdx'
import { CH } from '@code-hike/mdx/components'
import RefHeaderSection from './reference/RefHeaderSection'
@@ -32,6 +31,7 @@ import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlo
import Options from '~/components/Options'
import Param from '~/components/Params'
+import { Admonition } from 'ui'
import {
IconMenuJavascript,
IconMenuHome,
@@ -71,11 +71,11 @@ const components = {
QuickstartIntro,
DatabaseSetup,
ProjectSetup,
+ KotlinProjectSetup,
SocialProviderSetup,
SocialProviderSettingsSupabase,
StepHikeCompact,
StorageManagement,
- AuthHelpersFAQ,
Mermaid,
Extensions,
Alert: (props: any) => (
diff --git a/apps/docs/components/reference/OldVersionAlert.tsx b/apps/docs/components/reference/OldVersionAlert.tsx
index 55b94576d75..a9c04d535d8 100644
--- a/apps/docs/components/reference/OldVersionAlert.tsx
+++ b/apps/docs/components/reference/OldVersionAlert.tsx
@@ -1,7 +1,7 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
+import { Admonition } from 'ui'
import { useMenuActiveRefId } from '~/hooks/useMenuState'
-import Admonition from '../Admonition'
import { ICommonSection } from './Reference.types'
export interface OldVersionAlertProps {
diff --git a/apps/docs/docs/ref/kotlin/introduction.mdx b/apps/docs/docs/ref/kotlin/introduction.mdx
index 7f1f3784175..914310ba347 100644
--- a/apps/docs/docs/ref/kotlin/introduction.mdx
+++ b/apps/docs/docs/ref/kotlin/introduction.mdx
@@ -18,15 +18,17 @@ This reference documents every object and method available in Supabase's Kotlin
Supported Kotlin targets:
-| **Module** | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
-| -------------------------------------- | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
-| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **JS** _(Browser)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
-| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
+| ------------------------------------------------------------------ | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
+| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **JS** _(Browser, NodeJS)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **tvOS** _(tvosArm64, tvosX64, tvosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **watchOS** _(watchosArm64, watchosX64, watchosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
+| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
✅ = full support
diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx
index b24019de139..59bead2a64d 100644
--- a/apps/docs/layouts/SiteLayout.tsx
+++ b/apps/docs/layouts/SiteLayout.tsx
@@ -8,6 +8,7 @@ import Head from 'next/head'
import { PropsWithChildren, memo } from 'react'
import Footer from '~/components/Navigation/Footer'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
+import { Announcement, LW8CountdownBanner } from 'ui'
const levelsData = {
home: {
@@ -326,6 +327,9 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
Supabase Docs
+
+
+
diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx
index b637c118b20..1363448853c 100644
--- a/apps/docs/layouts/guides/index.tsx
+++ b/apps/docs/layouts/guides/index.tsx
@@ -1,17 +1,15 @@
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import Head from 'next/head'
-import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
-import { IconExternalLink } from 'ui'
+import { ExpandableVideo, IconExternalLink } from 'ui'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import useHash from '~/hooks/useHash'
import { LayoutMainContent } from '../DefaultLayout'
-import ExpandableVideo from 'ui/src/components/ExpandableVideo/ExpandableVideo'
interface Props {
meta: {
diff --git a/apps/docs/lib/mdx/plugins/remarkAdmonition.ts b/apps/docs/lib/mdx/plugins/remarkAdmonition.ts
index 343c58ad419..a9a212d2825 100644
--- a/apps/docs/lib/mdx/plugins/remarkAdmonition.ts
+++ b/apps/docs/lib/mdx/plugins/remarkAdmonition.ts
@@ -1,8 +1,8 @@
import { Content, Paragraph, Parent } from 'mdast'
import { MdxJsxFlowElement } from 'mdast-util-mdx'
+import { AdmonitionProps } from 'ui'
import { Node } from 'unist'
import { visit } from 'unist-util-visit'
-import { AdmonitionProps } from '~/components/Admonition'
/**
* Transforms an `mkdocs-material` Admonition to a Supabase Admonition.
diff --git a/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
index 1f34a54c413..80d80a6cc65 100644
--- a/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
+++ b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx
@@ -153,7 +153,7 @@ And after ChatGPT receives a response from the plugin it will answer your questi
## Resources
- ChatGPT Retrieval Plugin: [github.com/openai/chatgpt-retrieval-plugin](https://github.com/openai/chatgpt-retrieval-plugin)
-- ChatGTP Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
+- ChatGPT Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
export const Page = ({ children }) =>
export default Page
diff --git a/apps/docs/pages/guides/ai/examples/openai.mdx b/apps/docs/pages/guides/ai/examples/openai.mdx
index 7094a8fc936..d2462d371c9 100644
--- a/apps/docs/pages/guides/ai/examples/openai.mdx
+++ b/apps/docs/pages/guides/ai/examples/openai.mdx
@@ -5,7 +5,7 @@ export const meta = {
title: 'Generating OpenAI GPT3 completions',
description: 'Generate GPT text completions using OpenAI and Supabase Edge Functions.',
subtitle: 'Generate GPT text completions using OpenAI and Supabase Edge Functions.',
- video: 'https://www.youtube.com/v/29p8kIqyU_Y',
+ video: 'https://www.youtube-nocookie.com/v/29p8kIqyU_Y',
tocVideo: '29p8kIqyU_Y',
}
diff --git a/apps/docs/pages/guides/ai/going-to-prod.mdx b/apps/docs/pages/guides/ai/going-to-prod.mdx
new file mode 100644
index 00000000000..443e559d19a
--- /dev/null
+++ b/apps/docs/pages/guides/ai/going-to-prod.mdx
@@ -0,0 +1,100 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'ai-going-to-prod',
+ title: 'Going to Production',
+ description: 'Checklist for going to production with your AI application.',
+ subtitle: 'Going to production checklist for AI applications.',
+ sidebar_label: 'Going to Production',
+}
+
+This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal precision for your use case.
+
+See our [Engineering for Scale](/docs/guides/ai/engineering-for-scale) guide for more information about engineering at scale.
+
+## Do you need indexes?
+
+Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% precision and not being RAM bound.
+
+There are a couple of cases where you might not need indexes:
+
+- You have a small dataset and don't need to scale it.
+- You are not expecting high amounts of vector search queries per second.
+- You need to guarantee 100% precision.
+
+You don't have to create indexes in these cases and can use sequential scans instead. This type of workload will not be RAM bound and will not require any additional resources but will result in higher latencies and lower throughput. Extra CPU cores may help to improve queries per second, but it will not help to improve latency.
+
+On the other hand, if you need to scale your application, you will need to create indexes. This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower precision, since you are replacing exact (KNN) search with approximate (ANN) search.
+
+## Understanding `probes` and `lists`
+
+Indexes used for approximate vector similarity search in pgvector divides a dataset into partitions. The number of these partitions is defined by the `lists` constant. The `probes` controls how many lists are going to be searched during a query.
+
+The values of lists and probes directly affect precision and requests per second (RPS).
+
+- Higher `lists` means an index will be built slower, but you can achieve better RPS and precision.
+- Higher `probes` means that select queries will be slower, but you can achieve better precision.
+- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same precision.
+
+You can find more examples of how `lists` and `probes` constants affect precision and RPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
+
+
+
+
+
+
+## Performance Tips when using indexes
+
+First, a few generic tips which you can pick and choose from:
+
+1. The Supabase managed platform will automatically optimize Postgres configs for you based on your compute addon. But if you self-host, consider **adjusting your Postgres config** based on RAM & CPU cores. See [example optimizations](https://gist.github.com/egor-romanov/323e2847851bbd758081511785573c08) for more details.
+2. Prefer `inner-product` to `L2` or `Cosine` distances if your vectors are normalized (like `text-embedding-ada-002`). If embeddings are not normalized, `Cosine` distance should give the best results with an index.
+3. **Pre-warm your database.** Implement the warm-up technique before transitioning to production or running benchmarks.
+ - Execute 10,000 to 50,000 "warm-up" queries before each benchmark, matching the number of `probes` you are going to use in production. Additionally, you can execute about 1,000 queries with probes ranging from three to ten times the prod's probes. Both of these help to increase RAM utilization.
+4. **Establish your workload.** Increasing the lists constant for the pgvector index can accelerate your queries (at the expense of a slower build). For instance, for benchmarks with 1,000,000 embeddings, we employed a `lists` constant of 2000 (`number of vectors / 500`) as opposed to the suggested 1000 (`number of vectors / 1000`).
+5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the `probes` constant, balancing precision with requests per second (RPS).
+
+## Going into production
+
+1. Decide if you are going to use indexes or not. You can skip the rest of this guide if you do not use indexes.
+2. Over-provision RAM during preparation. You can scale down in step `5`, but it's better to start with a larger size to get the best results for RAM requirements. (We'd recommend at least 8XL if you're using Supabase.)
+3. Upload your data to the database. If you use the [`vecs`](/docs/guides/ai/python/api) library, it will automatically generate an index with default parameters.
+4. Run a benchmark using randomly generated queries and observe the results. Again, you can use the `vecs` library with the `ann-benchmarks` tool. Do it with probes set to 10 (default) and then with probes set to 100 or more, so RPS will be lower than 10.
+5. Monitor the RAM usage, and save it as a note for yourself. You would likely want to use a compute add-on in the future that has the same amount of RAM that was used at the moment (both actual RAM usage and RAM used for cache and buffers).
+6. Scale down your compute add-on to the one that would have the same amount of RAM used at the moment.
+7. Repeat step 3 to load the data into RAM. You should see RPS increase on subsequent runs, and stop when it no longer increases. Then repeat the benchmark with probes set to a higher value if you haven't already performed it for that compute add-on size.
+8. Run a benchmark using real queries and observe the results. You can use the `vecs` library for that as well with `ann-benchmarks` tool. Set probes to 10 (default) and then gradually increase/decrease probes until you see that both precision and RPS match your requirements.
+9. If you want higher RPS and you don't expect to have frequent inserts and reindexing, you can increase `lists` constantly. You have to rebuild the index with a higher lists value and repeat steps 6-7 to find the best combination of `lists` and `probes` constants to achieve the best RPS and precision values. Higher `lists` mean that index will build slower, but you can achieve better RPS and precision. Higher probes mean that select queries will be slower, but you can achieve better precision.
+
+## Useful links
+
+Don't forget to check out the general [Production Checklist](/docs/guides/platform/going-into-prod) to ensure your project is secure, performant, and will remain available for your users.
+
+You can look at our [Choosing Compute Add-on](/docs/guides/ai/choosing-compute-addon) guide to get a basic understanding of how much compute you might need for your workload.
+
+Or take a look at our [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blog post to see what pgvector is capable of and how the above technique can be used to achieve the best results.
+
+
+
+
+
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/auth/auth-captcha.mdx b/apps/docs/pages/guides/auth/auth-captcha.mdx
index 0834167875d..e36e96262bc 100644
--- a/apps/docs/pages/guides/auth/auth-captcha.mdx
+++ b/apps/docs/pages/guides/auth/auth-captcha.mdx
@@ -51,7 +51,7 @@ In the Settings page, look for the **Sitekey** section and copy the key.
## Enable Captcha protection for your Supabase project
-Navigate to the **[Authentication](https://supabase.com/dashboard/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.
+Navigate to the **[Auth](https://supabase.com/dashboard/project/_/settings/auth)** section of your Project Settings in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.

diff --git a/apps/docs/pages/guides/auth/auth-email-templates.mdx b/apps/docs/pages/guides/auth/auth-email-templates.mdx
index 8a3a0eedcc7..30dccd9321b 100644
--- a/apps/docs/pages/guides/auth/auth-email-templates.mdx
+++ b/apps/docs/pages/guides/auth/auth-email-templates.mdx
@@ -45,6 +45,32 @@ To guard against this:
The user should be brought to a page on your site where they can confirm the action by clicking a button.
The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL.
+### Email Tracking
+
+If you are using an external email provider that enables "email tracking", the links inside the Supabase email templates will be overwritten and won't perform as expected. We recommend disabling email tracking to ensure email links are not overwritten.
+
+### Redirecting the user to a server-side endpoint
+
+If you intend to use [Server-side rendering](/docs/guides/auth/server-side-rendering), you might want the email link to redirect the user to a server-side endpoint to check if they are authenticated before returning the page. However, the default email link will redirect the user after verification to the redirect URL with the session in the query fragments. Since the session is returned in the query fragments by default, you won't be able to access it on the server-side.
+
+You can customize the email link in the email template to redirect the user to a server-side endpoint successfully. For example:
+
+```html
+Accept the invite
+
+```
+When the user clicks on the link, the request will hit `https://api.example.com/v1/authenticate` and you can grab the `token_hash` and `type` query parameters from the URL. Then, you can call the [`verifyOtp`](/docs/reference/javascript/auth-verifyotp) method to get back an authenticated session before redirecting the user back to the client. Since the `verifyOtp` method makes a `POST` request to Supabase Auth to verify the user, the session will be returned in the response body, which can be read by the server. For example:
+
+```js
+const { token_hash, type } = Object.fromEntries(new URLSearchParams(window.location.search))
+const { data: { session }, error } = supabase.auth.verifyOtp({ token_hash, type })
+
+// subsequently redirect the user back to the client
+// ...
+
+```
+
export const Page = ({ children }) =>
export default Page
diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
index 8256063a9b8..ed2aea88f97 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx
@@ -92,7 +92,7 @@ Options are available via `queryParams`:
prompt: 'consent',
hd: 'domain.com',
}}
- onlyThirdPartyProviders={true}
+ onlyThirdPartyProviders
/>
```
diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
index 4e08a013707..c66d2e48e4c 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx
@@ -854,24 +854,6 @@ See [refreshing session example](/docs/guides/auth/auth-helpers/nextjs#managing-
- [Protected Routes](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/[id]/page.tsx)
- [Conditional Rendering in Client Components with SSR](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login-form.tsx)
-## Frequently asked questions
-
-
-
-### OAuth sign in isn't redirecting on the server side
-
-The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
-
-```ts
-import { NextResponse } from "next/server";
-...
-const { data } = await supabase.auth.signInWithOAuth({
- provider: 'github',
-})
-
-return NextResponse.redirect(data.url)
-```
-
## Migration Guide
### Migrating to v0.7.X
diff --git a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx
index 1026ca5e0cc..e1769bca84d 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/remix.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/remix.mdx
@@ -745,24 +745,6 @@ export default function Index() {
> Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/replication) on the table you are subscribing to.
-## Frequently asked questions
-
-
-
-### OAuth sign in isn't redirecting on the server side
-
-The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
-
-```ts
-import { redirect } from "@remix-run/node"; // or cloudflare/deno
-...
-const { data } = await supabase.auth.signInWithOAuth({
- provider: 'github',
-})
-
-return redirect(data.url)
-```
-
## Migration Guide
### Migrating to v0.2.0
diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
index a3582e0b8bc..10fdf26cd6e 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
@@ -749,24 +749,6 @@ export const actions = {
}
```
-## Frequently asked questions
-
-
-
-### OAuth sign in isn't redirecting on the server side
-
-The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
-
-```ts
-import { redirect } from '@sveltejs/kit';
-...
-const { data } = await supabase.auth.signInWithOAuth({
- provider: 'github',
-})
-
-throw redirect(303, data.url)
-```
-
## Migration Guide [#migration]
### Migrate to 0.10
diff --git a/apps/docs/pages/guides/auth/auth-mfa.mdx b/apps/docs/pages/guides/auth/auth-mfa.mdx
index 0591e3af52b..3365952bd57 100644
--- a/apps/docs/pages/guides/auth/auth-mfa.mdx
+++ b/apps/docs/pages/guides/auth/auth-mfa.mdx
@@ -687,17 +687,24 @@ json_query_path(auth.jwt(), '$.amr[0]')
authentication method in the JWT.
Once you have extracted the most recent entry in the array, you can compare the
-`method` and `timestamp` to enforce stricter rules.
+`method` and `timestamp` to enforce stricter rules. For instance, you can mandate that access will be only be granted on a table to users who have recently signed in with a password.
-Currently recognized methods are:
+Currently recognized authentication methods are:
+- `oauth` - any OAuth based sign in (social login).
- `password` - any password based sign in.
- `otp` - any one-time password based sign in (email code, SMS code, magic
link).
-- `oauth` - any OAuth based sign in (social login).
- `totp` - a TOTP additional factor.
+- `sso/saml` - any Single Sign On (SAML) method.
-This list will expand in the future.
+The following additional claims are available when using PKCE flow:
+- `invite` - any sign in via an invitation.
+- `magiclink` - any sign in via magic link. Excludes logins resulting from invocation of `signUp`.
+- `email/signup` - any login resulting from an email signup.
+- `email_change` - any login resulting from a change in email.
+
+More authentication methods will be added over time as we increase the number of authentication methods supported by Supabase.
export const Page = ({ children }) =>
diff --git a/apps/docs/pages/guides/auth/auth-password-reset.mdx b/apps/docs/pages/guides/auth/auth-password-reset.mdx
index 1b81df20c2a..2f76e690ead 100644
--- a/apps/docs/pages/guides/auth/auth-password-reset.mdx
+++ b/apps/docs/pages/guides/auth/auth-password-reset.mdx
@@ -14,12 +14,7 @@ export const meta = {
## Single Page Application (SPA)
### Sending password reset email
-Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail)
-to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to
-the update password page. This URL must be saved in your
-allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at
-[Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't
-redirect the user.
+Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user.
```ts
await supabase.auth.resetPasswordForEmail('hello@example.com', {
@@ -28,9 +23,7 @@ await supabase.auth.resetPasswordForEmail('hello@example.com', {
```
### Email link
-The email link you receive will work like a magic link. This way when you click the link you will be logged into
-the website. Since we passed a redirect URL to the [`.resetPasswordForEmail`](https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail)
-method the user should be sent to the update password page.
+The email link you receive will work like a magic link. This way when you click the link you will be logged into the website. Since we passed a redirect URL to the [`.resetPasswordForEmail`](https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail) method the user should be sent to the update password page.
### Update Password
To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and pass along the new password to this method.
@@ -42,12 +35,7 @@ await supabase.auth.updateUser({ password: new_password })
## Server-Side Rendering (SSR)
### Sending password reset email
-Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail)
-to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to
-the callback page along with a query parameter to the update password page. This URL must be saved in your
-allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at
-[Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't
-redirect the user.
+Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to the callback page along with a query parameter to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user.
```ts
await supabase.auth.resetPasswordForEmail('hello@example.com', {
@@ -66,6 +54,7 @@ The email link you receive will behave like a magic link. When the link is click
### Exchange authorization code
After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeCodeForSession` function.
+
```ts
// api/auth/callback.ts
@@ -84,6 +73,7 @@ The query parameter is always `code` for the authorization code returned from th
We will also need to check for the `next` query parameter to redirect the user to the update password page.
+
```ts
// api/auth/callback.ts
@@ -96,8 +86,7 @@ res.redirect(next)
```
### Update Password
-To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and
-pass along the new password to this method.
+To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and pass along the new password to this method.
```ts
await supabase.auth.updateUser({ password: new_password })
diff --git a/apps/docs/pages/guides/auth/auth-smtp.mdx b/apps/docs/pages/guides/auth/auth-smtp.mdx
new file mode 100644
index 00000000000..8f30266f81f
--- /dev/null
+++ b/apps/docs/pages/guides/auth/auth-smtp.mdx
@@ -0,0 +1,39 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'auth-smtp',
+ title: 'Configure a Custom SMTP',
+ description: 'Moving towards production: Configuring a custom SMTP provider',
+}
+
+# Auth SMTP
+
+At present, you can trial the Supabase platform by sending up to **4** emails per hour via the built-in service. The default email service as a whole is offered on a best effort basis: we will do our best to maintain it and will review usage of the service on a regular basis to see if the email service should be continued.
+
+As you progress toward production, you may find yourself wanting for a custom SMTP service in order to increase your limits. A custom SMTP server will allow you to set your own cap on the number of emails sent per hour.
+
+Beyond rate limits, an SMTP server might also help with:
+
+- Deliverability and Reputation Management
+- Scalability
+- Analytics and Tracking
+- Compliance and Anti Spam measures
+
+## How to Set up SMTP
+
+Head over to the [Auth Settings Page](https://supabase.com/dashboard/project/_/settings/auth) and hit "Enable Custom SMTP" under the SMTP Provider section.
+
+Fill in fields below with the relevant details obtained from your custom SMTP provider:
+
+
+
+### SMTP Providers
+
+You can use Supabase Auth with any major SMTP provider of your choosing. Some SMTP providers you could consider using are:
+
+- [Twilio SendGrid](https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api)
+- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html)
+- [Resend](https://resend.com/docs/dashboard/emails/introduction)
+
+export const Page = ({ children }) =>
+export default Page
diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
index d4bae16558c..60277debe66 100644
--- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx
+++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx
@@ -7,16 +7,42 @@ export const meta = {
video: 'https://www.youtube.com/v/akScoPO01bc',
}
-## Overview
-
In this guide we'll show you how to authenticate your users with SMS based One-Time Password (OTP) tokens.
There are two reasons to use Supabase SMS OTP tokens:
-- You want users to log in with mobile number + password, and the mobile number should be verified via SMS
-- You want users to log in with mobile number ONLY (i.e. passwordless login)
+- You want users to log in with a phone number and password, and verify the phone number on signup with SMS
+- You want users to log in with a phone number ONLY (i.e. passwordless login)
-We'll cover:
+What you'll need:
+
+- Twilio account ([sign up](https://www.twilio.com/try-twilio))
+- Supabase project (create one [here](https://supabase.com/dashboard))
+- Mobile phone capable of receiving SMS
+
+SMS Authentication can be done with either Twilio Verify or Twilio Programmable Messaging. [Twilio Verify](https://www.twilio.com/en-us/trusted-activation/verify) is a specialized OTP solution and is recommended for most developers that need over-the-phone authentication. Alternatively you can use [Twilio Programmable Messaging](https://www.twilio.com/docs/messaging) which offers generic SMS sending support.
+
+## Twilio Verify
+
+To set up Twilio Verify, you will need to:
+
+1. Create a new [verification service](https://support.twilio.com/hc/en-us/articles/360033309133-Getting-Started-with-Twilio-Verify-V2) in the Twilio dashboard.
+2. [Switch Phone Provider to Twilio Verify](https://supabase.com/dashboard/project/_/auth/providers)
+3. Configure the Twilio Verify Service ID field using the Verification Service ID obtained in 1.
+
+When using Twilio Verify, OTPs are generated by Twilio. This means that:
+
+- Unlike other providers, the OTP expiry duration and message content fields are not configurable via the Supabase dashboard. Please head to Twilio Verify to configure these settings.
+- The token remains the same during its validity period until the verification is successful. This means if your user make another request within that period, they will receive the same token.
+- Twilio Verify has a separate set of rate limits that apply. Visit Twilio's [Rate Limit and Timeouts page](https://www.twilio.com/docs/verify/api/rate-limits-and-timeouts) to find out more.
+
+
+ At this time, Twilio Verify is only supported on the `whatsapp` and `sms` channels.
+
+
+## Twilio (Programmable Messaging)
+
+In this section we'll cover:
- [Finding your Twilio credentials](#finding-your-twilio-credentials)
- [Using OTP with password based logins](#using-otp-with-password-based-logins)
@@ -349,6 +375,8 @@ let { data, error } = await supabase.auth.verifyOtp({
## Resources
+- [Reasons to use Twilio Verify](https://www.twilio.com/blog/9-reasons-to-use-the-verify-api)
+- [Twilio Verify Documentation](https://www.twilio.com/docs/verify)
- [Twilio Signup](https://www.twilio.com/try-twilio)
- [Supabase Dashboard](https://supabase.com/dashboard)
- [Supabase Row Level Security](/docs/guides/auth#row-level-security)
diff --git a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
index d0dd5855c73..f46284137b6 100644
--- a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
+++ b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx
@@ -33,7 +33,7 @@ Setting up Facebook logins for your application consists of 3 parts:
-## Set up FaceBook Login for your Facebook App
+## Set up Facebook Login for your Facebook App
From the `Add Products to your App` screen:
diff --git a/apps/docs/pages/guides/cli.mdx b/apps/docs/pages/guides/cli.mdx
index d2703e85b65..38506dd1df0 100644
--- a/apps/docs/pages/guides/cli.mdx
+++ b/apps/docs/pages/guides/cli.mdx
@@ -4,12 +4,15 @@ export const meta = {
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
+ subtitle:
+ 'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
}
-The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
-You can also use the CLI to manage your Supabase projects, handle database migrations and CI/CD workflows, and generate types directly from your database schema.
+You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` (to create a new local project) and then `supabase start`.
-## Installation
+The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, handle database migrations, and generate types directly from your database schema.
+
+## Installing the Supabase CLI
-## Updates
+## Updating the Supabase CLI
When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same methods.
@@ -121,12 +124,38 @@ npx supabase stop --no-backup
npx supabase start
```
+## Running Supabase locally
+
+Inside the folder where you want to create your project, run:
+
+```bash
+supabase init
+```
+
+This will create a new `supabase` folder. It's safe to commit this folder to your version control system.
+
+Now, to start the Supabase stack, run:
+
+```bash
+supabase start
+```
+
+This takes time on your first run because the CLI needs to download the local Docker images. The CLI includes the entire Supabase toolset, and a few additional images that are useful for local development (like a local SMTP server and a database diff tool).
+
+The local development environment includes Supabase Studio, a graphical interface for working with your database, running by default on [localhost:54323](http://localhost:54323).
+
+
+
+When you are finished working on your Supabase project, you can stop the stack with:
+
+```bash
+supabase stop
+```
+
## See also
- [Supabase CLI Reference](/docs/reference/cli/introduction)
- [Supabase CLI Configuration](/docs/reference/cli/config)
-- [Local Development](/docs/guides/getting-started/local-development)
-- [Managing Environments](/docs/guides/cli/managing-environments)
export const Page = ({ children }) =>
diff --git a/apps/docs/pages/guides/cli/github-action/[slug].tsx b/apps/docs/pages/guides/cli/github-action/[slug].tsx
new file mode 100644
index 00000000000..181f3acb409
--- /dev/null
+++ b/apps/docs/pages/guides/cli/github-action/[slug].tsx
@@ -0,0 +1,138 @@
+import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
+import { GetStaticPaths, GetStaticProps } from 'next'
+import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
+import { serialize } from 'next-mdx-remote/serialize'
+import { relative } from 'path'
+import rehypeSlug from 'rehype-slug'
+import remarkGfm from 'remark-gfm'
+import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
+import components from '~/components'
+import Layout from '~/layouts/DefaultGuideLayout'
+import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
+import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
+import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
+
+// We fetch these docs at build time from an external repo
+const org = 'supabase'
+const repo = 'setup-cli'
+const branch = 'gh-pages'
+const docsDir = 'docs'
+const externalSite = 'https://supabase.github.io/setup-cli'
+
+// Each external docs page is mapped to a local page
+const pageMap = [
+ {
+ slug: 'generating-types',
+ meta: {
+ title: 'Generate types from your database',
+ description: 'End-to-end type safety across client, server, and database.',
+ subtitle: 'End-to-end type safety across client, server, and database.',
+ tocVideo: 'VSNgAIObBdw',
+ },
+ remoteFile: 'generating-types.md',
+ },
+]
+
+interface ActionDocsProps {
+ source: MDXRemoteSerializeResult
+ meta: {
+ title: string
+ description?: string
+ }
+}
+
+export default function ActionDocs({ source, meta }: ActionDocsProps) {
+ return (
+
+
+
+ )
+}
+
+/**
+ * Fetch markdown from external repo and transform links
+ */
+export const getStaticProps: GetStaticProps = async ({ params }) => {
+ const page = pageMap.find(({ slug }) => slug === params.slug)
+
+ if (!page) {
+ throw new Error(`No page mapping found for slug '${params.slug}'`)
+ }
+
+ const { remoteFile, meta } = page
+
+ const response = await fetch(
+ `https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
+ )
+
+ const source = await response.text()
+
+ const urlTransform: UrlTransformFunction = (url) => {
+ try {
+ const externalSiteUrl = new URL(externalSite)
+
+ const placeholderHostname = 'placeholder'
+ const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
+
+ // Don't modify a url with a FQDN or a url that's only a hash
+ if (hostname !== placeholderHostname || pathname === '/') {
+ return url
+ }
+
+ const relativePage = (
+ pathname.endsWith('.md')
+ ? pathname.replace(/\.md$/, '')
+ : relative(externalSiteUrl.pathname, pathname)
+ ).replace(/^\//, '')
+
+ const page = pageMap.find(({ remoteFile }) => `${relativePage}.md` === remoteFile)
+
+ // If we have a mapping for this page, use the mapped path
+ if (page) {
+ return page.slug + hash
+ }
+
+ // If we don't have this page in our docs, link to original docs
+ return `${externalSite}/${relativePage}${hash}`
+ } catch (err) {
+ console.error('Error transforming markdown URL', err)
+ return url
+ }
+ }
+
+ const codeHikeOptions: CodeHikeConfig = {
+ theme: codeHikeTheme,
+ lineNumbers: true,
+ showCopyButton: true,
+ skipLanguages: [],
+ autoImport: false,
+ }
+
+ const mdxSource = await serialize(source, {
+ scope: {
+ chCodeConfig: codeHikeOptions,
+ },
+ mdxOptions: {
+ remarkPlugins: [
+ remarkGfm,
+ remarkMkDocsAdmonition,
+ [removeTitle, meta.title],
+ [remarkCodeHike, codeHikeOptions],
+ ],
+ rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
+ },
+ })
+
+ return { props: { source: mdxSource, meta } }
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return {
+ paths: pageMap.map(({ slug }) => ({
+ params: {
+ slug,
+ },
+ })),
+ fallback: false,
+ }
+}
diff --git a/apps/docs/pages/guides/getting-started/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx
similarity index 93%
rename from apps/docs/pages/guides/getting-started/local-development.mdx
rename to apps/docs/pages/guides/cli/local-development.mdx
index c6516e0caab..1682de48d8c 100644
--- a/apps/docs/pages/guides/getting-started/local-development.mdx
+++ b/apps/docs/pages/guides/cli/local-development.mdx
@@ -4,7 +4,9 @@ export const meta = {
id: 'local-development',
title: 'Local Development',
description: 'How to use Supabase on your local development machine.',
- video: 'https://www.youtube.com/v/vyHyYpvjaks',
+ subtitle: 'How to use Supabase on your local development machine.',
+ video: 'https://www.youtube-nocookie.com/v/vyHyYpvjaks',
+ tocVideo: 'vyHyYpvjaks',
}
Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running quickly, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/).
@@ -25,23 +27,6 @@ The Dashboard provides a wide range of features for setting up your project: cre
5. **Work offline**: Need to work from a train? A plane? An automobile? No problem. Developing your project locally allows you to work offline.
-
-
-
-
-## Prerequisites
-
-Make sure you have these installed on your local machine:
-
-- [Docker Desktop](https://docs.docker.com/desktop/)
-- [Supabase CLI](/docs/guides/cli)
-- [Git](https://github.com/git-guides/install-git)
-
## Log in to the Supabase CLI
```bash
@@ -415,24 +400,24 @@ supabase functions deploy
To use Auth locally, update your project's `supabase/config.toml` file that gets created after running `supabase init`. Add any providers you want, and set enabled to `true`.
-```bash config.toml
+```bash supabase/config.toml
[auth.external.github]
enabled = true
-client_id = "env($SUPABASE_AUTH_GITHUB_CLIENT_ID)"
-secret = "env($SUPABASE_AUTH_GITHUB_SECRET)"
+client_id = "env(SUPABASE_AUTH_GITHUB_CLIENT_ID)"
+secret = "env(SUPABASE_AUTH_GITHUB_SECRET)"
redirect_uri = "http://localhost:54321/auth/v1/callback"
```
-As a best practice, any secret values should be loaded from environment variables. You can add them to `supabase/.env` for the CLI to automatically substitute them.
+As a best practice, any secret values should be loaded from environment variables. You can add them to `.env` file in your project's root directory for the CLI to automatically substitute them.
-```bash supabase/.env
+```bash .env
SUPABASE_AUTH_GITHUB_CLIENT_ID="redacted"
SUPABASE_AUTH_GITHUB_SECRET="redacted"
```
For these changes to take effect, you need to run `supabase stop` and `supabase start` again.
-If you have additional RLS policies defined on your `auth` schema, you can pull them as a migration file locally.
+If you have additional triggers or RLS policies defined on your `auth` schema, you can pull them as a migration file locally.
```bash
supabase db remote commit --schema auth
@@ -474,7 +459,7 @@ This will switch the logging drivers and will direct logs to the Analytics serve
## Limitations and considerations
-The local development environment is not as feature-complete as the Supabase Platform. We're working towards parity between the hosted platform and the local environment. Here are some of the differences:
+The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences:
- You cannot update your project settings in the Dashboard. This must be done using the local config file.
- The CLI version determines the local version of Studio used, so make sure you keep your local [Supabase CLI up to date](https://github.com/supabase/cli#getting-started). We're constantly adding new features and bug fixes.
diff --git a/apps/docs/pages/guides/cli/managing-config.mdx b/apps/docs/pages/guides/cli/managing-config.mdx
new file mode 100644
index 00000000000..1f93c9e52cf
--- /dev/null
+++ b/apps/docs/pages/guides/cli/managing-config.mdx
@@ -0,0 +1,50 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'managing-config',
+ title: 'Managing config and secrets',
+ description: 'Managing local configuration using config.toml.',
+}
+
+The Supabase CLI uses a `config.toml` file to manage local configuration. This file is located in the `supabase` directory of your project.
+
+## Config reference
+
+The `config.toml` file is automatically created when you run `supabase start`.
+
+There are a wide variety of options available, which can be found in the [CLI Config Reference](/docs/reference/cli/config).
+
+For example, to enable the "Apple" OAuth provider for local development, you can append the following information to `config.toml`:
+
+```toml
+[auth.external.apple]
+enabled = false
+client_id = ""
+secret = ""
+redirect_uri = "" # Overrides the default auth redirectUrl.
+```
+
+## Using secrets inside config.toml
+
+You can reference environment variables within the `config.toml` file using the `env()` function. This will detect any values stored in an `.env` file at the root of your project directory. This is particularly useful for storing sensitive information like API keys, and any other values that you don't want to check into version control.
+
+For example, if your `.env` contained the following values:
+
+```bash
+GITHUB_CLIENT_ID=""
+GITHUB_SECRET=""
+```
+
+Then you would reference them inside of our `config.toml` like this:
+
+```toml
+[auth.external.github]
+enabled = true
+client_id = "env(GITHUB_CLIENT_ID)"
+secret = "env(GITHUB_SECRET)"
+redirect_uri = "" # Overrides the default auth redirectUrl.
+```
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/cli/managing-environments.mdx b/apps/docs/pages/guides/cli/managing-environments.mdx
index 3807044ad65..b20a4c5c5f1 100644
--- a/apps/docs/pages/guides/cli/managing-environments.mdx
+++ b/apps/docs/pages/guides/cli/managing-environments.mdx
@@ -3,37 +3,15 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'managing-environments',
title: 'Managing Environments',
- description: 'How to deploy Supabase schema changes with a CI / CD pipeline.',
- video: 'https://www.youtube.com/v/rOLyOsBR1Uc',
+ description: 'Manage multiple environments using Database Migrations and GitHub Actions.',
+ subtitle: 'Manage multiple environments using Database Migrations and GitHub Actions.',
+ video: 'https://www.youtube-nocookie.com/v/rOLyOsBR1Uc',
+ tocVideo: 'rOLyOsBR1Uc',
}
-## Overview
+This guide shows you how to set up your local Supabase development environment that integrates with GitHub Actions to automatically test and release schema changes to staging and production Supabase projects.
-The Supabase CLI provides the tools you need to manage multiple environments.
-This guide shows you how to set up your local Supabase development environment that integrates with GitHub Actions to automatically
-test and release schema changes to staging and production Supabase projects.
-
-## Prerequisites
-
-Make sure you have these installed on your local machine:
-
-- [Docker Desktop](https://docs.docker.com/desktop/)
-- [Supabase CLI](/docs/guides/cli)
-- [Git](https://github.com/git-guides/install-git)
-
-To get started:
-
-- Create a [Supabase project](https://supabase.com/dashboard) or use an existing one
-- Initialize a local Git repository
-
-
-
-
+
## Set up a local environment
diff --git a/apps/docs/pages/guides/cli/testing-emails.mdx b/apps/docs/pages/guides/cli/testing-emails.mdx
new file mode 100644
index 00000000000..f9e3cce6706
--- /dev/null
+++ b/apps/docs/pages/guides/cli/testing-emails.mdx
@@ -0,0 +1,24 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ id: 'testing-emails-locally',
+ title: 'Testing emails locally',
+ description: 'Testing emails for Supabase Auth on your local machine.',
+ subtitle: 'Testing emails for Supabase Auth on your local machine.',
+}
+
+The Supabase CLI uses [Inbucket](https://github.com/inbucket/inbucket) to capture emails sent from your local machine. This is useful for testing emails sent from Supabase Auth.
+
+## Accessing Inbucket
+
+By default, Inbucket is available at [localhost:54324](http://localhost:54324) when you run `supabase start`. Simply open this URL in your browser to view the emails.
+
+## Going into Production
+
+The "default" email provided by Supabase is only for development purposes. It is [heavily restricted](/docs/guides/platform/going-into-prod#auth-rate-limits) to ensure that it is not used for spam.
+
+Before you go into production, you must configure your own email provider. This is as simple as enabling a new SMTP credentials in your [project settings](https://supabase.com/dashboard/project/_/settings/auth).
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/cli/using-environment-variables-in-config.mdx b/apps/docs/pages/guides/cli/using-environment-variables-in-config.mdx
deleted file mode 100644
index b50724d29cb..00000000000
--- a/apps/docs/pages/guides/cli/using-environment-variables-in-config.mdx
+++ /dev/null
@@ -1,35 +0,0 @@
-import Layout from '~/layouts/DefaultGuideLayout'
-
-export const meta = {
- id: 'using-environment-variables-in-config',
- title: 'Using environment variables in config.toml',
- description: 'How to use environment variables in config.toml with the Supabase CLI.',
-}
-
-The Supabase CLI is capable of utilizing environment variables stored in our project's root directory's `.env` file within the `config.toml` file.
-
-We can reference the environment variable by using the `env()` function.
-
-Inside of our `.env` file we add the environment variable as we normally would
-
-```bash
-GITHUB_CLIENT_ID=""
-GITHUB_SECRET=""
-```
-
-And then reference them inside of our `config.toml`
-
-```toml
-[auth.external.github]
-enabled = true
-client_id = "env(GITHUB_CLIENT_ID)"
-secret = "env(GITHUB_SECRET)"
-# Overrides the default auth redirectUrl.
-redirect_uri = ""
-```
-
-These same environment variables will be referenced by the `supabase start` command from the Supabase CLI.
-
-export const Page = ({ children }) =>
-
-export default Page
diff --git a/apps/docs/pages/guides/database/connecting-to-postgres.mdx b/apps/docs/pages/guides/database/connecting-to-postgres.mdx
index c49cc1dbe27..3b6ba626626 100644
--- a/apps/docs/pages/guides/database/connecting-to-postgres.mdx
+++ b/apps/docs/pages/guides/database/connecting-to-postgres.mdx
@@ -38,7 +38,7 @@ Every Supabase project provides a full Postgres database. You can connect to the
## Connection Pooler
-Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard:
+Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/partners/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard:
1. Go to the `Settings` section.
2. Click `Database`.
@@ -267,7 +267,7 @@ psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.
- Install Drizzle and releated dependencies.
+ Install Postgres.js and releated dependencies.
@@ -275,7 +275,6 @@ psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.
```shell
npm i postgres
- npm i -D drizzle-kit
```
diff --git a/apps/docs/pages/guides/database/tables.mdx b/apps/docs/pages/guides/database/tables.mdx
index 730b3fcd977..223874fbd4f 100644
--- a/apps/docs/pages/guides/database/tables.mdx
+++ b/apps/docs/pages/guides/database/tables.mdx
@@ -425,7 +425,7 @@ Views provide the several benefits:
#### Simplicity
-As a query becomes complex it becomes a hassle to call it. Especially when we run it at regularly. In the example above, instead of repeatedly running:
+As a query becomes more complex, it can be a hassle to call it over and over - especially when we run it regularly. In the example above, instead of repeatedly running:
```sql
select
diff --git a/apps/docs/pages/guides/functions/troubleshooting.mdx b/apps/docs/pages/guides/functions/troubleshooting.mdx
index 5bdaad00dd9..8e8449b1db3 100644
--- a/apps/docs/pages/guides/functions/troubleshooting.mdx
+++ b/apps/docs/pages/guides/functions/troubleshooting.mdx
@@ -34,6 +34,31 @@ There are two debugging tools available: Invocations and Logs. Invocations shows
- Search the [Edge Runtime](https://github.com/supabase/edge-runtime) and [CLI](https://github.com/supabase/cli) repos for the error message, to see if it has been reported before.
- If the output from the commands above does not help you to resolve the issue, please open a support ticket via the Supabase Dashboard (by clicking the "Help" button at the top right) and include all output and details about your commands.
+# Advanced Techniques
+
+## Checking Function Boot Time
+
+Check the logs for the function. In the logs, look for a "Booted" event and note the reported boot time. If available, click on the event to access more details, including the regions from where the function was served. Investigate if the boot time is excessively high (`higher than 1 second`) and note any patterns or regions where it occurs.
+
+## Finding Bundle Size
+
+To find the bundle size of a function, run the following command locally:
+
+```bash
+deno info /path/to/function/index.ts
+```
+
+Look for the "size" field in the output which represents an approximated the bundle size of the function.You can find the accurate bundle size when you deploy your function via Supabase CLI. If the function is part of a larger application, consider examining the bundle size of the specific function independently.
+
+## Analyze Dependencies
+
+Review the dependencies listed in the output of the `deno info` command. Pay attention to any significantly large dependencies, as they can contribute to increased bundle size and potential boot time issues.
+Examine if there are any unnecessary or redundant dependencies that can be removed. Check for outdated dependencies and recommend updating to the latest versions if applicable. When running deno info make sure to provide the correct path of the import map if you use one.
+
+```bash
+deno info --import-map=/path/to/import_map.json /path/to/function/index.ts
+```
+
export const Page = ({ children }) =>
export default Page
diff --git a/apps/docs/pages/guides/getting-started.mdx b/apps/docs/pages/guides/getting-started.mdx
index 16766d4017f..0b5f90df3af 100644
--- a/apps/docs/pages/guides/getting-started.mdx
+++ b/apps/docs/pages/guides/getting-started.mdx
@@ -157,6 +157,13 @@ export const quickstarts = [
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.',
icon: '/docs/img/icons/flutter-icon',
},
+ {
+ title: 'Android Kotlin',
+ href: '/guides/getting-started/quickstarts/kotlin',
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.',
+ icon: '/docs/img/icons/kotlin-icon',
+ },
{
title: 'SvelteKit',
href: '/guides/getting-started/quickstarts/sveltekit',
@@ -268,6 +275,13 @@ export const mobile = [
'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/expo-icon',
},
+ {
+ title: 'Android Kotlin',
+ href: '/guides/getting-started/tutorials/with-kotlin',
+ description:
+ 'Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality.',
+ icon: '/docs/img/icons/kotlin-icon',
+ },
{
title: 'Ionic React',
href: '/guides/getting-started/tutorials/with-ionic-react',
diff --git a/apps/docs/pages/guides/getting-started/features.mdx b/apps/docs/pages/guides/getting-started/features.mdx
index 1d9513517e1..9b615c93353 100755
--- a/apps/docs/pages/guides/getting-started/features.mdx
+++ b/apps/docs/pages/guides/getting-started/features.mdx
@@ -153,36 +153,36 @@ Manage your projects programmatically. [Docs](/docs/reference/api).
Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development.
-| Product | Feature | Stage | Available on self-hosted |
-| -------------------------- | ---------------------- | ------- | ------------------------------------------- |
-| Database | Postgres | `GA` | ✅ |
-| Database | Triggers | `GA` | ✅ |
-| Database | Functions | `GA` | ✅ |
-| Database | Extensions | `GA` | ✅ |
-| Database | Full Text Search | `GA` | ✅ |
-| Database | Webhooks | `GA` | ✅ |
-| Database | Point-in-Time Recovery | `alpha` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
-| Database | Vault | `alpha` | ✅ |
-| Studio | | `GA` | ✅ |
-| Realtime | Postgres Changes | `GA` | ✅ |
-| Realtime | Broadcast | `beta` | ✅ |
-| Realtime | Presence | `beta` | ✅ |
-| Storage | | `GA` | ✅ |
-| Storage | S3 Backend | `GA` | ✅ |
-| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
-| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) |
-| Storage | Image Transformations | `GA` | ✅ |
-| Storage | Resumable Uploads | `beta` | ✅ |
-| Edge Functions | | `beta` | 🚧 [Deno Deploy](https://deno.com/deploy) |
-| Auth | OAuth Providers | `beta` | ✅ |
-| Auth | Passwordless | `beta` | ✅ |
-| Auth | Next.js Auth Helpers | `alpha` | ✅ |
-| Auth | SvelteKit Auth Helpers | `alpha` | ✅ |
-| Auth | Remix Auth Helpers | `alpha` | ✅ |
-| Management API | | `beta` | N/A |
-| CLI | | `beta` | N/A |
-| Client Library: JavaScript | | `GA` | N/A |
-| Client Library: Dart | | `beta` | N/A |
+| Product | Feature | Stage | Available on self-hosted |
+| -------------------------- | ---------------------- | ------ | ------------------------------------------- |
+| Database | Postgres | `GA` | ✅ |
+| Database | Triggers | `GA` | ✅ |
+| Database | Functions | `GA` | ✅ |
+| Database | Extensions | `GA` | ✅ |
+| Database | Full Text Search | `GA` | ✅ |
+| Database | Webhooks | `GA` | ✅ |
+| Database | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
+| Database | Vault | `beta` | ✅ |
+| Studio | | `GA` | ✅ |
+| Realtime | Postgres Changes | `GA` | ✅ |
+| Realtime | Broadcast | `beta` | ✅ |
+| Realtime | Presence | `beta` | ✅ |
+| Storage | | `GA` | ✅ |
+| Storage | S3 Backend | `GA` | ✅ |
+| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
+| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) |
+| Storage | Image Transformations | `GA` | ✅ |
+| Storage | Resumable Uploads | `beta` | ✅ |
+| Edge Functions | | `beta` | ✅ |
+| Auth | OAuth Providers | `GA` | ✅ |
+| Auth | Passwordless | `GA` | ✅ |
+| Auth | Next.js Auth Helpers | `beta` | ✅ |
+| Auth | SvelteKit Auth Helpers | `beta` | ✅ |
+| Auth | Remix Auth Helpers | `beta` | ✅ |
+| CLI | | `beta` | ✅ Works with self-hosted |
+| Management API | | `beta` | N/A |
+| Client Library: JavaScript | | `GA` | N/A |
+| Client Library: Dart | | `beta` | N/A |
- ✅ = Fully Available
- 🚧 = Available, but requires external tools or configuration
diff --git a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx
new file mode 100644
index 00000000000..91f5a535fa5
--- /dev/null
+++ b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx
@@ -0,0 +1,305 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+import StepHikeCompact from '~/components/StepHikeCompact'
+
+export const meta = {
+ title: 'Use Supabase with Android Kotlin',
+ subtitle:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.',
+ breadcrumb: 'Framework Quickstarts',
+}
+
+
+
+
+
+
+ [Create a new project](https://app.supabase.com) in the Supabase Dashboard.
+
+ After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create all tables.
+
+
+
+
+
+ ```sql SQL_EDITOR
+ -- Create products table
+ create table
+ public.products (
+ _id bigint generated by default as identity not null,
+ productid text not null,
+ name text null,
+ description text null,
+ price real null,
+ image text null,
+ category text null,
+ nutrition text null,
+ constraint products_pkey primary key (productid),
+ ) tablespace pg_default;
+
+ ````
+
+
+
+
+
+
+
+
+ Open Android Studio > New > New Android Project.
+
+
+
+
+
+
+
+
+ Import Supabase and all required dependencies. Replace the version placeholders `$supabase_version` and `$ktor_version` with the respective latest versions.
+
+
+
+
+ ```kotlin
+ implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
+ implementation "io.ktor:ktor-client-android:$ktor_version"
+ implementation "io.ktor:ktor-client-core:$ktor_version"
+ implementation "io.ktor:ktor-utils:$ktor_version"
+ ```
+
+
+
+
+
+
+
+
+ Open the `build.gradle` (app), add the serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same as the Kotlin version in your app.
+
+
+
+ ```kotlin
+ plugins {
+ ...
+ id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version'
+ ...
+ }
+ ```
+
+
+
+
+
+
+
+
+ You can create a Supabase client whenever you need to perform an API call. That being said, it is recommended to use a dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android#kts).
+
+
+
+
+ ```kotlin
+ val client = createSupabaseClient(
+ supabaseUrl = "https://xyzcompany.supabase.co",
+ supabaseKey = "public-anon-key"
+ ) {
+ install(GoTrue)
+ install(Postgrest)
+ install(Storage)
+ }
+ ```
+
+
+
+
+
+
+
+
+
+
+
+ ```kotlin
+ @Serializable
+ data class ProductDto(
+ @SerialName("productid")
+ val productId: String,
+ @SerialName("name")
+ val name: String,
+ @SerialName("description")
+ val description: String,
+ @SerialName("price")
+ val price: Double,
+ @SerialName("image")
+ val image: String,
+ @SerialName("category")
+ val category: String,
+ @SerialName("nutrition")
+ val nutrition: String,
+ @SerialName("_id")
+ val _id: Int,
+ )
+ ```
+
+
+
+
+
+
+
+
+ This kind of object will be consumed by the view.
+
+
+
+ ```kotlin
+ data class Product(
+ val productId: String,
+ val name: String,
+ val description: String,
+ val price: Double,
+ val image: String,
+ val category: String,
+ val nutrition: String,
+ val _id: Int,
+ )
+ ```
+
+
+
+
+
+
+
+
+ Create a repository to interact with the data source.
+
+
+
+ ```kotlin
+ interface ProductRepository {
+ fun getProducts(): List
+ }
+
+ class ProductRepositoryImpl @Inject constructor(
+ private val postgrest: Postgrest,
+ ) : ProductRepository {
+ override suspend fun getProducts(): List {
+ val result = client.postgrest["products"]
+ .select().decodeList()
+ // Handle result data for next step
+ return result
+ }
+ }
+ ```
+
+
+
+
+
+
+ Use [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) for dependency injection.
+
+
+ ```kotlin
+ InstallIn(SingletonComponent::class)
+ @Module
+ abstract class RepositoryModule {
+ @Binds
+ abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository
+ }
+ ```
+
+
+
+
+
+
+ Add the `@Inject` annotation to use the repository in a ViewModel.
+
+
+ ```kotlin
+ class ProductListViewModel @Inject constructor(
+ private val productRepository: ProductRepository
+ ) : ViewModel() {
+
+ private val _productList = MutableStateFlow?>(listOf())
+ val productList: Flow?> = _productList
+
+ init {
+ getProducts()
+ }
+
+ fun getProducts() {
+ viewModelScope.launch {
+ val products = productRepository.getProducts()
+ _productList.emit(products?.map { it -> it.asDomainModel() })
+ }
+ }
+
+ private fun ProductDto.asDomainModel(): Product {
+ return Product(
+ productId = this.productId,
+ name = this.name,
+ price = this.price,
+ image = this.image,
+ description = this.description,
+ category = this.category,
+ nutrition = this.nutrition,
+ _id = this._id
+ )
+ }
+ ```
+
+
+
+
+
+
+
+
+ ```kotlin
+ @Composable
+ fun ProductListScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: ProductListViewModel = hiltViewModel(),
+ ) {
+ val productList = viewModel.productList.collectAsState(initial = listOf()).value
+ if (!productList.isNullOrEmpty()) {
+ LazyColumn(
+ modifier = modifier.padding(24.dp),
+ contentPadding = PaddingValues(5.dp)
+ ) {
+ items(productList) { item ->
+ ProductListItem(
+ product = item,
+ modifier = modifier,
+ onClick = {
+ navController.navigate(
+ ProductDetailsDestination.createRouteWithParam(
+ item.id
+ )
+ )
+ },
+ )
+ }
+ }
+ }
+ }
+ ```
+
+
+
+
+
+
+
+
+
+
+
+
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx
new file mode 100644
index 00000000000..8f295c4233e
--- /dev/null
+++ b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx
@@ -0,0 +1,1267 @@
+import Layout from '~/layouts/DefaultGuideLayout'
+
+export const meta = {
+ title: 'Build a Product Management Android App with Jetpack Compose',
+ description: 'Learn how to use Supabase in your Android Kotlin App.',
+}
+
+This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication using:
+
+- [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information.
+- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up a password).
+- [Supabase Storage](/docs/guides/storage) - users can upload a profile photo.
+
+
+
+
+ If you get stuck while working through this guide, refer to the [full example on
+ GitHub](https://github.com/hieuwu/product-sample-supabase-kt).
+
+
+
+
+## Building the app
+
+### Create new Android project
+
+Open Android Studio > New Project > Base Activity (Jetpack Compose).
+
+
+
+### Set up API key and secret securely
+
+#### Create local environment secret
+
+Create or edit the `local.properties` file at the root (same level as `build.gradle`) of your project.
+
+> **Note**: Do not commit this file to your source control, e.g. by adding it to your `.gitignore` file!
+
+```kotlin
+SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
+SUPABASE_URL=YOUR_SUPABASE_URL
+```
+
+#### Read and set value to BuildConfig
+
+In your `build.gradle` (app) file, create a `Properties` object and read the values from your `local.properties` file by calling the `buildConfigField` method:
+
+```kotlin
+defaultConfig {
+ applicationId "com.example.manageproducts"
+ minSdkVersion 22
+ targetSdkVersion 33
+ versionCode 5
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ // Set value part
+ Properties properties = new Properties()
+ properties.load(project.rootProject.file("local.properties").newDataInputStream())
+ buildConfigField("String", "SUPABASE_ANON_KEY", "\"${properties.getProperty("SUPABASE_ANON_KEY")}\"")
+ buildConfigField("String", "SECRET", "\"${properties.getProperty("SECRET")}\"")
+ buildConfigField("String", "SUPABASE_URL", "\"${properties.getProperty("SUPABASE_URL")}\"")
+}
+```
+
+#### Use value from `BuildConfig`
+
+Read the value from `BuildConfig`:
+
+```kotlin
+val url = BuildConfig.SUPABASE_URL
+val apiKey = BuildConfig.SUPABASE_ANON_KEY
+```
+
+### Set up Supabase dependencies
+
+
+
+In the `build.gradle` (app) file, add these dependencies then press "Sync now". Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with their respective latest versions.
+
+```kotlin
+implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
+implementation "io.github.jan-tennert.supabase:storage-kt:$supabase_version"
+implementation "io.github.jan-tennert.supabase:gotrue-kt:$supabase_version"
+implementation "io.ktor:ktor-client-android:$ktor_version"
+implementation "io.ktor:ktor-client-core:$ktor_version"
+implementation "io.ktor:ktor-utils:$ktor_version"
+```
+
+Also in the `build.gradle` (app) file, add the plugin for serialization. The version of this plugin should be the same as your Kotlin version.
+
+```kotlin
+plugins {
+ ...
+ id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version'
+ ...
+}
+```
+
+### Set up Hilt for dependency injection
+
+In the `build.gradle` (app) file, add the following:
+
+```kotlin
+implementation "com.google.dagger:hilt-android:$hilt_version"
+annotationProcessor "com.google.dagger:hilt-compiler:$hilt_version"
+implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
+```
+
+Create a new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation:
+
+```kotlin
+// ManageProductApplication.kt
+@HiltAndroidApp
+class ManageProductApplication: Application()
+```
+
+Open the `AndroidManifest.xml` file, update name property of Application tag:
+
+```xml
+
+
+```
+
+Add annotation for the `MainActivity`:
+
+```kotlin
+@AndroidEntryPoint
+class MainActivity : ComponentActivity() {
+}
+```
+
+### Provide Supabase instances with Hilt
+
+To make the app easier to test, create a `SupabaseMdule.kt` file as follows:
+
+```kotlin
+@InstallIn(SingletonComponent::class)
+@Module
+object SupabaseModule {
+
+ @Provides
+ @Singleton
+ fun provideSupabaseClient(): SupabaseClient {
+ return createSupabaseClient(
+ supabaseUrl = BuildConfig.SUPABASE_URL,
+ supabaseKey = BuildConfig.SUPABASE_ANON_KEY
+ ) {
+ install(Postgrest)
+ install(GoTrue) {
+ flowType = FlowType.PKCE
+ scheme = "app"
+ host = "supabase.com"
+ }
+ install(Storage)
+ }
+ }
+
+ @Provides
+ @Singleton
+ fun provideSupabaseDatabase(client: SupabaseClient): Postgrest {
+ return client.postgrest
+ }
+
+ @Provides
+ @Singleton
+ fun provideSupabaseGoTrue(client: SupabaseClient): GoTrue {
+ return client.gotrue
+ }
+
+
+ @Provides
+ @Singleton
+ fun provideSupabaseStorage(client: SupabaseClient): Storage {
+ return client.storage
+ }
+
+}
+```
+
+### Create a data transfer object
+
+Create a `ProductDto.kt` class and use annotations to parse data from Supabase:
+
+```kotlin
+@Serializable
+data class ProductDto(
+
+ @SerialName("name")
+ val name: String,
+
+ @SerialName("price")
+ val price: Double,
+
+ @SerialName("image")
+ val image: String?,
+
+ @SerialName("id")
+ val id: String,
+)
+```
+
+Create a Domain object in `Product.kt` expose the data in your view:
+
+```kotlin
+data class Product(
+ val id: String,
+ val name: String,
+ val price: Double,
+ val image: String?
+)
+```
+
+### Implement repositories
+
+Create a `ProductRepository` interface and its implementation named `ProductRepositoryImpl`. This holds the logic to interact with data sources from Supabase. Do the same with the `AuthenticationRepository`.
+
+Create the Product Repository:
+
+```kotlin
+interface ProductRepository {
+ suspend fun createProduct(product: Product): Boolean
+ suspend fun getProducts(): List?
+ suspend fun getProduct(id: String): ProductDto
+ suspend fun deleteProduct(id: String)
+ suspend fun updateProduct(
+ id: String, name: String, price: Double, imageName: String, imageFile: ByteArray
+ )
+}
+```
+
+```kotlin
+class ProductRepositoryImpl @Inject constructor(
+ private val postgrest: Postgrest,
+ private val storage: Storage,
+) : ProductRepository {
+ override suspend fun createProduct(product: Product): Boolean {
+ return try {
+ withContext(Dispatchers.IO) {
+ val productDto = ProductDto(
+ name = product.name,
+ price = product.price,
+ )
+ postgrest["products"].insert(productDto)
+ true
+ }
+ true
+ } catch (e: java.lang.Exception) {
+ throw e
+ }
+ }
+
+ override suspend fun getProducts(): List? {
+ return withContext(Dispatchers.IO) {
+ val result = postgrest["products"]
+ .select().decodeList()
+ result
+ }
+ }
+
+
+ override suspend fun getProduct(id: String): ProductDto {
+ return withContext(Dispatchers.IO) {
+ postgrest["products"].select {
+ eq("id", id)
+ }.decodeSingle()
+ }
+ }
+
+ override suspend fun deleteProduct(id: String) {
+ return withContext(Dispatchers.IO) {
+ postgrest["products"].delete {
+ eq("id", id)
+ }
+ }
+ }
+
+ override suspend fun updateProduct(
+ id: String,
+ name: String,
+ price: Double,
+ imageName: String,
+ imageFile: ByteArray
+ ) {
+ withContext(Dispatchers.IO) {
+ if (imageFile.isNotEmpty()) {
+ val imageUrl =
+ storage["Product%20Image"].upload(
+ path = "$imageName.png",
+ data = imageFile,
+ upsert = true
+ )
+ postgrest["products"].update({
+ set("name", name)
+ set("price", price)
+ set("image", buildImageUrl(imageFileName = imageUrl))
+ }) {
+ eq("id", id)
+ }
+ } else {
+ postgrest["products"].update({
+ set("name", name)
+ set("price", price)
+ }) {
+ eq("id", id)
+ }
+ }
+ }
+ }
+
+ // Because I named the bucket as "Product Image" so when it turns to an url, it is "%20"
+ // For better approach, you should create your bucket name without space symbol
+ private fun buildImageUrl(imageFileName: String) =
+ "${BuildConfig.SUPABASE_URL}/storage/v1/object/public/${imageFileName}".replace(" ", "%20")
+}
+```
+
+Create the Authentication Repository:
+
+```kotlin
+interface AuthenticationRepository {
+ suspend fun signIn(email: String, password: String): Boolean
+ suspend fun signUp(email: String, password: String): Boolean
+ suspend fun signInWithGoogle(): Boolean
+}
+```
+
+```kotlin
+class AuthenticationRepositoryImpl @Inject constructor(
+ private val goTrue: GoTrue
+) : AuthenticationRepository {
+ override suspend fun signIn(email: String, password: String): Boolean {
+ return try {
+ goTrue.loginWith(Email) {
+ this.email = email
+ this.password = password
+ }
+ true
+ } catch (e: Exception) {
+ false
+ }
+ }
+
+ override suspend fun signUp(email: String, password: String): Boolean {
+ return try {
+ goTrue.signUpWith(Email) {
+ this.email = email
+ this.password = password
+ }
+ true
+ } catch (e: Exception) {
+ false
+ }
+ }
+
+ override suspend fun signInWithGoogle(): Boolean {
+ return try {
+ goTrue.loginWith(Google)
+ true
+ } catch (e: Exception) {
+ false
+ }
+ }
+}
+```
+
+### Implement screens
+
+Create a `ProductListViewModel`:
+
+```kotlin
+@HiltViewModel
+class ProductListViewModel @Inject constructor(
+private val productRepository: ProductRepository,
+) : ViewModel() {
+
+ private val _productList = MutableStateFlow?>(listOf())
+ val productList: Flow?> = _productList
+
+
+ private val _isLoading = MutableStateFlow(false)
+ val isLoading: Flow = _isLoading
+
+ init {
+ getProducts()
+ }
+
+ fun getProducts() {
+ viewModelScope.launch {
+ val products = productRepository.getProducts()
+ _productList.emit(products?.map { it -> it.asDomainModel() })
+ }
+ }
+
+ fun removeItem(product: Product) {
+ viewModelScope.launch {
+ val newList = mutableListOf().apply { _productList.value?.let { addAll(it) } }
+ newList.remove(product)
+ _productList.emit(newList.toList())
+ // Call api to remove
+ productRepository.deleteProduct(id = product.id)
+ // Then fetch again
+ getProducts()
+ }
+ }
+
+ private fun ProductDto.asDomainModel(): Product {
+ return Product(
+ id = this.id,
+ name = this.name,
+ price = this.price,
+ image = this.image
+ )
+ }
+
+}
+
+```
+
+Create the `ProductListScreen.kt`:
+
+```kotlin
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
+@Composable
+fun ProductListScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: ProductListViewModel = hiltViewModel(),
+) {
+ val isLoading by viewModel.isLoading.collectAsState(initial = false)
+ val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = isLoading)
+ SwipeRefresh(state = swipeRefreshState, onRefresh = { viewModel.getProducts() }) {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ title = {
+ Text(
+ text = stringResource(R.string.product_list_text_screen_title),
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
+ },
+ )
+ },
+ floatingActionButton = {
+ AddProductButton(onClick = { navController.navigate(AddProductDestination.route) })
+ }
+ ) { padding ->
+ val productList = viewModel.productList.collectAsState(initial = listOf()).value
+ if (!productList.isNullOrEmpty()) {
+ LazyColumn(
+ modifier = modifier.padding(padding),
+ contentPadding = PaddingValues(5.dp)
+ ) {
+ itemsIndexed(
+ items = productList,
+ key = { _, product -> product.name }) { _, item ->
+ val state = rememberDismissState(
+ confirmStateChange = {
+ if (it == DismissValue.DismissedToStart) {
+ // Handle item removed
+ viewModel.removeItem(item)
+ }
+ true
+ }
+ )
+ SwipeToDismiss(
+ state = state,
+ background = {
+ val color by animateColorAsState(
+ targetValue = when (state.dismissDirection) {
+ DismissDirection.StartToEnd -> MaterialTheme.colorScheme.primary
+ DismissDirection.EndToStart -> MaterialTheme.colorScheme.primary.copy(
+ alpha = 0.2f
+ )
+ null -> Color.Transparent
+ }
+ )
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .background(color = color)
+ .padding(16.dp),
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Delete,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = modifier.align(Alignment.CenterEnd)
+ )
+ }
+
+ },
+ dismissContent = {
+ ProductListItem(
+ product = item,
+ modifier = modifier,
+ onClick = {
+ navController.navigate(
+ ProductDetailsDestination.createRouteWithParam(
+ item.id
+ )
+ )
+ },
+ )
+ },
+ directions = setOf(DismissDirection.EndToStart),
+ )
+ }
+ }
+ } else {
+ Text("Product list is empty!")
+ }
+
+ }
+ }
+}
+
+@Composable
+private fun AddProductButton(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+) {
+ FloatingActionButton(
+ modifier = modifier,
+ onClick = onClick,
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = null,
+ )
+ }
+}
+```
+
+Create the `ProductDetailsViewModel.kt`:
+
+```kotlin
+
+@HiltViewModel
+class ProductDetailsViewModel @Inject constructor(
+ private val productRepository: ProductRepository,
+ savedStateHandle: SavedStateHandle,
+ ) : ViewModel() {
+
+ private val _product = MutableStateFlow(null)
+ val product: Flow = _product
+
+ private val _name = MutableStateFlow("")
+ val name: Flow = _name
+
+ private val _price = MutableStateFlow(0.0)
+ val price: Flow = _price
+
+ private val _imageUrl = MutableStateFlow("")
+ val imageUrl: Flow = _imageUrl
+
+ init {
+ val productId = savedStateHandle.get(ProductDetailsDestination.productId)
+ productId?.let {
+ getProduct(productId = it)
+ }
+ }
+
+ private fun getProduct(productId: String) {
+ viewModelScope.launch {
+ val result = productRepository.getProduct(productId).asDomainModel()
+ _product.emit(result)
+ _name.emit(result.name)
+ _price.emit(result.price)
+ }
+ }
+
+ fun onNameChange(name: String) {
+ _name.value = name
+ }
+
+ fun onPriceChange(price: Double) {
+ _price.value = price
+ }
+
+ fun onSaveProduct(image: ByteArray) {
+ viewModelScope.launch {
+ productRepository.updateProduct(
+ id = _product.value?.id,
+ price = _price.value,
+ name = _name.value,
+ imageFile = image,
+ imageName = "image_${_product.value.id}",
+ )
+ }
+ }
+
+ fun onImageChange(url: String) {
+ _imageUrl.value = url
+ }
+
+ private fun ProductDto.asDomainModel(): Product {
+ return Product(
+ id = this.id,
+ name = this.name,
+ price = this.price,
+ image = this.image
+ )
+ }
+}
+```
+
+Create the `ProductDetailsScreen.kt`:
+
+```kotlin
+@OptIn(ExperimentalCoilApi::class)
+@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
+@Composable
+fun ProductDetailsScreen(
+ modifier: Modifier = Modifier,
+ viewModel: ProductDetailsViewModel = hiltViewModel(),
+ navController: NavController,
+ productId: String?,
+) {
+ val snackBarHostState = remember { SnackbarHostState() }
+ val coroutineScope = rememberCoroutineScope()
+
+ Scaffold(
+ snackbarHost = { SnackbarHost(snackBarHostState) },
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ IconButton(onClick = {
+ navController.navigateUp()
+ }) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ },
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ title = {
+ Text(
+ text = stringResource(R.string.product_details_text_screen_title),
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
+ },
+ )
+ }
+ ) {
+ val name = viewModel.name.collectAsState(initial = "")
+ val price = viewModel.price.collectAsState(initial = 0.0)
+ var imageUrl = Uri.parse(viewModel.imageUrl.collectAsState(initial = null).value)
+ val contentResolver = LocalContext.current.contentResolver
+
+ Column(
+ modifier = modifier
+ .padding(16.dp)
+ .fillMaxSize()
+ ) {
+ val galleryLauncher =
+ rememberLauncherForActivityResult(ActivityResultContracts.GetContent())
+ { uri ->
+ uri?.let {
+ if (it.toString() != imageUrl.toString()) {
+ viewModel.onImageChange(it.toString())
+ }
+ }
+ }
+
+ Image(
+ painter = rememberImagePainter(imageUrl),
+ contentScale = ContentScale.Fit,
+ contentDescription = null,
+ modifier = Modifier
+ .padding(16.dp, 8.dp)
+ .size(100.dp)
+ .align(Alignment.CenterHorizontally)
+ )
+ IconButton(modifier = modifier.align(alignment = Alignment.CenterHorizontally),
+ onClick = {
+ galleryLauncher.launch("image/*")
+ }) {
+ Icon(
+ imageVector = Icons.Filled.Edit,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ OutlinedTextField(
+ label = {
+ Text(
+ text = "Product name",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 2,
+ shape = RoundedCornerShape(32),
+ modifier = modifier.fillMaxWidth(),
+ value = name.value,
+ onValueChange = {
+ viewModel.onNameChange(it)
+ },
+ )
+ Spacer(modifier = modifier.height(12.dp))
+ OutlinedTextField(
+ label = {
+ Text(
+ text = "Product price",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 2,
+ shape = RoundedCornerShape(32),
+ modifier = modifier.fillMaxWidth(),
+ value = price.value.toString(),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
+ onValueChange = {
+ viewModel.onPriceChange(it.toDouble())
+ },
+ )
+ Spacer(modifier = modifier.weight(1f))
+ Button(
+ modifier = modifier.fillMaxWidth(),
+ onClick = {
+ if (imageUrl.host?.contains("supabase") == true) {
+ viewModel.onSaveProduct(image = byteArrayOf())
+ } else {
+ val image = uriToByteArray(contentResolver, imageUrl)
+ viewModel.onSaveProduct(image = image)
+ }
+ coroutineScope.launch {
+ snackBarHostState.showSnackbar(
+ message = "Product updated successfully !",
+ duration = SnackbarDuration.Short
+ )
+ }
+ }) {
+ Text(text = "Save changes")
+ }
+ Spacer(modifier = modifier.height(12.dp))
+ OutlinedButton(
+ modifier = modifier
+ .fillMaxWidth(),
+ onClick = {
+ navController.navigateUp()
+ }) {
+ Text(text = "Cancel")
+ }
+
+ }
+
+ }
+}
+
+
+private fun getBytes(inputStream: InputStream): ByteArray {
+ val byteBuffer = ByteArrayOutputStream()
+ val bufferSize = 1024
+ val buffer = ByteArray(bufferSize)
+ var len = 0
+ while (inputStream.read(buffer).also { len = it } != -1) {
+ byteBuffer.write(buffer, 0, len)
+ }
+ return byteBuffer.toByteArray()
+}
+
+
+private fun uriToByteArray(contentResolver: ContentResolver, uri: Uri): ByteArray {
+ if (uri == Uri.EMPTY) {
+ return byteArrayOf()
+ }
+ val inputStream = contentResolver.openInputStream(uri)
+ if (inputStream != null) {
+ return getBytes(inputStream)
+ }
+ return byteArrayOf()
+}
+```
+
+Create a AddProductScreen:
+
+```kotlin
+@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AddProductScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: AddProductViewModel = hiltViewModel(),
+) {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ IconButton(onClick = {
+ navController.navigateUp()
+ }) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ },
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ title = {
+ Text(
+ text = stringResource(R.string.add_product_text_screen_title),
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
+ },
+ )
+ }
+ ) { padding ->
+ val navigateAddProductSuccess =
+ viewModel.navigateAddProductSuccess.collectAsState(initial = null).value
+ val isLoading =
+ viewModel.isLoading.collectAsState(initial = null).value
+ if (isLoading == true) {
+ LoadingScreen(message = "Adding Product",
+ onCancelSelected = {
+ navController.navigateUp()
+ })
+ } else {
+ SuccessScreen(
+ message = "Product added",
+ onMoreAction = {
+ viewModel.onAddMoreProductSelected()
+ },
+ onNavigateBack = {
+ navController.navigateUp()
+ })
+ }
+
+ }
+}
+```
+
+Create the `AddProductViewModel.kt`:
+
+```kotlin
+@HiltViewModel
+class AddProductViewModel @Inject constructor(
+ private val productRepository: ProductRepository,
+) : ViewModel() {
+
+ private val _isLoading = MutableStateFlow(false)
+ val isLoading: Flow = _isLoading
+
+ private val _showSuccessMessage = MutableStateFlow(false)
+ val showSuccessMessage: Flow = _showSuccessMessage
+
+ fun onCreateProduct(name: String, price: Double) {
+ if (name.isEmpty() || price <= 0) return
+ viewModelScope.launch {
+ _isLoading.value = true
+ val product = Product(
+ id = UUID.randomUUID().toString(),
+ name = name,
+ price = price,
+ )
+ productRepository.createProduct(product = product)
+ _isLoading.value = false
+ _showSuccessMessage.emit(true)
+
+ }
+ }
+}
+```
+
+Create a `SignUpViewModel`:
+
+```kotlin
+@HiltViewModel
+class SignUpViewModel @Inject constructor(
+ private val authenticationRepository: AuthenticationRepository
+) : ViewModel() {
+
+ private val _email = MutableStateFlow("")
+ val email: Flow = _email
+
+ private val _password = MutableStateFlow("")
+ val password = _password
+
+ fun onEmailChange(email: String) {
+ _email.value = email
+ }
+
+ fun onPasswordChange(password: String) {
+ _password.value = password
+ }
+
+ fun onSignUp() {
+ viewModelScope.launch {
+ authenticationRepository.signUp(
+ email = _email.value,
+ password = _password.value
+ )
+ }
+ }
+}
+```
+
+Create the `SignUpScreen.kt`:
+
+```kotlin
+@Composable
+fun SignUpScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: SignUpViewModel = hiltViewModel()
+) {
+ val snackBarHostState = remember { SnackbarHostState() }
+ val coroutineScope = rememberCoroutineScope()
+ Scaffold(
+ snackbarHost = { androidx.compose.material.SnackbarHost(snackBarHostState) },
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ IconButton(onClick = {
+ navController.navigateUp()
+ }) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ },
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ title = {
+ Text(
+ text = "Sign Up",
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
+ },
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .padding(paddingValues)
+ .padding(20.dp)
+ ) {
+ val email = viewModel.email.collectAsState(initial = "")
+ val password = viewModel.password.collectAsState()
+ OutlinedTextField(
+ label = {
+ Text(
+ text = "Email",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 1,
+ shape = RoundedCornerShape(32),
+ modifier = modifier.fillMaxWidth(),
+ value = email.value,
+ onValueChange = {
+ viewModel.onEmailChange(it)
+ },
+ )
+ OutlinedTextField(
+ label = {
+ Text(
+ text = "Password",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 1,
+ shape = RoundedCornerShape(32),
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ value = password.value,
+ onValueChange = {
+ viewModel.onPasswordChange(it)
+ },
+ )
+ val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
+ Button(modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ onClick = {
+ localSoftwareKeyboardController?.hide()
+ viewModel.onSignUp()
+ coroutineScope.launch {
+ snackBarHostState.showSnackbar(
+ message = "Create account successfully. Sign in now!",
+ duration = SnackbarDuration.Long
+ )
+ }
+ }) {
+ Text("Sign up")
+ }
+ }
+ }
+}
+```
+
+Create a `SignInViewModel`:
+
+```kotlin
+@HiltViewModel
+class SignInViewModel @Inject constructor(
+ private val authenticationRepository: AuthenticationRepository
+) : ViewModel() {
+
+ private val _email = MutableStateFlow("")
+ val email: Flow = _email
+
+ private val _password = MutableStateFlow("")
+ val password = _password
+
+ fun onEmailChange(email: String) {
+ _email.value = email
+ }
+
+ fun onPasswordChange(password: String) {
+ _password.value = password
+ }
+
+ fun onLogin() {
+ viewModelScope.launch {
+ authenticationRepository.signIn(
+ email = _email.value,
+ password = _password.value
+ )
+ }
+ }
+
+ fun onGoogleSignIn() {
+ viewModelScope.launch {
+ authenticationRepository.signInWithGoogle()
+ }
+ }
+
+}
+```
+
+Create the `SignInScreen.kt`:
+
+```kotlin
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
+@Composable
+fun SignInScreen(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ viewModel: SignInViewModel = hiltViewModel()
+) {
+ val snackBarHostState = remember { SnackbarHostState() }
+ val coroutineScope = rememberCoroutineScope()
+ Scaffold(
+ snackbarHost = { androidx.compose.material.SnackbarHost(snackBarHostState) },
+ topBar = {
+ TopAppBar(
+ navigationIcon = {
+ IconButton(onClick = {
+ navController.navigateUp()
+ }) {
+ Icon(
+ imageVector = Icons.Filled.ArrowBack,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ },
+ backgroundColor = MaterialTheme.colorScheme.primary,
+ title = {
+ Text(
+ text = "Login",
+ color = MaterialTheme.colorScheme.onPrimary,
+ )
+ },
+ )
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = modifier
+ .padding(paddingValues)
+ .padding(20.dp)
+ ) {
+ val email = viewModel.email.collectAsState(initial = "")
+ val password = viewModel.password.collectAsState()
+ androidx.compose.material.OutlinedTextField(
+ label = {
+ Text(
+ text = "Email",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 1,
+ shape = RoundedCornerShape(32),
+ modifier = modifier.fillMaxWidth(),
+ value = email.value,
+ onValueChange = {
+ viewModel.onEmailChange(it)
+ },
+ )
+ androidx.compose.material.OutlinedTextField(
+ label = {
+ Text(
+ text = "Password",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.titleMedium
+ )
+ },
+ maxLines = 1,
+ shape = RoundedCornerShape(32),
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ value = password.value,
+ onValueChange = {
+ viewModel.onPasswordChange(it)
+ },
+ )
+ val localSoftwareKeyboardController = LocalSoftwareKeyboardController.current
+ Button(modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ onClick = {
+ localSoftwareKeyboardController?.hide()
+ viewModel.onGoogleSignIn()
+ }) {
+ Text("Sign in with Google")
+ }
+ Button(modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp),
+ onClick = {
+ localSoftwareKeyboardController?.hide()
+ viewModel.onSignIn()
+ coroutineScope.launch {
+ snackBarHostState.showSnackbar(
+ message = "Sign in successfully !",
+ duration = SnackbarDuration.Long
+ )
+ }
+ }) {
+ Text("Sign in")
+ }
+ OutlinedButton(modifier = modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp), onClick = {
+ navController.navigate(SignUpDestination.route)
+ }) {
+ Text("Sign up")
+ }
+ }
+ }
+}
+```
+
+### Create the success screen
+
+Implement a "Sign In" success screen. Create new "Empty Activity" and in `AndroidManifest.xml`, add a deep link, make sure `scheme` and `host` are the same as the ones set in the GoTrue instance.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```kotlin
+@AndroidEntryPoint
+class DeepLinkHandlerActivity : ComponentActivity() {
+
+ @Inject
+ lateinit var supabaseClient: SupabaseClient
+
+ private lateinit var callback: (String, String) -> Unit
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ supabaseClient.handleDeeplinks(intent = intent,
+ onSessionSuccess = { userSession ->
+ Log.d("LOGIN", "Log in successfully with user info: ${userSession.user}")
+ userSession.user?.apply {
+ callback(email ?: "", createdAt.toString())
+ }
+ })
+ setContent {
+ val navController = rememberNavController()
+ val emailState = remember { mutableStateOf("") }
+ val createdAtState = remember { mutableStateOf("") }
+ LaunchedEffect(Unit) {
+ callback = { email, created ->
+ emailState.value = email
+ createdAtState.value = created
+ }
+ }
+ ManageProductsTheme {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ SignInSuccessScreen(
+ modifier = Modifier.padding(20.dp),
+ navController = navController,
+ email = emailState.value,
+ createdAt = createdAtState.value,
+ onClick = { navigateToMainApp() }
+ )
+ }
+ }
+ }
+ }
+
+ private fun navigateToMainApp() {
+ val intent = Intent(this, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+ }
+ startActivity(intent)
+ }
+}
+```
+
+export const Page = ({ children }) =>
+
+export default Page
diff --git a/apps/docs/pages/guides/integrations.mdx b/apps/docs/pages/guides/integrations.mdx
deleted file mode 100644
index f64300cb056..00000000000
--- a/apps/docs/pages/guides/integrations.mdx
+++ /dev/null
@@ -1,45 +0,0 @@
-import Layout from '~/layouts/DefaultLayout'
-import Link from 'next/link'
-import Image from 'next/image'
-import { GlassPanel } from 'ui'
-import { integrations } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
-
-export const meta = {
- title: 'Integrations',
-}
-
-Explore a variety of integrations from Supabase partners. Need a different integration? Find a [Supabase expert](https://supabase.com/partners/experts) to help build your next idea.
-
-
-
-export const Page = ({ children }) =>
-
-export default Page
diff --git a/apps/docs/pages/guides/integrations/appsmith.mdx b/apps/docs/pages/guides/integrations/appsmith.mdx
deleted file mode 100644
index fcc582c185a..00000000000
--- a/apps/docs/pages/guides/integrations/appsmith.mdx
+++ /dev/null
@@ -1,173 +0,0 @@
-import Layout from '~/layouts/DefaultGuideLayout'
-
-export const meta = {
- id: 'appsmith',
- title: 'Appsmith',
- description:
- 'Get started with Supabase and Appsmith, an open-source framework for building internal tools.',
-}
-
-This guide explains how to quickly build a Support Dashboard by connecting a Supabase back-end to an Appsmith front-end.
-
-[Appsmith](https://www.appsmith.com/) is an open-source framework for building internal tools. It lets you drag-and-drop UI components to build pages, connect to any API, database or GraphQL source and write logic with JavaScript objects.
-
-If you don’t have an Appsmith account, create one [here](https://app.appsmith.com/user/signup).
-
-Let’s get started!
-
-## Step 1: Set up your Backend on Supabase
-
-- On the [Supabase dashboard](https://supabase.com/dashboard), click `New project` and set the name to **Support Dashboard**
-
-
-
-- Create a new table by clicking on the Create Table option on the side navigation.
-- Supabase provides many ways to add data to the tables, from writing queries to creating schemas using UI to simply uploading CSV files. For our support dashboard, we will be creating the **tickets** table by uploading the [CSV file](https://raw.githubusercontent.com/vihar/datasets/master/tickets.csv) on Supabase.
-
-
-
-The database is now set up.
-
-## Step 2: Connect the database to Appsmith
-
-- Note down the database connection information under Project Settings in Supabase.
-
-
-
-- On Appsmith, create a new application under the dashboard under your preferred organization.
-- Click on the `+` icon next to Datasources on the left navigation bar under Page1
-- Next, click on Create New tab and choose PostgreSQL datasource, you’ll see the following screenshot:
-
-
-
-- Fill out the form to connect to your Supabase instance. Click **Test** to test connection and then **Save** to save the datasource
-
-
-
-## Step 3: Build UI on Appsmith
-
-- Click on the + icon next to widgets and drag and drop a Tab widget. We can configure using the property pane by clicking on the cog icon on the top-right corner.
-- As seen in the below screenshot, we have added four tabs to support the dashboard.
-
-
-
-- Add widgets to the **Home** tab to create the dashboard as shown in the screenshot below. For eg: **Critical Open Issues** is a **Text** widget and below it is an **Input** widget which we will bind later to display the number of open tickets.
-
-- Set up the **New** button to open a modal which will have a form to raise a new ticket.
-
-
-
-- In the modal widget, add a few widgets to accept input when creating a new ticket. Please refer to the screenshot below.
-
-
-
-## Step 4: Writing Queries in Appsmith and binding data to widgets
-
-- Click on the + icon next to Datasources on the navigation bar and click New Query next to the Supabase connection here to create a new query.
-
-
-
-- Rename the query to create_new_ticket under the query pane; here we can write SQL that can collect the data from the widgets using mustache templates.
-
-```jsx
-INSERT INTO PUBLIC."tickets"("id","createdAt","user","updatedAt","description",
-"status","priority","category","assignedTo")
-VALUES('{{appsmith.store.ticket.id}}','{{moment().format('yyyy-mm-ddHH:MM:ss')}}','{{c_user.text}}',
-'{{moment().format('yyyy-mm-ddHH:MM:ss')}}','{{c_description.text}}','{{c_status.selectedOptionValue}}',
-'{{c_property.selectedOptionValue}}',
-'{{c_category.selectedOptionValue}}','{{c_assignee.selectedOptionValue}}');
-```
-
-- Click the **Confirm** button on the modal and under **Events**, set the **onClick** property to execute the create_new_ticket query.
-- Create a second query named **get_tickets** that will list all the tickets.
-
- ***
-
-```jsx
-SELECT * FROM public."tickets";
-```
-
-- Drag and drop a table widget under the **Assigned To Me** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
-
-```jsx
-{
- {
- get_tickets.data.filter(
- (t) => t.assignedTo === 'confidence@appsmith.com' && t.status !== 'closed'
- )
- }
-}
-```
-
-- Drag and drop a table widget under the **Resolved** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
-
-```jsx
-{
- {
- get_tickets.data.filter((t) => t.status === 'open')
- }
-}
-```
-
-- Drag and drop a table widget under the **Closed** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
-
-```jsx
-{
- {
- get_tickets.data.filter((t) => t.status === 'closed')
- }
-}
-```
-
-## Step 5: Creating Charts in Appsmith
-
-- On the **Home** tab, click on the first Chart widget. Add Title **Open Issues By Category**. Change the **Chart Type** property to **Column Chart**.
-- Update the x-axis and y-axis Labels under **Axis** on the property pane.
-- Add the following code snippet under the **Series Data** property to bind the data to be displayed on the x and y axes.
-
-```jsx
-[
- {
- "x": "Hardware",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='hardware').length}}
- },
- {
- "x": "Software",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='software').length}}
- },
- {
- "x": "Other",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='other').length}}
- }
-]
-```
-
-- The second chart will be a pie chart. Add the title, axes labels as mentioned above,
-- Add the following code snippet under the **Series Data** property of the pie chart.
-
-```jsx
-[
- {
- "x": "High",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='high').length}}
- },
- {
- "x": "Medium",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='medium').length}}
- },
- {
- "x": "Low",
- "y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='low').length}}
- }
-]
-```
-
-## Resources
-
-- [Appsmith](https://www.appsmith.com/) official website.
-- [Appsmith GitHub](https://github.com/appsmithorg).
-- [Appsmith](https://docs.appsmith.com/) documentation.
-
-export const Page = ({ children }) =>
-
-export default Page
diff --git a/apps/docs/pages/guides/integrations/auth0.mdx b/apps/docs/pages/guides/integrations/auth0.mdx
deleted file mode 100644
index c379072a409..00000000000
--- a/apps/docs/pages/guides/integrations/auth0.mdx
+++ /dev/null
@@ -1,470 +0,0 @@
-import Layout from '~/layouts/DefaultGuideLayout'
-
-export const meta = {
- id: 'auth0',
- title: 'Auth0',
- description:
- 'Swap out Supabase authentication with Auth0. Let Auth0 handle tokens and signing users in and out, while Supabase enforces authorization policies with Row Level Security (RLS).',
-}
-
-This guide steps through building a Next.js application with Auth0 and Supabase. We configure Auth0 to handle authenticating users and managing tokens, while writing our authorization logic in Supabase - using Row Level Security policies.
-
-> Note: This guide is heavily inspired by the [Using Next.js and Auth0 with Supabase](https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/) article on [Auth0's blog](https://auth0.com/blog/). Check it out for a practical step-by-step guide on integrating Auth0 and Supabase.
-
-The full code example for this guide can be found [here](https://github.com/dijonmusters/supabase-auth0-example).
-
-[Auth0](https://auth0.com/) is an authentication and authorization platform, offering numerous strategies to authenticate and manage users. It provides fine-grain control over how users sign in to your application, the token that is generated, and what data is stored about your users.
-
-[Next.js](https://nextjs.org/) is a web application framework built on top of React. We will be using it for this example, as it allows us to write server-side logic within our application. Auth0 have also written a [very well integrated authentication library](https://www.npmjs.com/package/@auth0/nextjs-auth0) specifically for Next.js.
-
-> Note: API routes (serverless functions) in Next.js closely resemble the structure of Node server frameworks - such as Express, Koa and Fastify. The server-side logic in this guide could easily be refactored in one of these frameworks and managed as a separate application to the front-end.
-
-If you don’t have an Auth0 account, create one [here](https://auth0.com/signup).
-
-You will also need a Supabase account, which can be created by signing in [here](https://supabase.com/dashboard/).
-
-## Step 1: Creating an Auth0 tenant
-
-From the Auth0 dashboard, click the menu to the right of the Auth0 logo, and select `Create tenant`.
-
-
-
-Enter a `Domain` for your tenant - this will need to be unique.
-
-Select a `Region` - this should be geographically close to the majority of your users.
-
-Select `Development` for `Environment Tag` - this should be production when you're ready to go live.
-
-
-
-## Step 2: Setting up an Auth0 application
-
-From the sidebar menu, select `Applications` > `Applications` and click `Create Application`.
-
-Give your application a name, select the `Regular Web Applications` option and click `Create`.
-
-
-
-Select `Settings` and navigate to the `Application URIs` section, and update the following:
-
-`Allowed Callback URLs`: `http://localhost:3000/api/auth/callback`
-
-`Allowed Logout URLs`: `http://localhost:3000`
-
-Scroll to the bottom of the `Settings` section and reveal the `Advanced Settings`.
-
-Select `OAuth` and set `JSON Web Token Signature` to `RS256`.
-
-Confirm `OIDC Conformant` is `Enabled`.
-
-Click `Save` to update the settings.
-
-## Step 3: Creating a Supabase project
-
-From your [Supabase dashboard](https://supabase.com/dashboard/), click `New project`.
-
-Enter a `Name` for your Supabase project.
-
-Enter a secure `Database Password`.
-
-Select the same `Region` you selected for your Auth0 tenant.
-
-Click `Create new project`.
-
-
-
-## Step 4: Creating data in Supabase
-
-From the sidebar menu in the [Supabase dashboard](https://supabase.com/dashboard/), click `Table editor`, then `New table`.
-
-Enter `todo` as the `Name` field.
-
-Select `Enable Row Level Security (RLS)`.
-
-Create two new columns:
-
-- `title` as `text`
-- `user_id` as `text`
-- `is_complete` as `bool` with the default value `false`
-
-Click `Save` to create the new table.
-
-
-
-From the `Table editor` view, select the `todo` table and click `Insert row`.
-
-Fill out the `title` field and click `Save`.
-
-
-
-Click `Insert row` and add a couple of extra todos.
-
-
-
-## Step 5: Building a Next.js app
-
-Create a new Next.js project:
-
-```bash
-npx create-next-app
-```
-
-Create a `.env.local` file and enter the following values:
-
-```
-AUTH0_SECRET=any-secure-value
-AUTH0_BASE_URL=http://localhost:3000
-AUTH0_ISSUER_BASE_URL=https://..auth0.com
-AUTH0_CLIENT_ID=get-from-auth0-dashboard
-AUTH0_CLIENT_SECRET=get-from-auth0-dashboard
-NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard
-NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard
-SUPABASE_JWT_SECRET=get-from-supabase-dashboard
-```
-
-> Note: Auth0 values can be found under `Settings > Basic Information` for your application.
-
-
-
-> Note: Supabase values can be found under `Settings > API` for your project.
-
-
-
-Restart your Next.js development server to read in the new values from `.env.local`.
-
-```bash
-npm run dev
-```
-
-## Step 6: Install Auth0 Next.js library
-
-Install the `@auth0/nextjs-auth0` library.
-
-```bash
-npm i @auth0/nextjs-auth0
-```
-
-Create a new file `pages/api/auth/[...auth0].js` and add:
-
-```jsx
-// pages/api/auth/[...auth0].js
-
-import { handleAuth } from '@auth0/nextjs-auth0'
-
-export default handleAuth()
-```
-
-> Note: This will create a few API routes for us. The main ones we will use are `/api/auth/login` and `/api/auth/logout` to handle signing users in and out.
-
-Open `pages/_app.js` and wrap our `Component` with the `UserProvider` from Auth0:
-
-```jsx
-// pages/_app.js
-
-import React from 'react'
-import { UserProvider } from '@auth0/nextjs-auth0/client'
-
-const App = ({ Component, pageProps }) => {
- return (
-
-
-
- )
-}
-
-export default App
-```
-
-Update `pages/index.js` to ensure the user is logged in to view the landing page.
-
-```jsx
-// pages/index.js
-
-import styles from '../styles/Home.module.css'
-import { withPageAuthRequired } from '@auth0/nextjs-auth0'
-import Link from 'next/link'
-
-const Index = ({ user }) => {
- return (
-