docs: add Reflex framework quickstart (#45441)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs update — adds a new framework quickstart.

## What is the current behavior?

The Framework Quickstarts section under Getting Started covers Next.js,
Nuxt, React, Flask, and others, but doesn't include Reflex
(https://reflex.dev), an open-source Python web framework that compiles
to React. Python developers building full-stack apps with Reflex have to
piece together the Supabase setup from the general Python reference
rather than following a quickstart.

## What is the new behavior?

Adds a Reflex framework quickstart at
`apps/docs/content/guides/getting-started/quickstarts/reflex.mdx`,
mirroring the structure of the existing Flask quickstart: same DB setup
partial (`quickstart_db_setup.mdx`), same env var step using
`ProjectConfigVariables`, same six-step `StepHikeCompact` shape. A nav
entry is added directly after Flask in `NavigationMenu.constants.ts`,
gated on `!jsOnly` to match Flask's pattern.

Verified end-to-end against a fresh Supabase project: ran every command
in the docs literally, including the full SQL from
`quickstart_db_setup.mdx` (with the `grant select on public.instruments
to anon` line). The rendered Reflex app shows the Instruments heading
with all three seeded rows. No console errors, no event-loop warnings.

## Additional context

Conventions used in the quickstart:

- Uses `uv` (`uv init`, `uv add`, `uv run`) rather than pip + venv. This
follows the broader docs pattern of each quickstart using its
framework's idiomatic tooling (Next.js → npx, RedwoodJS → yarn, Laravel
→ composer, Flutter → pubspec.yaml). It also matches what Reflex's own
`reflex init` post-install message recommends. `uv add` produces a
`pyproject.toml` and `uv.lock` so users can rebuild deterministically
with `uv sync`.
- The Supabase client is constructed via `acreate_client` with a
lazy-init pattern, and the event handler is `async def`. This avoids
blocking Reflex's event loop on the HTTP request.

In touch with the Supabase team on this — happy to iterate on copy or
scope based on review.

Pre-flight: ran pnpm run format locally, which passed cleanly. Did not
run pnpm run build locally — hit a Windows/CRLF-related TOML parsing
failure in an unrelated troubleshooting frontmatter file
(apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx)
during page data collection. Relying on Vercel preview to validate the
build.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added Reflex (Python) and Spring Boot to the framework quickstarts.
* Reorganized quickstart listings for clearer framework navigation and
grouping.

* **Documentation**
* Added a Reflex quickstart guide covering Supabase setup, environment
variables, asynchronous data loading, error handling, and running the
app.
  * Added Reflex-specific AI guidance for configuring Supabase projects.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Nik Richers <nik@validmind.ai>
This commit is contained in:
Palash AwasthiandNik Richers authored and GitHub committed 2026-09-09 23:35:24 +00:00
1 parent 976e7338bc
commit 165582b08e
10 files changed
+292 -84

No files matched your search

@@ -1,11 +1,10 @@
'use client'
import type { ContentListingIcon } from '~/lib/content-listings.schema'
import { Axiom, Datadog, Grafana, Last9, Otlp, Sentry } from 'icons'
import { Braces, Cloud, Server } from 'lucide-react'
import type { ReactNode } from 'react'
import type { ContentListingIcon } from '~/lib/content-listings.schema'
type IconKind = Extract<ContentListingIcon, { kind: string }>['kind']
const ICON_KIND_COMPONENTS: Record<IconKind, ReactNode> = {
@@ -33,5 +32,15 @@ export function resolveContentListingIcon(
</span>
)
}
// GlassPanel picks -light via next-themes; class-based dark: is more reliable for
// Reflex (near-black light mark vanishes on dark cards if theme resolution lags).
if (typeof icon === 'string' && icon.endsWith('/reflex-icon')) {
return (
<>
<img src={`${icon}-light.svg`} alt="" className="w-5 dark:hidden" />
<img src={`${icon}.svg`} alt="" className="w-5 hidden dark:block" />
</>
)
}
return icon
}
@@ -96,6 +96,12 @@ const frameworks = [
icon: '/docs/img/icons/python-icon',
href: '/guides/getting-started/quickstarts/flask',
},
{
name: 'Reflex',
icon: '/docs/img/icons/reflex-icon',
href: '/guides/getting-started/quickstarts/reflex',
hasLightIcon: true,
},
{
name: 'Laravel',
icon: '/docs/img/icons/laravel-icon',
@@ -106,6 +112,11 @@ const frameworks = [
icon: '/docs/img/icons/rails-icon',
href: '/guides/getting-started/quickstarts/ruby-on-rails',
},
{
name: 'Spring Boot',
icon: '/docs/img/icons/spring-boot-icon',
href: '/guides/getting-started/quickstarts/spring-boot',
},
]
export function FrameworkQuickstarts({ labelledBy }: { labelledBy?: string }) {
@@ -366,38 +366,50 @@ export const gettingstarted: NavMenuConstant = {
name: 'Framework Quickstarts',
enabled: frameworkQuickstartsEnabled,
items: [
{
name: 'React',
url: '/guides/getting-started/quickstarts/reactjs',
},
{
name: 'Next.js',
url: '/guides/getting-started/quickstarts/nextjs',
},
{
name: 'React',
url: '/guides/getting-started/quickstarts/reactjs',
name: 'TanStack Start',
url: '/guides/getting-started/quickstarts/tanstack' as `/${string}`,
},
{
name: 'Astro',
url: '/guides/getting-started/quickstarts/astrojs',
},
{
name: 'Nuxt',
url: '/guides/getting-started/quickstarts/nuxtjs',
},
{
name: 'Vue',
url: '/guides/getting-started/quickstarts/vue',
},
{
name: 'Nuxt',
url: '/guides/getting-started/quickstarts/nuxtjs',
},
{
name: 'SvelteKit',
url: '/guides/getting-started/quickstarts/sveltekit' as `/${string}`,
},
{
name: 'SolidJS',
url: '/guides/getting-started/quickstarts/solidjs',
},
{
name: 'RedwoodJS',
url: '/guides/getting-started/quickstarts/redwoodjs' as `/${string}`,
},
{
name: 'Refine',
url: '/guides/getting-started/quickstarts/refine',
},
{
name: 'Hono',
url: '/guides/getting-started/quickstarts/hono',
},
{
name: 'Expo React Native',
url: '/guides/getting-started/quickstarts/expo-react-native',
},
{
name: 'Flutter',
url: '/guides/getting-started/quickstarts/flutter',
},
{
name: 'iOS SwiftUI',
url: '/guides/getting-started/quickstarts/ios-swiftui',
@@ -407,8 +419,12 @@ export const gettingstarted: NavMenuConstant = {
url: '/guides/getting-started/quickstarts/kotlin' as `/${string}`,
},
{
name: 'SvelteKit',
url: '/guides/getting-started/quickstarts/sveltekit' as `/${string}`,
name: 'Expo React Native',
url: '/guides/getting-started/quickstarts/expo-react-native',
},
{
name: 'Flutter',
url: '/guides/getting-started/quickstarts/flutter',
},
{
name: 'Flask (Python)',
@@ -416,8 +432,9 @@ export const gettingstarted: NavMenuConstant = {
enabled: !jsOnly,
},
{
name: 'TanStack Start',
url: '/guides/getting-started/quickstarts/tanstack' as `/${string}`,
name: 'Reflex (Python)',
url: '/guides/getting-started/quickstarts/reflex' as `/${string}`,
enabled: !jsOnly,
},
{
name: 'Laravel PHP',
@@ -434,18 +451,6 @@ export const gettingstarted: NavMenuConstant = {
url: '/guides/getting-started/quickstarts/spring-boot' as `/${string}`,
enabled: !jsOnly,
},
{
name: 'SolidJS',
url: '/guides/getting-started/quickstarts/solidjs',
},
{
name: 'RedwoodJS',
url: '/guides/getting-started/quickstarts/redwoodjs' as `/${string}`,
},
{
name: 'Refine',
url: '/guides/getting-started/quickstarts/refine',
},
],
},
{
@@ -0,0 +1,130 @@
---
title: 'Use Supabase with Reflex'
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Reflex app.'
breadcrumb: 'Framework Quickstarts'
---
<AiPrompt id="reflex" />
<$Partial path="quickstart_db_setup.mdx" />
## 3. Create a Reflex app
Create a new directory for your Reflex app, initialize a project with `uv`, add Reflex as a dependency, and scaffold a blank app with `--template blank`.
```bash
mkdir my_app && cd my_app
uv init
uv add reflex
uv run reflex init --template blank
```
## 4. Set up AI tooling (optional)
<$Partial path="quickstart_ai_tooling.mdx" />
## 5. Install the Supabase client library
The fastest way to get started is to use the `supabase-py` client library, which provides a convenient interface for working with Supabase from a Reflex app.
Install `supabase-py` and `python-dotenv` to load environment variables.
```bash
uv add supabase python-dotenv
```
## 6. Declare Supabase environment variables
Create a `.env` file in your project root and populate it with your Supabase connection variables that you can get from the helper below, or [from the project **Connect** panel](/dashboard/project/_?showConnect=true&connectTab=frameworks):
<Button variant="primary" asChild>
<a href="/dashboard/project/_?showConnect=true&connectTab=frameworks">Open Connect panel</a>
</Button>
```text name=.env
SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
SUPABASE_PUBLISHABLE_KEY=<SUBSTITUTE_SUPABASE_PUBLISHABLE_KEY>
```
<$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />
## 7. Create the Supabase client
Open `my_app/my_app.py` and add the following code to create an async Supabase client from your environment variables. Reflex's event handlers run inside an async event loop, so the client is created once with `acreate_client` and reused across requests, rather than recreated per page load.
```python name=my_app/my_app.py
import os
import reflex as rx
from supabase import acreate_client, AsyncClient
from dotenv import load_dotenv
load_dotenv()
_supabase: AsyncClient | None = None
async def get_supabase() -> AsyncClient:
global _supabase
if _supabase is None:
_supabase = await acreate_client(
os.environ.get("SUPABASE_URL"),
os.environ.get("SUPABASE_PUBLISHABLE_KEY"),
)
return _supabase
```
## 8. Query data from the app
Add a `State` class that holds the query result, an async event handler that fetches data from your `instruments` table, and a page that renders the result. `on_load` triggers the handler when the page is opened.
```python name=my_app/my_app.py
from postgrest import APIError
class State(rx.State):
instruments: list[dict] = []
error: str = ""
async def load_instruments(self):
supabase = await get_supabase()
try:
response = await supabase.table("instruments").select("*").execute()
except APIError as error:
self.error = f"Error loading instruments: {error.message}"
return
self.instruments = response.data
def index() -> rx.Component:
return rx.container(
rx.heading("Instruments"),
rx.cond(
State.error,
rx.text(State.error),
rx.foreach(
State.instruments,
lambda instrument: rx.text(instrument["name"]),
),
),
)
app = rx.App()
app.add_page(index, on_load=State.load_instruments)
```
## 9. Start the app
Run the Reflex development server, go to http://localhost:3000 in a browser, and you should see the list of instruments.
```bash
uv run reflex run
```
<$Partial path="quickstart_going_to_production.mdx" />
## Next steps
- Set up [Auth](/docs/guides/auth) for your app
- [Insert more data](/docs/guides/database/import-data) into your database
- Upload and serve static files using [Storage](/docs/guides/storage)
+14
View File
@@ -179,6 +179,20 @@ database.new and run the instruments table SQL. Then:
REFERENCE
https://supabase.com/docs/guides/getting-started/quickstarts/refine.md`,
reflex: `Help me add Supabase to my Reflex project. Create a Supabase project at
database.new and run the instruments table SQL. Then:
1. Run \`uv init\` and \`uv add reflex\`, then \`uv run reflex init --template blank\`
to scaffold the app.
2. Run \`uv add supabase python-dotenv\`.
3. Create \`.env\` and set \`SUPABASE_URL\` and \`SUPABASE_PUBLISHABLE_KEY\`.
4. In \`my_app/my_app.py\`, create a single async Supabase client with
\`acreate_client\` (one client per process, not recreated per request) and an
\`rx.State\` event handler that queries and renders the instruments table,
handling \`postgrest.APIError\`.
5. Run \`uv run reflex run\` and open http://localhost:3000.
REFERENCE
https://supabase.com/docs/guides/getting-started/quickstarts/reflex.md`,
'ruby-on-rails': `Help me add Supabase to my Ruby on Rails project. Create a Supabase project at
database.new. Then:
1. Run \`rails new blog -d=postgresql\` to scaffold a new Rails project.
@@ -76,12 +76,11 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Full-stack React with server rendering, wired to Supabase Postgres and cookie-based auth.',
},
{
title: 'Nuxt',
href: '/guides/getting-started/quickstarts/nuxtjs',
icon: '/docs/img/icons/nuxt-icon',
hasLightIcon: false,
description:
'Full-stack Vue with server rendering, reading Postgres through a Supabase composable.',
title: 'TanStack Start',
href: '/guides/getting-started/quickstarts/tanstack',
icon: '/docs/img/icons/tanstack-icon',
hasLightIcon: true,
description: 'Type-safe full-stack React that queries Supabase Postgres in server functions.',
},
{
title: 'Astro',
@@ -92,12 +91,35 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Content-driven sites that render on the server and pull Supabase Postgres data per request.',
},
{
title: 'Hono',
href: '/guides/getting-started/quickstarts/hono',
icon: '/docs/img/icons/hono-icon',
title: 'Vue',
href: '/guides/getting-started/quickstarts/vue',
icon: '/docs/img/icons/vuejs-icon',
hasLightIcon: false,
description:
'Lightweight web APIs with Supabase Auth anonymous sign-in and RLS-protected reads.',
'Build single-page apps with the Vue composition API, backed by Supabase Postgres.',
},
{
title: 'Nuxt',
href: '/guides/getting-started/quickstarts/nuxtjs',
icon: '/docs/img/icons/nuxt-icon',
hasLightIcon: false,
description:
'Full-stack Vue with server rendering, reading Postgres through a Supabase composable.',
},
{
title: 'SvelteKit',
href: '/guides/getting-started/quickstarts/sveltekit',
icon: '/docs/img/icons/svelte-icon',
hasLightIcon: false,
description: 'Full-stack Svelte that loads Supabase Postgres data in server load functions.',
},
{
title: 'SolidJS',
href: '/guides/getting-started/quickstarts/solidjs',
icon: '/docs/img/icons/solidjs-icon',
hasLightIcon: false,
description:
'Fine-grained reactive UIs that load Supabase Postgres data with Solid resources.',
},
{
title: 'RedwoodJS',
@@ -108,20 +130,20 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Full-stack React and GraphQL, with Prisma migrations against your Supabase Postgres database.',
},
{
title: 'Expo React Native',
href: '/guides/getting-started/quickstarts/expo-react-native',
icon: '/docs/img/icons/expo-icon',
hasLightIcon: true,
title: 'Refine',
href: '/guides/getting-started/quickstarts/refine',
icon: '/docs/img/icons/refine-icon',
hasLightIcon: false,
description:
'Ship iOS and Android from one React Native codebase, backed by Supabase Postgres.',
'Scaffold CRUD dashboards and admin panels straight from your Supabase Postgres tables.',
},
{
title: 'Flutter',
href: '/guides/getting-started/quickstarts/flutter',
icon: '/docs/img/icons/flutter-icon',
title: 'Hono',
href: '/guides/getting-started/quickstarts/hono',
icon: '/docs/img/icons/hono-icon',
hasLightIcon: false,
feature: 'sdk:dart',
description: 'Ship iOS and Android from one Dart codebase, backed by Supabase Postgres.',
description:
'Lightweight web APIs with Supabase Auth anonymous sign-in and RLS-protected reads.',
},
{
title: 'iOS SwiftUI',
@@ -141,42 +163,20 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
'Native Android apps in Kotlin and Jetpack Compose, using the Supabase Kotlin SDK.',
},
{
title: 'SvelteKit',
href: '/guides/getting-started/quickstarts/sveltekit',
icon: '/docs/img/icons/svelte-icon',
hasLightIcon: false,
description: 'Full-stack Svelte that loads Supabase Postgres data in server load functions.',
},
{
title: 'SolidJS',
href: '/guides/getting-started/quickstarts/solidjs',
icon: '/docs/img/icons/solidjs-icon',
hasLightIcon: false,
description:
'Fine-grained reactive UIs that load Supabase Postgres data with Solid resources.',
},
{
title: 'Vue',
href: '/guides/getting-started/quickstarts/vue',
icon: '/docs/img/icons/vuejs-icon',
hasLightIcon: false,
description:
'Build single-page apps with the Vue composition API, backed by Supabase Postgres.',
},
{
title: 'TanStack Start',
href: '/guides/getting-started/quickstarts/tanstack',
icon: '/docs/img/icons/tanstack-icon',
title: 'Expo React Native',
href: '/guides/getting-started/quickstarts/expo-react-native',
icon: '/docs/img/icons/expo-icon',
hasLightIcon: true,
description: 'Type-safe full-stack React that queries Supabase Postgres in server functions.',
description:
'Ship iOS and Android from one React Native codebase, backed by Supabase Postgres.',
},
{
title: 'Refine',
href: '/guides/getting-started/quickstarts/refine',
icon: '/docs/img/icons/refine-icon',
title: 'Flutter',
href: '/guides/getting-started/quickstarts/flutter',
icon: '/docs/img/icons/flutter-icon',
hasLightIcon: false,
description:
'Scaffold CRUD dashboards and admin panels straight from your Supabase Postgres tables.',
feature: 'sdk:dart',
description: 'Ship iOS and Android from one Dart codebase, backed by Supabase Postgres.',
},
{
title: 'Python',
@@ -185,6 +185,14 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
hasLightIcon: false,
description: 'Serve Flask web apps that query Postgres with the Supabase Python client.',
},
{
title: 'Reflex',
href: '/guides/getting-started/quickstarts/reflex',
icon: '/docs/img/icons/reflex-icon',
hasLightIcon: true,
description:
'Full-stack Python web apps that query Supabase Postgres with the async Python client.',
},
{
title: 'Laravel',
href: '/guides/getting-started/quickstarts/laravel',
@@ -201,6 +209,14 @@ export const gettingStartedFrameworkQuickstarts: ContentListingGroup = {
description:
'Convention-driven Ruby apps with Active Record connected directly to your Supabase Postgres database.',
},
{
title: 'Spring Boot',
href: '/guides/getting-started/quickstarts/spring-boot',
icon: '/docs/img/icons/spring-boot-icon',
hasLightIcon: false,
description:
'Java APIs with Spring Data JPA connected directly to your Supabase Postgres database.',
},
],
}
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<!-- Light-mode mark: official Reflex "R" letterform, padded to a square -->
<g transform="translate(7.5 6)" fill="#110F1F">
<path
d="M0 11.6V.4h8.96v4.48H6.72V2.64H2.24v2.24h4.48v2.24H2.24v4.48H0Zm6.72 0V7.12h2.24v4.48H6.72Z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 335 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
<!-- Dark-mode mark: official Reflex "R" letterform, padded to a square -->
<g transform="translate(7.5 6)" fill="#fff">
<path
d="M0 11.6V.4h8.96v4.48H6.72V2.64H2.24v2.24h4.48v2.24H2.24v4.48H0Zm6.72 0V7.12h2.24v4.48H6.72Z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 331 B

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" fill="#6DB33F">
<!-- Spring Boot mark (Simple Icons path), brand green for light and dark tiles -->
<path
d="m23.693 10.7058-4.73-8.1844c-.4094-.7106-1.4166-1.2942-2.2402-1.2942H7.2725c-.819 0-1.8308.5836-2.2402 1.2942L.307 10.7058c-.4095.7106-.4095 1.873 0 2.5837l4.7252 8.189c.4094.7107 1.4166 1.2943 2.2402 1.2943h9.455c.819 0 1.826-.5836 2.2402-1.2942l4.7252-8.189c.4095-.7107.4095-1.8732 0-2.5838zM10.9763 5.7547c0-.5365.4377-.9742.9742-.9742s.9742.4377.9742.9742v5.8217c0 .5366-.4377.9742-.9742.9742s-.9742-.4376-.9742-.9742zm.9742 12.4294c-3.6427 0-6.6077-2.965-6.6077-6.6077.0047-2.0896.993-4.0521 2.6685-5.304a.8657.8657 0 0 1 1.2142.1788.8657.8657 0 0 1-.1788 1.2143c-2.1602 1.6048-2.612 4.6592-1.0072 6.8194 1.6049 2.1603 4.6593 2.612 6.8195 1.0072 1.2378-.9177 1.9673-2.372 1.9673-3.9157a4.8972 4.8972 0 0 0-1.9861-3.925c-.386-.2824-.466-.8284-.1836-1.2143.2824-.386.8283-.466 1.2143-.1835 1.6895 1.2471 2.6826 3.2238 2.6873 5.3228 0 3.6474-2.965 6.6077-6.6077 6.6077z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -197,6 +197,7 @@ may_uppercase = [
"Query Performance",
"Rails",
"React",
"Reflex",
"Rollup",
"React Email",
"React Native",