diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml
index 8ed1db87295..84c8b8a88c2 100644
--- a/.github/workflows/publish_image.yml
+++ b/.github/workflows/publish_image.yml
@@ -2,6 +2,8 @@ name: Publish to Image Registry
on:
push:
+ branches:
+ - 'studio'
tags:
- '*'
workflow_dispatch:
@@ -25,6 +27,7 @@ jobs:
latest=false
tags: |
type=ref,event=tag
+ type=sha,prefix={{date 'YYYYMMDD'}},enable=${{ github.ref_type == 'branch' }}
type=raw,value=${{ inputs.version }},enable=${{ github.event_name != 'push' }}
- uses: docker/setup-qemu-action@v2
diff --git a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx
index 4e8ffdd87dc..b509792f914 100644
--- a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx
+++ b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx
@@ -70,6 +70,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
## Basic Setup
+```jsx title="app/root.jsx"
+export const loader = async ({ request }) => {
+ // Environment variables may be stored somewhere other than `process.env` in runtimes other than node.
+ // We need to pipe these Supabase environment variables to the browser.
+ const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
+
+ // We can retrieve the session on the server and hand it to the client.
+ // This is used to make sure the session is available immediately upon rendering.
+ const response = new Response()
+ const supabase = createServerClient(
+ process.env.SUPABASE_URL!,
+ process.env.SUPABASE_ANON_KEY!,
+ { request, response }
+ )
+ const {
+ data: { session: initialSession },
+ } = await supabase.auth.getSession()
+
+ // In order for the set-cookie header to be set, headers must be returned as part of the loader response.
+ return json(
+ {
+ initialSession,
+ env: {
+ SUPABASE_URL,
+ SUPABASE_ANON_KEY,
+ },
+ },
+ {
+ headers: response.headers,
+ }
+ )
+}
+```
+
+
+
+
+```tsx title="app/root.tsx"
+export const loader: LoaderFunction = async ({ request }) => {
+ // Environment variables may be stored somewhere other than `process.env` in runtimes other than node.
+ // We need to pipe these Supabase environment variables to the browser.
+ const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
+
+ // We can retrieve the session on the server and hand it to the client.
+ // This is used to make sure the session is available immediately upon rendering.
+ const response = new Response()
+ const supabase = createServerClient(
+ process.env.SUPABASE_URL!,
+ process.env.SUPABASE_ANON_KEY!,
+ { request, response }
+ )
+ const {
+ data: { session: initialSession },
+ } = await supabase.auth.getSession()
+
+ // In order for the set-cookie header to be set, headers must be returned as part of the loader response.
+ return json(
+ {
+ initialSession,
+ env: {
+ SUPABASE_URL,
+ SUPABASE_ANON_KEY,
+ },
+ },
+ {
+ headers: response.headers,
+ }
+ )
+}
+```
+
+
+
+
+Next, upon the initial client-side rendering of your application, set up a browser client and listen to session changes and expose both throughout your routes via the outlet context:
+
+
+
+
+```jsx title="app/root.jsx"
+import { useEffect, useState } from 'react'
+import { json } from '@remix-run/node'
+import { Outlet, useLoaderData } from '@remix-run/react'
+import {
+ createServerClient,
+ createBrowserClient,
+} from '@supabase/auth-helpers-remix'
+
+export const loader: LoaderFunction = async ({ request }) => {
+ // ... see loader function above.
+}
+
+export default function App() {
+ const { env, initialSession } = useLoaderData()
+ const [supabase, setSupabase] = useState(null)
+ const [session, setSession] = useState(initialSession)
+
+ const context = { supabase, session }
+
+ useEffect(() => {
+ if (!supabase) {
+ const supabase = createBrowserClient(
+ env.SUPABASE_URL,
+ env.SUPABASE_ANON_KEY
+ )
+ setSupabase(supabase)
+ const {
+ data: { subscription },
+ } = supabase.auth.onAuthStateChange((_, session) => setSession(session))
+ return () => {
+ subscription.unsubscribe()
+ }
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+ )
+}
+```
+
+
+
+
+```tsx title="app/root.tsx"
+import { useEffect, useState } from 'react'
+import { json, LoaderFunction } from '@remix-run/node'
+import { Outlet, useLoaderData } from '@remix-run/react'
+import {
+ createServerClient,
+ createBrowserClient,
+ supabase,
+ Session,
+} from '@supabase/auth-helpers-remix'
+import { Database } from '../db_types'
+
+export type ContextType = {
+ supabase: supabase | null
+ session: Session | null
+}
+
+type LoaderData = {
+ env: { SUPABASE_URL: string; SUPABASE_ANON_KEY: string }
+ initialSession: Session | null
+}
+
+export const loader: LoaderFunction = async ({ request }) => {
+ // ... see loader function above.
+}
+
+export default function App() {
+ const { env, initialSession } = useLoaderData()
+ const [supabase, setSupabase] = useState(null)
+ const [session, setSession] = useState(initialSession)
+
+ const context: ContextType = { supabase, session }
+
+ useEffect(() => {
+ if (!supabase) {
+ const supabase = createBrowserClient(
+ env.SUPABASE_URL,
+ env.SUPABASE_ANON_KEY
+ )
+ setSupabase(supabase)
+ const {
+ data: { subscription },
+ } = supabase.auth.onAuthStateChange((_, session) => setSession(session))
+ return () => {
+ subscription.unsubscribe()
+ }
+ }
+ }, [])
+
+ return (
+
+
+
+
+
+ )
+}
+```
+
+
+
+
+Now the supabase client and session are available throughout the application via the `useOutletContext`:
+
+
+
+
+```jsx title="app/routes/index.jsx"
+import { useOutletContext } from '@remix-run/react'
+
+export default function Index() {
+ const { supabase, session } = useOutletContext()
+
+ return
{JSON.stringify({ session }, null, 2)}
+}
+```
+
+
+
+
+```tsx title="app/routes/index.tsx"
+import { useOutletContext } from '@remix-run/react'
+import type { ContextType } from '../root'
+
+export default function Index() {
+ const { supabase, session } = useOutletContext()
+
+ return
{JSON.stringify({ session }, null, 2)}
+}
+```
+
+
+
+
+You can find a full working example in the [source code on GitHub](https://github.com/supabase/auth-helpers/tree/main/examples/remix).
+
+## Usage with TypeScript
+
+You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the `createServerClient` or `createBrowserClient` functions to get enhanced type safety and auto completion:
+
+### Server-side
+
+```tsx
+import { createServerClient } from '@supabase/auth-helpers-remix'
+import { Database } from '../../db_types'
+
+export const loader = async ({ request }) => {
+ const response = new Response()
+
+ const supabase = createServerClient(
+ process.env.SUPABASE_URL,
+ process.env.SUPABASE_ANON_KEY,
+ { request, response }
+ )
+}
+```
+
+### Client-side
+
+```tsx
+import { createBrowserClient } from '@supabase/auth-helpers-remix'
+import { Database } from '../../db_types'
+
+const supabase = createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY)
+```
+
+## Loader
+
Loader functions run on the server immediately before the component is rendered. They respond to all GET requests on a route. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
+
+
+
```jsx
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
@@ -74,13 +354,13 @@ export const loader = async ({ request }) => {
// an empty response is required for the auth helpers
// to set cookies to manage auth
- const supabaseClient = createServerClient(
+ const supabase = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
- const { data } = await supabaseClient.from('test').select('*')
+ const { data } = await supabase.from('test').select('*')
// in order for the set-cookie header to be set,
// headers must be returned as part of the loader response
@@ -93,30 +373,22 @@ export const loader = async ({ request }) => {
}
```
-> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
-
-Loader functions run on the server immediately before the component is rendered. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
-
-```jsx
+```tsx
import { LoaderFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
-export const loader: LoaderFunction = async ({
- request,
-}: {
- request: Request,
-}) => {
+export const loader: LoaderFunction = async ({ request }) => {
const response = new Response()
- const supabaseClient = createServerClient(
+ const supabase = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
- const { data } = await supabaseClient.from('test').select('*')
+ const { data } = await supabase.from('test').select('*')
return json(
{ data },
@@ -127,14 +399,17 @@ export const loader: LoaderFunction = async ({
}
```
-> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
-
+> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function.
+
## Action
+Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
+
-Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
-
```jsx
import { json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
@@ -151,13 +424,13 @@ import { createServerClient } from '@supabase/auth-helpers-remix'
export const action = async ({ request }) => {
const response = new Response()
- const supabaseClient = createServerClient(
+ const supabase = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
- const { data } = await supabaseClient.from('test').select('*')
+ const { data } = await supabase.from('test').select('*')
return json(
{ data },
@@ -168,31 +441,23 @@ export const action = async ({ request }) => {
}
```
-> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
-
-Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`.
-
-```jsx
+```tsx
import { ActionFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using
import { createServerClient } from '@supabase/auth-helpers-remix'
-export const action: ActionFunction = async ({
- request,
-}: {
- request: Request,
-}) => {
+export const action: ActionFunction = async ({ request }) => {
const response = new Response()
- const supabaseClient = createServerClient(
+ const supabase = createServerClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY,
{ request, response }
)
- const { data } = await supabaseClient.from('test').select('*')
+ const { data } = await supabase.from('test').select('*')
return json(
{ data },
@@ -203,11 +468,11 @@ export const action: ActionFunction = async ({
}
```
-> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
-
+> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function.
+
## Session and User
You can determine if a user is authenticated by checking their session using the `getSession` function.
@@ -215,7 +480,7 @@ You can determine if a user is authenticated by checking their session using the
```jsx
const {
data: { session },
-} = await supabaseClient.auth.getSession()
+} = await supabase.auth.getSession()
```
The session contains a user property.
@@ -224,681 +489,418 @@ The session contains a user property.
const user = session?.user
```
-> This is the recommended way for accessing the logged in user. There is also a `getUser()` function but this does not refresh the session if it has expired.
+## Authentication UI
-## Client-side
+### Pre-built Auth UI
+
+We recommend using our [pre-built Auth UI](/docs/guides/auth/auth-helpers/auth-ui) for authenticating users. It supports custom themes and extensible styles to match your brand and aesthetic.
+
+### Custom Auth UI
+
+Should you prefer to build your own Auth UI, you can use our [JavaScript Library](/docs/reference/javascript/) to interact with Supabase Auth from the client-side.
+ ]}>
-In order to use the Supabase client in the browser - fetching data in `useEffect` or subscribing to realtime events - we need to do a little more plumbing. Remix does not include a way to make environment variables available to the browser, so we need to pipe them through from a `loader` function in our `root.jsx` route and attach them to the `window`.
+```jsx title="app/routes/signin.jsx"
+import { useOutletContext } from '@remix-run/react'
-```jsx title="app/root.jsx"
-export const loader = () => {
- const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env
- return json({
- env: {
- SUPABASE_URL,
- SUPABASE_ANON_KEY,
- },
- })
-}
-```
+export default function Auth() {
+ const { supabase, session } = useOutletContext()
-> These may not be stored in `process.env` for environments other than Node.
+ const handleSubmit = async (event) => {
+ event.preventDefault()
+ if (!supabase) return
+ const formData = new FormData(event.currentTarget)
+ const email = formData.get('loginEmail')
+ const password = formData.get('loginPassword')
+ // Sign in
+ const { error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ })
+ if (error) alert(error.message)
+ }
-Next, we call the `useLoaderData` hook in our component to get the `env` object.
-
-```jsx title="app/root.jsx"
-const { env } = useLoaderData()
-```
-
-And then, add a `
-