diff --git a/.claude/skills/react-hook-form/SKILL.md b/.claude/skills/react-hook-form/SKILL.md
index 29571cb24c742..c08c5037a2b4b 100644
--- a/.claude/skills/react-hook-form/SKILL.md
+++ b/.claude/skills/react-hook-form/SKILL.md
@@ -235,6 +235,12 @@ schema, `onChange`, rendering, and the submit mapping.
- Gate Save on `isDirty`; show Cancel only when dirty. In the owner, destructure
from `form.formState`; anywhere else, `useFormState({ control })`.
+- When the form lives in a Sheet or Dialog, also wire dirty dismissal:
+ `useConfirmOnClose` + `DiscardChangesConfirmationDialog`. Route Cancel,
+ Escape, and backdrop through the guard; call the raw `onClose` on successful
+ submit so you do not prompt after save. Details:
+ `apps/design-system/content/docs/ui-patterns/modality.mdx` (Dirty form
+ dismissal) and the studio-ui-patterns skill Sheets section.
- To show _which_ fields changed (review/summary dialogs), read `dirtyFields`
from the same subscription instead of hand-comparing
`defaultValues.x !== watchedX`. RHF already does that comparison correctly;
diff --git a/.claude/skills/studio-ui-patterns/SKILL.md b/.claude/skills/studio-ui-patterns/SKILL.md
index cc875ec57104a..3f8740ec5040b 100644
--- a/.claude/skills/studio-ui-patterns/SKILL.md
+++ b/.claude/skills/studio-ui-patterns/SKILL.md
@@ -125,6 +125,10 @@ Forms in sheets:
- `layout="horizontal"` for wider sheets
- `layout="vertical"` for narrow sheets (`size="sm"` or below)
+- When the sheet contains a form, wire dirty dismissal with `useConfirmOnClose` +
+ `DiscardChangesConfirmationDialog` (Cancel, Escape, and backdrop). Source of
+ truth: `apps/design-system/content/docs/ui-patterns/modality.mdx` (Dirty form
+ dismissal). Also see the react-hook-form skill for `isDirty` destructuring.
## Copy
diff --git a/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx b/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx
index 99c0671ea699e..0ef1e2da938da 100644
--- a/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx
+++ b/apps/design-system/content/docs/ui-patterns/connect-interstitials.mdx
@@ -165,12 +165,17 @@ the pair should use the dark set together.
**Where logos come from on `/authorize`**
-- Curated partner logos resolve from allowlisted `redirect_uri` hosts only,
- not from self-asserted `name` or `website`. Those pairs may use theme tiles
- and dark assets when the partner has them.
+- Curated partner logos resolve from allowlisted `redirect_uri` hosts, or from
+ a trusted partner name when `redirect_uri` is localhost / loopback (local MCP
+ clients). Do not resolve curated logos from self-asserted `name` or `website`
+ on a remote host. Those pairs may use theme tiles and dark assets when the
+ partner has them.
- Published organisation OAuth app icons uploaded in Studio remain trusted
remote images, paired with forced-light tiles on both sides.
- Everything else falls back to `SupabaseLogo` alone.
+- If the requester name looks like a known partner but `redirect_uri` is a
+ remote host outside that partner's allowlist, show a caution admonition.
+ Localhost MCP redirects are excluded.
## Account row
@@ -217,20 +222,14 @@ Match feedback to its scope:
feedback for a failure the user needs to resolve on the current card.
```tsx
-{
- actionError && (
-
-
- {actionError}
-
-
- )
-}
+
```
Clear stale action feedback when the user retries or changes a relevant
selection. Error copy should say what failed and, when it is not obvious, what
-the user can do next.
+the user can do next. When passive supporting copy occupies the same footer
+region, replace it with the action error until the error is cleared instead of
+stacking both messages.
Cancel
-
-
- Failed to authorize Stripe Projects. Please try again.
-
+ )
+}
+
export function SignOutButton() {
return } className="px-2" aria-label="Sign out" />
}
diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md
index 41cb00108b413..3f868c0feb1c1 100644
--- a/apps/docs/CONTRIBUTING.md
+++ b/apps/docs/CONTRIBUTING.md
@@ -260,7 +260,7 @@ Run `pnpm test:local lib/content-listings.test.ts` from apps/docs.
**Manually add content listings:**
1. Add or update a `ContentListingGroup` export in [`data/content-listings/[topic].data.ts`](data/content-listings/). The `id` field must be globally unique across all listing groups. For example, use `storage-get-started` rather than `get-started`. The ID is both the lookup key and the telemetry `listingId`.
-2. Place the component inline in guide MDX, for example ``. Use a partial only when the block is reused or gated with `$Show` at the partial level.
+2. Place the component inline in guide MDX, for example ``. Use a partial only when the block is reused or gated with `$Show` at the partial level. For individual items that depend on a feature flag (for example `sdk:dart`), set `feature` on the item instead of wrapping the whole listing.
3. Run `pnpm test:local lib/content-listings.test.ts` from `apps/docs`.
Code snippets for manually adding content listings are available in [`.vscode/content-listing.code-snippets`](../../.vscode/content-listing.code-snippets). Use `cl-data` for a data export with a namespaced ID. Use `cl-inline` for an MDX component.
diff --git a/apps/docs/components/ContentListings/ContentListings.client.tsx b/apps/docs/components/ContentListings/ContentListings.client.tsx
index f71fd0236b174..548bde14d5070 100644
--- a/apps/docs/components/ContentListings/ContentListings.client.tsx
+++ b/apps/docs/components/ContentListings/ContentListings.client.tsx
@@ -2,13 +2,14 @@
import type { ContentListingGroup, ContentListingItem } from '~/lib/content-listings.schema'
import {
+ filterContentListingItems,
getContentListingById,
getContentListingGroupLabel,
isExternalContentListingHref,
} from '~/lib/content-listings.utils'
import { useSendTelemetryEvent } from '~/lib/telemetry'
import Link from 'next/link'
-import { useCallback } from 'react'
+import { useCallback, useMemo } from 'react'
import { Badge } from 'ui'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { Heading } from 'ui/src/components/CustomHTMLElements'
@@ -52,10 +53,13 @@ function ContentListingGroupHeading({ group }: { group: ContentListingGroup }) {
function ContentListingsGroup({ group }: { group: ContentListingGroup }) {
const { trackClick } = useContentListingClickHandler(group)
+ const items = useMemo(() => filterContentListingItems(group.items), [group.items])
const isGrid = group.type === 'grid'
const listClassName = isGrid ? 'grid md:grid-cols-12 gap-4' : 'list-disc pl-6 space-y-2'
const gridItemClassName = isGrid ? GRID_ITEM_CLASS[group.columns ?? 3] : undefined
+ if (!items.length) return null
+
// Heading stays outside `not-prose` so it inherits the surrounding MDX prose
// typography. The list itself opts out so its explicit Tailwind layout wins.
return (
@@ -64,7 +68,7 @@ function ContentListingsGroup({ group }: { group: ContentListingGroup }) {
diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx
index 9ec3e750fd906..ede5583753d36 100644
--- a/apps/docs/content/guides/getting-started.mdx
+++ b/apps/docs/content/guides/getting-started.mdx
@@ -5,214 +5,13 @@ description: 'Resources for getting started with Supabase.'
hideToc: true
---
-
+
-
-
-
-
-
- Develop with Supabase AI-first using plugins, MCP, and skills.
-
-
-
-
- Learn about the different API keys in Supabase and how to use them.
-
-
-
-
- Use the Supabase CLI to develop locally and collaborate between teams.
-
-
-
-
-
-
-
-
-## Use cases
-
-
-
-
- Build AI-enabled applications using our Vector toolkit.
-
-
-
-
- Clone, deploy, and fully customize a SaaS subscription application with Next.js.
-
-
-
-
- Postgres full-text search, image storage, and more.
-
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a React app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a Next.js app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a Nuxt app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, secure it with
- auth, and query the data from a Hono app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database using Prisma
- migration and seeds, and query the data from a RedwoodJS app.
-
-
- <$Show if="sdk:dart">
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a Flutter app.
-
-
- $Show>
- <$Show if="sdk:swift">
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from an iOS app.
-
-
- $Show>
- <$Show if="sdk:kotlin">
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from an Android Kotlin app.
-
-
- $Show>
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a SvelteKit app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a SolidJS app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a Vue app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a TanStack Start app.
-
-
-
-
- Learn how to create a Supabase project, add some sample data to your database, and query the
- data from a Refine app.
-
-
-
-
-
- Learn how to build a user management app with Next.js and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Refine and Supabase Database, Auth, and Storage functionality.
-
-
-
$Show>
<$Show if="docs:mobile_tutorials">
-## Mobile tutorials
+
-
- <$Show if="sdk:dart">
-
-
- Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.
-
-
- $Show>
-
-
- Learn how to build a user management app with Expo React Native and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality.
-
-
- <$Show if="sdk:kotlin">
-
-
- Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality.
-
-
- $Show>
- <$Show if="sdk:swift">
-
-
- Learn how to build a user management app with iOS and Supabase Database, Auth, and Storage functionality.
-
-
- $Show>
-
-
- Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.
-
-
-
-
- Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.
-
-
-
-
-
- Official GitHub examples, curated content from the community, and more.
-
-
-
-
- Definitions for terminology and acronyms used in the Supabase documentation.
-
-
-
-
-
-
-
-
-
-## Migrate to Supabase
-
-
-
-
-
-
- Move your auth users from Auth0 to a Supabase project.
-
-
-
-
- Move your auth users from a Firebase project to a Supabase project.
-
-
-
-
- Migrate the contents of a Firestore collection to a single Postgres table.
-
-
-
-
- Convert your Firebase Storage files to Supabase Storage.
-
-
-
-
- Migrate your Heroku Postgres database to Supabase.
-
-
-
-
- Migrate your Render Postgres database to Supabase.
-
-
-
-
- Migrate your Amazon RDS database to Supabase.
-
-
-
-
- Migrate your Postgres database to Supabase.
-
-
-
-
- Migrate your MySQL database to Supabase.
-
-
-
-
- Migrate your Microsoft SQL Server database to Supabase.
-
-
-
-
-
-
-
-
-
-## Postgres resources
-
-
-
-
-
-
- Improve query performance using various index types in Postgres.
-
-
-
-
- Understand the types of foreign key constraint deletes.
-
-
-
-
- Delete all tables in a given schema.
-
-
-
-
- Retrieve the first row in each distinct group.
-
-
-
-
- Find out which version of Postgres you are running.
-
-
-
-
-
-
-{/* end of container */}
-
-
+
diff --git a/apps/docs/data/content-listings/getting-started.data.ts b/apps/docs/data/content-listings/getting-started.data.ts
new file mode 100644
index 0000000000000..2d5ae192dce75
--- /dev/null
+++ b/apps/docs/data/content-listings/getting-started.data.ts
@@ -0,0 +1,327 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const gettingStartedOverview: ContentListingGroup = {
+ id: 'getting-started-overview',
+ type: 'grid',
+ items: [
+ {
+ title: 'Build with AI tools',
+ href: '/guides/ai-tools',
+ description: 'Develop with Supabase AI-first using plugins, MCP, and skills.',
+ },
+ {
+ title: 'API Keys',
+ href: '/guides/getting-started/api-keys',
+ description: 'Learn about the different API keys in Supabase and how to use them.',
+ },
+ {
+ title: 'Local Development',
+ href: '/guides/local-development',
+ description: 'Use the Supabase CLI to develop locally and collaborate between teams.',
+ },
+ ],
+}
+
+export const gettingStartedUseCases: ContentListingGroup = {
+ id: 'getting-started-use-cases',
+ heading: 'Use cases',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'AI, Vectors, and embeddings',
+ href: '/guides/ai#examples',
+ icon: '/docs/img/icons/openai_logo',
+ hasLightIcon: true,
+ description: 'Build AI-enabled applications using our Vector toolkit.',
+ },
+ {
+ title: 'Subscription Payments (SaaS)',
+ href: 'https://github.com/vercel/nextjs-subscription-payments#nextjs-subscription-payments-starter',
+ icon: '/docs/img/icons/nextjs-icon',
+ hasLightIcon: false,
+ description:
+ 'Clone, deploy, and fully customize a SaaS subscription application with Next.js.',
+ },
+ {
+ title: 'Partner Gallery',
+ href: 'https://github.com/supabase-community/partner-gallery-example#supabase-partner-gallery-example',
+ icon: '/docs/img/icons/nextjs-icon',
+ hasLightIcon: false,
+ description: 'Postgres full-text search, image storage, and more.',
+ },
+ ],
+}
+
+export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
+ id: 'getting-started-framework-quickstarts',
+ heading: 'Framework quickstarts',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'React',
+ href: '/guides/getting-started/quickstarts/reactjs',
+ icon: '/docs/img/icons/react-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a React app.',
+ },
+ {
+ title: 'Next.js',
+ href: '/guides/getting-started/quickstarts/nextjs',
+ icon: '/docs/img/icons/nextjs-icon',
+ hasLightIcon: true,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Next.js app.',
+ },
+ {
+ title: 'Nuxt',
+ href: '/guides/getting-started/quickstarts/nuxtjs',
+ icon: '/docs/img/icons/nuxt-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Nuxt app.',
+ },
+ {
+ title: 'Hono',
+ href: '/guides/getting-started/quickstarts/hono',
+ icon: '/docs/img/icons/hono-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, secure it with auth, and query the data from a Hono app.',
+ },
+ {
+ title: 'RedwoodJS',
+ href: '/guides/getting-started/quickstarts/redwoodjs',
+ icon: '/docs/img/icons/redwood-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database using Prisma migration and seeds, and query the data from a RedwoodJS app.',
+ },
+ {
+ title: 'Flutter',
+ href: '/guides/getting-started/quickstarts/flutter',
+ icon: '/docs/img/icons/flutter-icon',
+ hasLightIcon: false,
+ feature: 'sdk:dart',
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.',
+ },
+ {
+ title: 'iOS SwiftUI',
+ href: '/guides/getting-started/quickstarts/ios-swiftui',
+ icon: '/docs/img/icons/swift-icon',
+ hasLightIcon: false,
+ feature: 'sdk:swift',
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an iOS app.',
+ },
+ {
+ title: 'Android Kotlin',
+ href: '/guides/getting-started/quickstarts/kotlin',
+ icon: '/docs/img/icons/kotlin-icon',
+ hasLightIcon: false,
+ feature: 'sdk: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.',
+ },
+ {
+ title: 'SvelteKit',
+ href: '/guides/getting-started/quickstarts/sveltekit',
+ icon: '/docs/img/icons/svelte-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.',
+ },
+ {
+ title: 'SolidJS',
+ href: '/guides/getting-started/quickstarts/solidjs',
+ icon: '/docs/img/icons/solidjs-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.',
+ },
+ {
+ title: 'Vue',
+ href: '/guides/getting-started/quickstarts/vue',
+ icon: '/docs/img/icons/vuejs-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.',
+ },
+ {
+ title: 'TanStack Start',
+ href: '/guides/getting-started/quickstarts/tanstack',
+ icon: '/docs/img/icons/tanstack-icon',
+ hasLightIcon: true,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a TanStack Start app.',
+ },
+ {
+ title: 'Refine',
+ href: '/guides/getting-started/quickstarts/refine',
+ icon: '/docs/img/icons/refine-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Refine app.',
+ },
+ ],
+}
+
+export const gettingStartedWebAppDemos: ContentListingGroup = {
+ id: 'getting-started-web-app-demos',
+ heading: 'Web app demos',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Next.js',
+ href: '/guides/getting-started/tutorials/with-nextjs',
+ icon: '/docs/img/icons/nextjs-icon',
+ hasLightIcon: true,
+ description:
+ 'Learn how to build a user management app with Next.js and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'React',
+ href: '/guides/getting-started/tutorials/with-react',
+ icon: '/docs/img/icons/react-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Vue 3',
+ href: '/guides/getting-started/tutorials/with-vue-3',
+ icon: '/docs/img/icons/vuejs-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Nuxt 3',
+ href: '/guides/getting-started/tutorials/with-nuxt-3',
+ icon: '/docs/img/icons/nuxt-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Angular',
+ href: '/guides/getting-started/tutorials/with-angular',
+ icon: '/docs/img/icons/angular-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'RedwoodJS',
+ href: '/guides/getting-started/tutorials/with-redwoodjs',
+ icon: '/docs/img/icons/redwood-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Svelte',
+ href: '/guides/getting-started/tutorials/with-svelte',
+ icon: '/docs/img/icons/svelte-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'SvelteKit',
+ href: '/guides/getting-started/tutorials/with-sveltekit',
+ icon: '/docs/img/icons/svelte-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Refine',
+ href: '/guides/getting-started/tutorials/with-refine',
+ icon: '/docs/img/icons/refine-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Refine and Supabase Database, Auth, and Storage functionality.',
+ },
+ ],
+}
+
+export const gettingStartedMobileTutorials: ContentListingGroup = {
+ id: 'getting-started-mobile-tutorials',
+ heading: 'Mobile tutorials',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Flutter',
+ href: '/guides/getting-started/tutorials/with-flutter',
+ icon: '/docs/img/icons/flutter-icon',
+ hasLightIcon: false,
+ feature: 'sdk:dart',
+ description:
+ 'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Expo React Native',
+ href: '/guides/getting-started/tutorials/with-expo-react-native',
+ icon: '/docs/img/icons/expo-icon',
+ hasLightIcon: true,
+ description:
+ 'Learn how to build a user management app with Expo React Native and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Expo React Native Social Auth',
+ href: '/guides/auth/quickstarts/with-expo-react-native-social-auth',
+ icon: '/docs/img/icons/expo-icon',
+ hasLightIcon: true,
+ description:
+ 'Learn how to implement social authentication in an app with Expo React Native and Supabase Database and Auth functionality.',
+ },
+ {
+ title: 'Android Kotlin',
+ href: '/guides/getting-started/tutorials/with-kotlin',
+ icon: '/docs/img/icons/kotlin-icon',
+ hasLightIcon: false,
+ feature: 'sdk:kotlin',
+ description:
+ 'Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'iOS Swift',
+ href: '/guides/getting-started/tutorials/with-swift',
+ icon: '/docs/img/icons/swift-icon',
+ hasLightIcon: false,
+ feature: 'sdk:swift',
+ description:
+ 'Learn how to build a user management app with iOS and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Ionic React',
+ href: '/guides/getting-started/tutorials/with-ionic-react',
+ icon: '/docs/img/icons/ionic-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Ionic Vue',
+ href: '/guides/getting-started/tutorials/with-ionic-vue',
+ icon: '/docs/img/icons/ionic-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.',
+ },
+ {
+ title: 'Ionic Angular',
+ href: '/guides/getting-started/tutorials/with-ionic-angular',
+ icon: '/docs/img/icons/ionic-icon',
+ hasLightIcon: false,
+ description:
+ 'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.',
+ },
+ ],
+}
diff --git a/apps/docs/data/content-listings/index.ts b/apps/docs/data/content-listings/index.ts
index bf0e5f9353750..ef18cdc48ae72 100644
--- a/apps/docs/data/content-listings/index.ts
+++ b/apps/docs/data/content-listings/index.ts
@@ -11,8 +11,16 @@ import {
functionsExamplesWebhooksPayments,
functionsGetStarted,
} from './functions.data'
+import {
+ gettingStartedFrameworkQuickstarts,
+ gettingStartedMobileTutorials,
+ gettingStartedOverview,
+ gettingStartedUseCases,
+ gettingStartedWebAppDemos,
+} from './getting-started.data'
import { logDrainsDestinations } from './log-drains.data'
import { realtimeExamples, realtimeGetStarted, realtimeResources } from './realtime.data'
+import { resourcesMigrate, resourcesOverview, resourcesPostgres } from './resources.data'
import {
selfHostingCommunity,
selfHostingGetHelp,
@@ -37,10 +45,18 @@ const ALL_GROUPS: readonly ContentListingGroup[] = [
functionsExamplesAiMedia,
functionsExamplesMessaging,
functionsExamplesOperations,
+ gettingStartedOverview,
+ gettingStartedUseCases,
+ gettingStartedFrameworkQuickstarts,
+ gettingStartedWebAppDemos,
+ gettingStartedMobileTutorials,
logDrainsDestinations,
realtimeGetStarted,
realtimeExamples,
realtimeResources,
+ resourcesOverview,
+ resourcesMigrate,
+ resourcesPostgres,
selfHostingGetStarted,
selfHostingCommunity,
selfHostingResolveIssues,
diff --git a/apps/docs/data/content-listings/resources.data.ts b/apps/docs/data/content-listings/resources.data.ts
new file mode 100644
index 0000000000000..bca9a8af82684
--- /dev/null
+++ b/apps/docs/data/content-listings/resources.data.ts
@@ -0,0 +1,131 @@
+import type { ContentListingGroup } from '~/lib/content-listings.schema'
+
+export const resourcesOverview: ContentListingGroup = {
+ id: 'resources-overview',
+ type: 'grid',
+ items: [
+ {
+ title: 'Examples',
+ href: '/guides/getting-started',
+ description: 'Official GitHub examples, curated content from the community, and more.',
+ },
+ {
+ title: 'Glossary',
+ href: '/guides/resources/glossary',
+ description: 'Definitions for terminology and acronyms used in the Supabase documentation.',
+ },
+ ],
+}
+
+export const resourcesMigrate: ContentListingGroup = {
+ id: 'resources-migrate',
+ heading: 'Migrate to Supabase',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Auth0',
+ href: '/guides/platform/migrating-to-supabase/auth0',
+ icon: '/docs/img/icons/auth0-icon',
+ hasLightIcon: true,
+ description: 'Move your auth users from Auth0 to a Supabase project.',
+ },
+ {
+ title: 'Firebase Auth',
+ href: '/guides/platform/migrating-to-supabase/firebase-auth',
+ icon: '/docs/img/icons/firebase-icon',
+ hasLightIcon: false,
+ description: 'Move your auth users from a Firebase project to a Supabase project.',
+ },
+ {
+ title: 'Firestore Data',
+ href: '/guides/platform/migrating-to-supabase/firestore-data',
+ icon: '/docs/img/icons/firebase-icon',
+ hasLightIcon: false,
+ description: 'Migrate the contents of a Firestore collection to a single Postgres table.',
+ },
+ {
+ title: 'Firebase Storage',
+ href: '/guides/platform/migrating-to-supabase/firebase-storage',
+ icon: '/docs/img/icons/firebase-icon',
+ hasLightIcon: false,
+ description: 'Convert your Firebase Storage files to Supabase Storage.',
+ },
+ {
+ title: 'Heroku',
+ href: '/guides/platform/migrating-to-supabase/heroku',
+ icon: '/docs/img/icons/heroku-icon',
+ hasLightIcon: false,
+ description: 'Migrate your Heroku Postgres database to Supabase.',
+ },
+ {
+ title: 'Render',
+ href: '/guides/platform/migrating-to-supabase/render',
+ icon: '/docs/img/icons/render-icon',
+ hasLightIcon: false,
+ description: 'Migrate your Render Postgres database to Supabase.',
+ },
+ {
+ title: 'Amazon RDS',
+ href: '/guides/platform/migrating-to-supabase/amazon-rds',
+ icon: '/docs/img/icons/aws-rds-icon',
+ hasLightIcon: false,
+ description: 'Migrate your Amazon RDS database to Supabase.',
+ },
+ {
+ title: 'Postgres',
+ href: '/guides/platform/migrating-to-supabase/postgres',
+ icon: '/docs/img/icons/postgres-icon',
+ hasLightIcon: false,
+ description: 'Migrate your Postgres database to Supabase.',
+ },
+ {
+ title: 'MySQL',
+ href: '/guides/platform/migrating-to-supabase/mysql',
+ icon: '/docs/img/icons/mysql-icon',
+ hasLightIcon: false,
+ description: 'Migrate your MySQL database to Supabase.',
+ },
+ {
+ title: 'Microsoft SQL Server',
+ href: '/guides/platform/migrating-to-supabase/mssql',
+ icon: '/docs/img/icons/mssql-icon',
+ hasLightIcon: false,
+ description: 'Migrate your Microsoft SQL Server database to Supabase.',
+ },
+ ],
+}
+
+export const resourcesPostgres: ContentListingGroup = {
+ id: 'resources-postgres',
+ heading: 'Postgres resources',
+ headingLevel: 'h3',
+ type: 'grid',
+ items: [
+ {
+ title: 'Managing Indexes',
+ href: '/guides/database/postgres/indexes',
+ description: 'Improve query performance using various index types in Postgres.',
+ },
+ {
+ title: 'Cascade Deletes',
+ href: '/guides/database/postgres/cascade-deletes',
+ description: 'Understand the types of foreign key constraint deletes.',
+ },
+ {
+ title: 'Drop all tables in schema',
+ href: '/guides/database/postgres/dropping-all-tables-in-schema',
+ description: 'Delete all tables in a given schema.',
+ },
+ {
+ title: 'Select first row per group',
+ href: '/guides/database/postgres/first-row-in-group',
+ description: 'Retrieve the first row in each distinct group.',
+ },
+ {
+ title: 'Print Postgres version',
+ href: '/guides/database/postgres/which-version-of-postgres',
+ description: 'Find out which version of Postgres you are running.',
+ },
+ ],
+}
diff --git a/apps/docs/internals/markdown-schema/ContentListings.ts b/apps/docs/internals/markdown-schema/ContentListings.ts
index 299b23c06ecf2..ce78463b70afa 100644
--- a/apps/docs/internals/markdown-schema/ContentListings.ts
+++ b/apps/docs/internals/markdown-schema/ContentListings.ts
@@ -1,6 +1,10 @@
import { withDocsBasePath } from '~/internals/internal-links'
import type { ContentListingGroup } from '~/lib/content-listings.schema'
-import { getContentListingById, isExternalContentListingHref } from '~/lib/content-listings.utils'
+import {
+ filterContentListingItems,
+ getContentListingById,
+ isExternalContentListingHref,
+} from '~/lib/content-listings.utils'
import { getInternalLinkBaseUrl } from '../internal-links'
@@ -14,6 +18,9 @@ export function serializeContentListingGroupToMarkdown(
group: ContentListingGroup,
linkBaseUrl: string
): string {
+ const items = filterContentListingItems(group.items)
+ if (!items.length) return ''
+
const lines: string[] = []
if (group.heading) {
const level = group.headingLevel ?? 'h2'
@@ -26,7 +33,7 @@ export function serializeContentListingGroupToMarkdown(
lines.push('')
}
- for (const item of group.items) {
+ for (const item of items) {
const href = isExternalContentListingHref(item.href)
? item.href
: `${linkBaseUrl}${withDocsBasePath(item.href)}`
diff --git a/apps/docs/lib/content-listings.test.ts b/apps/docs/lib/content-listings.test.ts
index 0c609a60699bb..87b0d5d82fea7 100644
--- a/apps/docs/lib/content-listings.test.ts
+++ b/apps/docs/lib/content-listings.test.ts
@@ -144,6 +144,74 @@ describe('serializeContentListingGroupToMarkdown', () => {
expect(markdown).not.toMatch(/^#+\s/m)
expect(markdown).toContain('**[Connect]')
})
+
+ it('omits feature-gated items when those features are disabled', () => {
+ const previous = process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL
+ process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = 'true'
+
+ try {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'frameworks',
+ heading: 'Frameworks',
+ items: [
+ {
+ title: 'React',
+ href: '/guides/getting-started/quickstarts/reactjs',
+ description: 'Web framework.',
+ },
+ {
+ title: 'Flutter',
+ href: '/guides/getting-started/quickstarts/flutter',
+ description: 'Mobile framework.',
+ feature: 'sdk:dart',
+ },
+ ],
+ },
+ ''
+ )
+
+ expect(markdown).toContain('**[React]')
+ expect(markdown).not.toContain('Flutter')
+ } finally {
+ if (previous === undefined) {
+ delete process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL
+ } else {
+ process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = previous
+ }
+ }
+ })
+
+ it('returns empty string when every item is feature-gated off', () => {
+ const previous = process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL
+ process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = 'true'
+
+ try {
+ const markdown = serializeContentListingGroupToMarkdown(
+ {
+ id: 'sdk-only',
+ heading: 'SDKs',
+ items: [
+ {
+ title: 'Flutter',
+ href: '/guides/getting-started/quickstarts/flutter',
+ description: 'Mobile framework.',
+ feature: 'sdk:dart',
+ },
+ ],
+ },
+ ''
+ )
+
+ expect(markdown).toBe('')
+ } finally {
+ if (previous === undefined) {
+ delete process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL
+ } else {
+ process.env.ENABLED_FEATURES_OVERRIDE_DISABLE_ALL = previous
+ }
+ }
+ })
})
describe('ContentListings markdown handler', () => {
diff --git a/apps/docs/lib/content-listings.utils.ts b/apps/docs/lib/content-listings.utils.ts
index 10c73b4d4f1e4..93c12304399dd 100644
--- a/apps/docs/lib/content-listings.utils.ts
+++ b/apps/docs/lib/content-listings.utils.ts
@@ -1,6 +1,7 @@
import { CONTENT_LISTINGS } from '~/data/content-listings'
+import { isFeatureEnabled, type Feature } from 'common/enabled-features'
-import type { ContentListingGroup } from './content-listings.schema'
+import type { ContentListingGroup, ContentListingItem } from './content-listings.schema'
/** Label for telemetry — prefers heading, falls back to id. */
export function getContentListingGroupLabel(group: ContentListingGroup): string {
@@ -14,3 +15,8 @@ export function isExternalContentListingHref(href: string): boolean {
export function getContentListingById(id: string): ContentListingGroup | undefined {
return CONTENT_LISTINGS[id]
}
+
+/** Omits items whose `feature` flag is disabled. Shared by UI and markdown export. */
+export function filterContentListingItems(items: ContentListingItem[]): ContentListingItem[] {
+ return items.filter((item) => !item.feature || isFeatureEnabled(item.feature as Feature))
+}
diff --git a/apps/docs/lib/content-listings.zod.mjs b/apps/docs/lib/content-listings.zod.mjs
index 3f7060ef8cb21..a678cb6b57863 100644
--- a/apps/docs/lib/content-listings.zod.mjs
+++ b/apps/docs/lib/content-listings.zod.mjs
@@ -29,6 +29,11 @@ export const contentListingItemSchema = z.object({
badge: z.string().min(1).optional(),
/** Grid cards only. Defaults to inline (next to the title), matching existing usage. */
badgePosition: z.enum(['inline', 'below']).optional(),
+ /**
+ * When set, the item is omitted unless `isFeatureEnabled(feature)` is true.
+ * Use for SDK- or product-gated links (e.g. `sdk:dart`) inside a shared listing.
+ */
+ feature: z.string().min(1).optional(),
})
export const contentListingGroupTypeSchema = z.enum(['list', 'grid'])
diff --git a/apps/studio/components/interfaces/ApiAuthorization/ApiAuthorization.Form.tsx b/apps/studio/components/interfaces/ApiAuthorization/ApiAuthorization.Form.tsx
index 334ffc539a7ac..c5269d6ecfb77 100644
--- a/apps/studio/components/interfaces/ApiAuthorization/ApiAuthorization.Form.tsx
+++ b/apps/studio/components/interfaces/ApiAuthorization/ApiAuthorization.Form.tsx
@@ -21,9 +21,14 @@ import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import type { ApprovalState, IApprovalFormSchema } from './ApiAuthorization.Schema'
import {
AuthorizeConnectLogo,
+ AuthorizeImpersonationWarning,
AuthorizeRequesterDetails,
} from '@/components/interfaces/Organization/OAuthApps/AuthorizeRequesterDetails'
-import { InterstitialLayout } from '@/components/layouts/InterstitialLayout'
+import { getOAuthImpersonationWarning } from '@/components/interfaces/Organization/OAuthApps/OAuthApps.utils'
+import {
+ InterstitialActionError,
+ InterstitialLayout,
+} from '@/components/layouts/InterstitialLayout'
import type { ApiAuthorizationResponse } from '@/data/api-authorization/api-authorization-query'
import type { Organization, ResponseError } from '@/types'
@@ -71,6 +76,8 @@ export interface ApiAuthorizationMainViewProps {
requester: ApiAuthorizationResponse
organizations: OrganizationsState
requestedOrganizationSlug: string | undefined
+ actionError?: string
+ onOrganizationChange: () => void
onApprove: () => void
onDecline: () => void
}
@@ -81,6 +88,8 @@ export function ApiAuthorizationMainView({
requester,
organizations,
requestedOrganizationSlug,
+ actionError,
+ onOrganizationChange,
onApprove,
onDecline,
}: ApiAuthorizationMainViewProps): ReactNode {
@@ -108,6 +117,10 @@ export function ApiAuthorizationMainView({
) : (
<>
+
{organizations._tag === 'loading' && }
{organizations._tag === 'error' && (
@@ -121,6 +134,7 @@ export function ApiAuthorizationMainView({
requester={requester}
organizations={organizations.organizations}
requestedOrganizationSlug={requestedOrganizationSlug}
+ onOrganizationChange={onOrganizationChange}
/>
)}
{showReadyContent && (
@@ -134,6 +148,7 @@ export function ApiAuthorizationMainView({
approvalState={approvalState}
requester={requester}
redirectUrl={externalRedirectUrl}
+ actionError={actionError}
onApprove={onApprove}
onDecline={onDecline}
/>
@@ -221,6 +236,7 @@ interface OrganizationSelectorProps {
requestedOrganizationSlug: string | undefined
organizations: Array
disabled?: boolean
+ onOrganizationChange: () => void
}
function OrganizationSelector({
@@ -229,6 +245,7 @@ function OrganizationSelector({
requestedOrganizationSlug,
organizations,
disabled = false,
+ onOrganizationChange,
}: OrganizationSelectorProps): ReactNode {
return (