diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml index 9d5aa59959a..7baa26f4981 100644 --- a/.github/workflows/search.yml +++ b/.github/workflows/search.yml @@ -45,6 +45,7 @@ jobs: sparse-checkout: | apps/docs apps/www/.env.local.example + examples supabase - uses: pnpm/action-setup@v4 @@ -63,7 +64,9 @@ jobs: - name: Update embeddings working-directory: ./apps/docs if: ${{ !inputs.refresh }} - run: pnpm run embeddings + run: | + pnpm run codegen:examples + pnpm run embeddings - name: Refresh embeddings working-directory: ./apps/docs diff --git a/DEVELOPERS.md b/DEVELOPERS.md index c41044ceb71..ef6e298c9ae 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -203,7 +203,7 @@ Some things to consider: ## Community channels -If you get 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! +If you get 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! ## Contributors diff --git a/apps/design-system/styles/globals.css b/apps/design-system/styles/globals.css index 2f2e162c7a2..0b8e85d7e22 100644 --- a/apps/design-system/styles/globals.css +++ b/apps/design-system/styles/globals.css @@ -1,12 +1,12 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; @import './../../../packages/ui/build/css/themes/classic-dark.css'; @import './../../../packages/ui/build/css/themes/light.css'; +@tailwind base; +@tailwind components; +@tailwind utilities; + @layer base { :root { --chart-1: 12 76% 61%; diff --git a/apps/docs/app/api/graphql/tests/searchDocs.test.ts b/apps/docs/app/api/graphql/tests/searchDocs.test.ts index 2f1f9524a92..92b9012a862 100644 --- a/apps/docs/app/api/graphql/tests/searchDocs.test.ts +++ b/apps/docs/app/api/graphql/tests/searchDocs.test.ts @@ -4,7 +4,9 @@ import { type OpenAIClientInterface } from '~/lib/openAi' import { ApiError } from '../../utils' import { POST } from '../route' -const contentEmbeddingMock = vi.fn().mockImplementation(async () => Result.ok([0.1, 0.2, 0.3])) +const contentEmbeddingMock = vi + .fn() + .mockImplementation(async () => Result.ok({ embedding: [0.1, 0.2, 0.3], tokenCount: 10 })) const openAIMock: OpenAIClientInterface = { createContentEmbedding: contentEmbeddingMock, } diff --git a/apps/docs/app/api/utils.ts b/apps/docs/app/api/utils.ts index 652ea444e59..ffdfc4f4985 100644 --- a/apps/docs/app/api/utils.ts +++ b/apps/docs/app/api/utils.ts @@ -64,6 +64,16 @@ export class NoDataError
extends ApiError } } +export class FileNotFoundError
extends Error { + constructor( + message: string, + error: Error, + public details?: Details + ) { + super(`FileNotFound: ${message}`, { cause: error }) + } +} + export class MultiError extends Error { constructor( message: string, @@ -79,7 +89,7 @@ export class MultiError).push(error) + ;((this.cause ??= []) as Array).push(error) return this } } diff --git a/apps/docs/content/_partials/oauth_pkce_flow.mdx b/apps/docs/content/_partials/oauth_pkce_flow.mdx index c98eacb6336..f1035c08937 100644 --- a/apps/docs/content/_partials/oauth_pkce_flow.mdx +++ b/apps/docs/content/_partials/oauth_pkce_flow.mdx @@ -150,7 +150,7 @@ export const GET: APIRoute = async ({ request, cookies, redirect }) => { if (code) { const supabase = createServerClient( import.meta.env.PUBLIC_SUPABASE_URL, - import.meta.env.PUBLIC_SUPABASE_ANON_KEY, + import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY, { cookies: { getAll() { @@ -194,18 +194,22 @@ export async function loader({ request }: LoaderFunctionArgs) { const headers = new Headers() if (code) { - const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { - cookies: { - getAll() { - return parseCookieHeader(request.headers.get('Cookie') ?? '') + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_PUBLISHABLE_KEY!, + { + cookies: { + getAll() { + return parseCookieHeader(request.headers.get('Cookie') ?? '') + }, + setAll(cookiesToSet) { + cookiesToSet.forEach(({ name, value, options }) => + headers.append('Set-Cookie', serializeCookieHeader(name, value, options)) + ) + }, }, - setAll(cookiesToSet) { - cookiesToSet.forEach(({ name, value, options }) => - headers.append('Set-Cookie', serializeCookieHeader(name, value, options)) - ) - }, - }, - }) + } + ) const { error } = await supabase.auth.exchangeCodeForSession(code) @@ -233,7 +237,7 @@ app.get("/auth/callback", async function (req, res) { if (code) { const supabase = createServerClient( process.env.SUPABASE_URL, - process.env.SUPABASE_ANON_KEY, { + process.env.SUPABASE_PUBLISHABLE_KEY, { cookies: { getAll() { return parseCookieHeader(context.req.headers.cookie ?? '') diff --git a/apps/docs/content/guides/ai/examples/nextjs-vector-search.mdx b/apps/docs/content/guides/ai/examples/nextjs-vector-search.mdx index 47d201dccf4..dc0d3b8fc74 100644 --- a/apps/docs/content/guides/ai/examples/nextjs-vector-search.mdx +++ b/apps/docs/content/guides/ai/examples/nextjs-vector-search.mdx @@ -283,7 +283,7 @@ With our database set up, we need to process and store all `.mdx` files in the ` ```bash NEXT_PUBLIC_SUPABASE_URL= - NEXT_PUBLIC_SUPABASE_ANON_KEY= + NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY= SUPABASE_SERVICE_ROLE_KEY= # Get your key at https://platform.openai.com/account/api-keys @@ -470,8 +470,8 @@ const handleConfirm = React.useCallback( const eventSource = new SSE(`api/vector-search`, { headers: { - apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? '', - Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, + apikey: process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY ?? '', + Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY}`, 'Content-Type': 'application/json', }, payload: JSON.stringify({ query }), diff --git a/apps/docs/content/guides/api/api-keys.mdx b/apps/docs/content/guides/api/api-keys.mdx index 76b76f0953c..2fc13803347 100644 --- a/apps/docs/content/guides/api/api-keys.mdx +++ b/apps/docs/content/guides/api/api-keys.mdx @@ -136,7 +136,7 @@ Rotating a secret key (`sb_secret_...`) is easy and painless. Use the [API Keys] If you are still using the JWT-based `service_role` key, there are two options. 1. **Strongly recommended:** Replace the `service_role` key with a new secret key instead. Follow the guide from above as if you are rotating an existing secret key. -2. [Rotate your project's JWT secret.](/dashboard/project/_/settings/api) This operation is only recommended if you suspect that the JWT secret has leaked itself. Consider switching your `anon` JWT-based key to the publishable key, and all `service_role` JWT-based keys to secret keys. Only then rotate the JWT secret. Check the FAQ below if you use the JWT-based keys in mobile, desktop or CLI applications! +2. [Rotate your project's JWT secret.](/dashboard/project/_/settings/jwt) This operation is only recommended if you suspect that the JWT secret has leaked itself. Consider switching your `anon` JWT-based key to the publishable key, and all `service_role` JWT-based keys to secret keys. Only then rotate the JWT secret. Check the FAQ below if you use the JWT-based keys in mobile, desktop or CLI applications! ## Known limitations and compatibility differences diff --git a/apps/docs/content/guides/api/automatic-retries-in-supabase-js.mdx b/apps/docs/content/guides/api/automatic-retries-in-supabase-js.mdx index e9cfbe80eaf..094f4b25a40 100644 --- a/apps/docs/content/guides/api/automatic-retries-in-supabase-js.mdx +++ b/apps/docs/content/guides/api/automatic-retries-in-supabase-js.mdx @@ -32,11 +32,15 @@ import fetchRetry from 'fetch-retry' const fetchWithRetry = fetchRetry(fetch) // Create a Supabase client instance with the custom fetch -const supabase = createClient('https://your-supabase-url.supabase.co', 'your-anon-key', { - global: { - fetch: fetchWithRetry, - }, -}) +const supabase = createClient( + 'https://your-supabase-url.supabase.co', + 'sb_publishable_... or anon key', + { + global: { + fetch: fetchWithRetry, + }, + } +) ``` ## 3. Configure retry options diff --git a/apps/docs/content/guides/api/creating-routes.mdx b/apps/docs/content/guides/api/creating-routes.mdx index 64c44884024..6ef1b30c0df 100644 --- a/apps/docs/content/guides/api/creating-routes.mdx +++ b/apps/docs/content/guides/api/creating-routes.mdx @@ -72,7 +72,7 @@ Both of these routes require the `anon` key to be passed through an `apikey` hea You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide. Let's see how to make a request to the `todos` table which we created in the first step, -using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_ANON_KEY`) we provided: +using the API URL (`SUPABASE_URL`) and Key (`SUPABASE_PUBLISHABLE_KEY`) we provided: /rest/v1/todos' \ --H "apikey: " \ --H "Authorization: Bearer " +-H "apikey: " \ +-H "Authorization: Bearer " ``` diff --git a/apps/docs/content/guides/api/rest/generating-types.mdx b/apps/docs/content/guides/api/rest/generating-types.mdx index 7c5b6d288e9..d74f2ce603b 100644 --- a/apps/docs/content/guides/api/rest/generating-types.mdx +++ b/apps/docs/content/guides/api/rest/generating-types.mdx @@ -95,7 +95,10 @@ You can supply the type definitions to `supabase-js` like so: import { createClient } from '@supabase/supabase-js' import { Database } from './database.types' -const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY) +const supabase = createClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_PUBLISHABLE_KEY +) ``` ## Helper types for tables and joins diff --git a/apps/docs/content/guides/api/using-custom-schemas.mdx b/apps/docs/content/guides/api/using-custom-schemas.mdx index 929fd31123b..5f9fe4770d5 100644 --- a/apps/docs/content/guides/api/using-custom-schemas.mdx +++ b/apps/docs/content/guides/api/using-custom-schemas.mdx @@ -45,7 +45,9 @@ Now you can access these schemas from data APIs: ```js // Initialize the JS client import { createClient } from '@supabase/supabase-js' -const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { db: { schema: 'myschema' } }) +const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { + db: { schema: 'myschema' }, +}) // Make a request const { data: todos, error } = await supabase.from('todos').select('*') @@ -81,14 +83,14 @@ final data = await supabase.schema('myschema').from('todos').select(); # for GET or HEAD request use Accept-Profile curl '/rest/v1/todos' \ - -H "apikey: " \ - -H "Authorization: Bearer " \ + -H "apikey: " \ + -H "Authorization: Bearer " \ -H "Accept-Profile: myschema" # for POST, PATCH, PUT and DELETE Request use Content-Profile curl -X POST '/rest/v1/todos' \ - -H "apikey: " \ - -H "Authorization: Bearer " \ + -H "apikey: " \ + -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -H "Content-Profile: myschema" \ -d '{"column_name": "value"}' diff --git a/apps/docs/content/guides/auth/auth-anonymous.mdx b/apps/docs/content/guides/auth/auth-anonymous.mdx index 0a4f0b43c2e..30b6af58592 100644 --- a/apps/docs/content/guides/auth/auth-anonymous.mdx +++ b/apps/docs/content/guides/auth/auth-anonymous.mdx @@ -57,7 +57,7 @@ Call the [`signInAnonymously()`](/docs/reference/javascript/auth-signinanonymous ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://your-project.supabase.co', 'your-anon-key') +const supabase = createClient('https://your-project.supabase.co', 'sb_publishable_... or anon key') // ---cut--- const { data, error } = await supabase.auth.signInAnonymously() @@ -121,7 +121,7 @@ You can use the [`updateUser()`](/docs/reference/javascript/auth-updateuser) met ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://your-project.supabase.co', 'your-anon-key') +const supabase = createClient('https://your-project.supabase.co', 'sb_publishable_... or anon key') // ---cut--- const { data: updateEmailData, error: updateEmailError } = await supabase.auth.updateUser({ @@ -205,7 +205,7 @@ You can use the [`linkIdentity()`](/docs/reference/javascript/auth-linkidentity) ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://your-project.supabase.co', 'your-anon-key') +const supabase = createClient('https://your-project.supabase.co', 'sb_publishable_... or anon key') // ---cut--- const { data, error } = await supabase.auth.linkIdentity({ provider: 'google' }) diff --git a/apps/docs/content/guides/auth/auth-email-templates.mdx b/apps/docs/content/guides/auth/auth-email-templates.mdx index bda140dfe59..8f23b52a284 100644 --- a/apps/docs/content/guides/auth/auth-email-templates.mdx +++ b/apps/docs/content/guides/auth/auth-email-templates.mdx @@ -107,7 +107,10 @@ When the user clicks on the link, the request will hit `https://api.example.com/ ```ts import { createClient, type EmailOtpType } from '@supabase/supabase-js' -const supabase = createClient('https://your-project-id.supabase.co', 'your-anon-key') +const supabase = createClient( + 'https://your-project-id.supabase.co', + 'sb_publishable_... or anon key' +) // ---cut--- const { token_hash, type } = Object.fromEntries(new URLSearchParams(window.location.search)) diff --git a/apps/docs/content/guides/auth/auth-helpers/flutter-auth-ui.mdx b/apps/docs/content/guides/auth/auth-helpers/flutter-auth-ui.mdx index 73507acfc71..c0e25cb4fd0 100644 --- a/apps/docs/content/guides/auth/auth-helpers/flutter-auth-ui.mdx +++ b/apps/docs/content/guides/auth/auth-helpers/flutter-auth-ui.mdx @@ -27,7 +27,7 @@ import 'package:supabase_auth_ui/supabase_auth_ui.dart'; void main() async { await Supabase.initialize( url: dotenv.get('SUPABASE_URL'), - anonKey: dotenv.get('SUPABASE_ANON_KEY'), + anonKey: dotenv.get('SUPABASE_PUBLISHABLE_KEY'), ); runApp(const MyApp()); diff --git a/apps/docs/content/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/content/guides/auth/auth-helpers/nextjs-pages.mdx index 9af561e396d..7e276930bb8 100644 --- a/apps/docs/content/guides/auth/auth-helpers/nextjs-pages.mdx +++ b/apps/docs/content/guides/auth/auth-helpers/nextjs-pages.mdx @@ -57,7 +57,7 @@ Retrieve your project URL and anon key in your project's [API settings](https:// ```bash .env.local NEXT_PUBLIC_SUPABASE_URL=your-supabase-url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key ``` ## Basic setup diff --git a/apps/docs/content/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/content/guides/auth/auth-helpers/nextjs.mdx index 547cdfc1ee6..af8e4bf0bd2 100644 --- a/apps/docs/content/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/content/guides/auth/auth-helpers/nextjs.mdx @@ -55,7 +55,7 @@ Retrieve your project's URL and anon key from your [API settings](https://supaba ```bash .env.local NEXT_PUBLIC_SUPABASE_URL=your-supabase-url -NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key ``` ## Managing session with middleware @@ -1172,7 +1172,7 @@ import { createClient } from '@supabase/supabase-js' export default async function Page() { const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY ) const { data } = await supabase.from('todos').select() @@ -1192,7 +1192,7 @@ import type { Database } from '@/lib/database.types' export default async function Page() { const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! ) const { data } = await supabase.from('todos').select() @@ -1229,7 +1229,7 @@ export async function POST(request) { const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY ) const { data } = await supabase.from('todos').insert({ title }).select() @@ -1252,7 +1252,7 @@ export async function POST(request: Request) { const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! ) const { data } = await supabase.from('todos').insert({ title }).select() diff --git a/apps/docs/content/guides/auth/auth-helpers/remix.mdx b/apps/docs/content/guides/auth/auth-helpers/remix.mdx index da1299d328a..629d75f67b0 100644 --- a/apps/docs/content/guides/auth/auth-helpers/remix.mdx +++ b/apps/docs/content/guides/auth/auth-helpers/remix.mdx @@ -59,7 +59,7 @@ Retrieve your project URL and anon key in your project's [API settings](https:// ```bash .env SUPABASE_URL=YOUR_SUPABASE_URL -SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY ``` ### Code Exchange route @@ -89,7 +89,7 @@ export const loader = async ({ request }) => { if (code) { const supabaseClient = createServerClient( process.env.SUPABASE_URL, - process.env.SUPABASE_ANON_KEY, + process.env.SUPABASE_PUBLISHABLE_KEY, { request, response } ) await supabaseClient.auth.exchangeCodeForSession(code) @@ -122,7 +122,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { if (code) { const supabaseClient = createServerClient( process.env.SUPABASE_URL!, - process.env.SUPABASE_ANON_KEY!, + process.env.SUPABASE_PUBLISHABLE_KEY!, { request, response } ) await supabaseClient.auth.exchangeCodeForSession(code) @@ -145,7 +145,7 @@ The Supabase client can now be used server-side - in loaders and actions - by ca ### 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`. +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_PUBLISHABLE_KEY`, and a `Request` and `Response`. { const supabaseClient = createServerClient( process.env.SUPABASE_URL, - process.env.SUPABASE_ANON_KEY, + process.env.SUPABASE_PUBLISHABLE_KEY, { request, response } ) @@ -203,7 +203,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const response = new Response() const supabaseClient = createServerClient( process.env.SUPABASE_URL!, - process.env.SUPABASE_ANON_KEY!, + process.env.SUPABASE_PUBLISHABLE_KEY!, { request, response } ) @@ -229,7 +229,7 @@ Supabase will set cookie headers to manage the user's auth session, therefore, t ### 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_PUBLISHABLE_KEY`, and a `Request` and `Response`. { const supabaseClient = createServerClient( process.env.SUPABASE_URL, - process.env.SUPABASE_ANON_KEY, + process.env.SUPABASE_PUBLISHABLE_KEY, { request, response } ) @@ -284,7 +284,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { const supabaseClient = createServerClient( process.env.SUPABASE_URL!, - process.env.SUPABASE_ANON_KEY!, + process.env.SUPABASE_PUBLISHABLE_KEY!, { request, response } ) @@ -355,7 +355,7 @@ Since our environment variables are not available client-side, we need to plumb export const loader = () => { const env = { SUPABASE_URL: process.env.SUPABASE_URL, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY, + SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY, } return json({ env }) @@ -377,7 +377,9 @@ const { env } = useLoaderData() We then want to instantiate a single instance of a Supabase browser client, to be used across our client-side components. ```jsx app/root.jsx -const [supabase] = useState(() => createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)) +const [supabase] = useState(() => + createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY) +) ``` And then we can share this instance across our application with Outlet Context. @@ -393,7 +395,7 @@ And then we can share this instance across our application with Outlet Context. export const loader = ({}: LoaderFunctionArgs) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL!, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!, + SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!, } return json({ env }) @@ -416,7 +418,7 @@ We then want to instantiate a single instance of a Supabase browser client, to b ```tsx app/root.tsx const [supabase] = useState(() => - createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) + createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY) ) ``` @@ -453,15 +455,19 @@ Let's pipe that through from our loader. export const loader = async ({ request }) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY, + SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY, } const response = new Response() - const supabase = createServerClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { - request, - response, - }) + const supabase = createServerClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_PUBLISHABLE_KEY, + { + request, + response, + } + ) const { data: { session }, @@ -487,15 +493,19 @@ export const loader = async ({ request }) => { export const loader = async ({ request }: LoaderFunctionArgs) => { const env = { SUPABASE_URL: process.env.SUPABASE_URL!, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!, + SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY!, } const response = new Response() - const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { - request, - response, - }) + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_PUBLISHABLE_KEY!, + { + request, + response, + } + ) const { data: { session }, @@ -533,7 +543,9 @@ And then use the revalidator, inside the `onAuthStateChange` hook. const { env, session } = useLoaderData() const { revalidate } = useRevalidator() -const [supabase] = useState(() => createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY)) +const [supabase] = useState(() => + createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY) +) const serverAccessToken = session?.access_token @@ -562,7 +574,7 @@ const { env, session } = useLoaderData() const { revalidate } = useRevalidator() const [supabase] = useState(() => - createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) + createBrowserClient(env.SUPABASE_URL, env.SUPABASE_PUBLISHABLE_KEY) ) const serverAccessToken = session?.access_token @@ -703,10 +715,14 @@ import { useEffect, useState } from 'react' export const loader = async ({ request }) => { const response = new Response() - const supabase = createServerClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { - request, - response, - }) + const supabase = createServerClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_PUBLISHABLE_KEY, + { + request, + response, + } + ) const { data } = await supabase.from('posts').select() @@ -760,7 +776,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const response = new Response() const supabase = createServerClient( process.env.SUPABASE_URL!, - process.env.SUPABASE_ANON_KEY!, + process.env.SUPABASE_PUBLISHABLE_KEY!, { request, response, diff --git a/apps/docs/content/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/content/guides/auth/auth-helpers/sveltekit.mdx index 25d9724d262..c518caeba91 100644 --- a/apps/docs/content/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/content/guides/auth/auth-helpers/sveltekit.mdx @@ -45,7 +45,7 @@ Retrieve your project's URL and anon key from your [API settings](https://supaba ```bash .env.local # Find these in your Supabase project settings https://supabase.com/dashboard/project/_/settings/api PUBLIC_SUPABASE_URL=https://your-project.supabase.co -PUBLIC_SUPABASE_ANON_KEY=your-anon-key +PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon key ``` ### Creating a Supabase client @@ -65,13 +65,13 @@ Create a new `hooks.server.js` file in the root of your project and populate wit ```js src/hooks.server.js // src/hooks.server.js -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' export const handle = async ({ event, resolve }) => { event.locals.supabase = createSupabaseServerClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event, }) @@ -111,14 +111,14 @@ Create a new `hooks.server.ts` file in the root of your project and populate wit ```ts src/hooks.server.ts // src/hooks.server.ts -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' import type { Handle } from '@sveltejs/kit' export const handle: Handle = async ({ event, resolve }) => { event.locals.supabase = createSupabaseServerClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event, }) @@ -307,7 +307,7 @@ To utilize Supabase in shared load functions and within pages, it is essential t ```ts src/routes/+layout.js // src/routes/+layout.js -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' export const load = async ({ fetch, data, depends }) => { @@ -315,7 +315,7 @@ export const load = async ({ fetch, data, depends }) => { const supabase = createSupabaseLoadClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event: { fetch }, serverSession: data.session, }) @@ -339,7 +339,7 @@ export const load = async ({ fetch, data, depends }) => { ```ts src/routes/+layout.ts // src/routes/+layout.ts -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' import type { Database } from '../DatabaseDefinitions' @@ -348,7 +348,7 @@ export const load = async ({ fetch, data, depends }) => { const supabase = createSupabaseLoadClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event: { fetch }, serverSession: data.session, }) @@ -648,7 +648,7 @@ Edit your `/src/hooks.server.js` with the below: ```js src/hooks.server.js // src/hooks.server.js -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' import { redirect, error } from '@sveltejs/kit' import { sequence } from '@sveltejs/kit/hooks' @@ -656,7 +656,7 @@ import { sequence } from '@sveltejs/kit/hooks' async function supabase({ event, resolve }) { event.locals.supabase = createSupabaseServerClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event, }) @@ -717,14 +717,14 @@ export const handle = sequence(supabase, authorization) ```ts src/hooks.server.ts // src/hooks.server.ts import { type Handle, redirect, error } from '@sveltejs/kit' -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' import { sequence } from '@sveltejs/kit/hooks' async function supabase({ event, resolve }) { event.locals.supabase = createSupabaseServerClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event, }) @@ -932,9 +932,12 @@ import { createClient } from '@supabase/auth-helpers-sveltekit' import { env } from '$env/dynamic/public' // or use the static env -// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'; -export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY) +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_PUBLISHABLE_KEY +) ``` @@ -942,14 +945,14 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S ```js src/hooks.server.ts // src/hooks.server.ts -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public' import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' import type { Handle } from '@sveltejs/kit' export const handle: Handle = async ({ event, resolve }) => { event.locals.supabase = createSupabaseServerClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event, }) @@ -1024,7 +1027,7 @@ In order to use the Supabase library in your client code you will need to setup ```ts src/routes/+layout.ts // src/routes/+layout.ts import { invalidate } from '$app/navigation' -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit' import type { LayoutLoad } from './$types' import type { Database } from '../DatabaseDefinitions' @@ -1034,7 +1037,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { const supabase = createSupabaseLoadClient({ supabaseUrl: PUBLIC_SUPABASE_URL, - supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY, event: { fetch }, serverSession: data.session, }) @@ -1295,12 +1298,16 @@ import { dev } from '$app/environment' import { env } from '$env/dynamic/public' // or use the static env -// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'; -export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, { - persistSession: false, - autoRefreshToken: false, -}) +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_PUBLISHABLE_KEY, + { + persistSession: false, + autoRefreshToken: false, + } +) setupSupabaseHelpers({ supabaseClient, @@ -1318,9 +1325,12 @@ import { createClient } from '@supabase/auth-helpers-sveltekit' import { env } from '$env/dynamic/public' // or use the static env -// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'; -export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY) +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_PUBLISHABLE_KEY +) ``` @@ -1646,7 +1656,7 @@ import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit'; const { supabaseClient } = createSupabaseClient( import.meta.env.VITE_SUPABASE_URL as string, - import.meta.env.VITE_SUPABASE_ANON_KEY as string + import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY as string ); export { supabaseClient }; @@ -1662,12 +1672,16 @@ import { dev } from '$app/environment' import { env } from '$env/dynamic/public' // or use the static env -// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'; -export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, { - persistSession: false, - autoRefreshToken: false, -}) +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_PUBLISHABLE_KEY, + { + persistSession: false, + autoRefreshToken: false, + } +) setupSupabaseHelpers({ supabaseClient, diff --git a/apps/docs/content/guides/auth/auth-mfa.mdx b/apps/docs/content/guides/auth/auth-mfa.mdx index 9a67c33cc97..719528aa2e2 100644 --- a/apps/docs/content/guides/auth/auth-mfa.mdx +++ b/apps/docs/content/guides/auth/auth-mfa.mdx @@ -82,7 +82,10 @@ When a user unenrolls a factor, call `supabase.auth.mfa.unenroll()` with the ID ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://your-project-id.supabase.co', 'your-anon-key') +const supabase = createClient( + 'https://your-project-id.supabase.co', + 'sb_publishable_... or anon key' +) // ---cut--- supabase.auth.mfa.unenroll({ factorId: 'd30fd651-184e-4748-a928-0a4b9be1d429' }) diff --git a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx index 56bae4f8f16..77144658e12 100644 --- a/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx +++ b/apps/docs/content/guides/auth/enterprise-sso/auth-sso-saml.mdx @@ -195,7 +195,7 @@ To initiate a sign-in request from your application's user interface (i.e. the S ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('', '') +const supabase = createClient('', '') // ---cut--- supabase.auth.signInWithSSO({ @@ -436,7 +436,7 @@ Yes, also referred to as [cross-origin authentication within the same site](http ```ts import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://your-project.supabase.co', 'your-anon-key') +const supabase = createClient('https://your-project.supabase.co', 'sb_publishable_... or anon key') // ---cut--- const { data, error } = await supabase.auth.signInWithSSO({ diff --git a/apps/docs/content/guides/auth/jwts.mdx b/apps/docs/content/guides/auth/jwts.mdx index a20b58a6cb1..cc98b5825f2 100644 --- a/apps/docs/content/guides/auth/jwts.mdx +++ b/apps/docs/content/guides/auth/jwts.mdx @@ -93,11 +93,15 @@ If you wish to send a JWT from a Third-Party Auth provider, or one you made your ```typescript import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://.supabase.co', 'SUPABASE_ANON_KEY', { - accessToken: async () => { - return '' - }, -}) +const supabase = createClient( + 'https://.supabase.co', + 'SUPABASE_PUBLISHABLE_KEY', + { + accessToken: async () => { + return '' + }, + } +) ``` @@ -124,7 +128,7 @@ import Supabase let supabase = SupabaseClient( supabaseURL: URL(string: "https://.supabase.co")!, - supabaseKey: "SUPABASE_ANON_KEY", + supabaseKey: "SUPABASE_PUBLISHABLE_KEY", options: SupabaseClientOptions( auth: SupabaseClientOptions.AuthOptions( accessToken: { @@ -142,7 +146,7 @@ let supabase = SupabaseClient( ```kotlin val supabase = createSupabaseClient( "https://.supabase.co", - "SUPABASE_ANON_KEY" + "SUPABASE_PUBLISHABLE_KEY" ) { accessToken = { "" diff --git a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx index 18d8d01a937..28058e5fe40 100644 --- a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx +++ b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx @@ -102,7 +102,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This return ( <> - - - - -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.svelte" +lines={[[1, -1]]} +meta="name=src/routes/+page.svelte" +/> -Create a `src/routes/+page.server.ts` file that will handle our magic link form when submitted. +Create a `src/routes/+page.server.ts` file that handles the magic link form when submitted. <$CodeTabs> -```ts name=src/routes/+page.server.ts -// src/routes/+page.server.ts -import { fail, redirect } from '@sveltejs/kit' -import type { Actions, PageServerLoad } from './$types' - -export const load: PageServerLoad = async ({ url, locals: { safeGetSession } }) => { - const { session } = await safeGetSession() - - // if the user is already logged in return them to the account page - if (session) { - redirect(303, '/account') - } - - return { url: url.origin } -} - -export const actions: Actions = { - default: async (event) => { - const { - url, - request, - locals: { supabase }, - } = event - const formData = await request.formData() - const email = formData.get('email') as string - const validEmail = /^[\w-\.+]+@([\w-]+\.)+[\w-]{2,8}$/.test(email) - - if (!validEmail) { - return fail(400, { errors: { email: 'Please enter a valid email address' }, email }) - } - - const { error } = await supabase.auth.signInWithOtp({ email }) - - if (error) { - return fail(400, { - success: false, - email, - message: `There was an issue, Please contact support.`, - }) - } - - return { - success: true, - message: 'Please check your email for a magic link to log into the website.', - } - }, -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.server.ts" +lines={[[1, -1]]} +meta="name=src/routes/+page.server.ts" +/> @@ -388,239 +164,77 @@ Change the email template to support a server-side authentication flow. Before we proceed, let's change the email template to support sending a token hash: -- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard. -- Select `Confirm signup` template. +- Go to the [**Auth** > **Emails**](/dashboard/project/_/auth/templates) page in the project dashboard. +- Select the **Confirm signup** template. - Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`. -- Repeat the previous step for `Magic link` template. +- Repeat the previous step for **Magic link** template. -Did you know? You could also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates). +**Did you know?** You can also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates). ### Confirmation endpoint -As we are working in a server-side rendering (SSR) environment, it is necessary to create a server endpoint responsible for exchanging the `token_hash` for a session. +As this is a server-side rendering (SSR) environment, you need to create a server endpoint responsible for exchanging the `token_hash` for a session. -In the following code snippet, we perform the following steps: +The following code snippet performs the following steps: -- Retrieve the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter. -- Exchange this `token_hash` for a session, which we store in storage (in this case, cookies). -- Finally, the user is redirected to the `account` page or the `error` page. +- Retrieves the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter. +- Exchanges this `token_hash` for a session, which you store in storage (in this case, cookies). +- Finally, redirect the user to the `account` page or the `error` page. <$CodeTabs> -```ts name=src/routes/auth/confirm/+server.ts -// src/routes/auth/confirm/+server.ts -import type { EmailOtpType } from '@supabase/supabase-js' -import { redirect } from '@sveltejs/kit' - -import type { RequestHandler } from './$types' - -export const GET: RequestHandler = async ({ url, locals: { supabase } }) => { - const token_hash = url.searchParams.get('token_hash') - const type = url.searchParams.get('type') as EmailOtpType | null - const next = url.searchParams.get('next') ?? '/account' - - /** - * Clean up the redirect URL by deleting the Auth flow parameters. - * - * `next` is preserved for now, because it's needed in the error case. - */ - const redirectTo = new URL(url) - redirectTo.pathname = next - redirectTo.searchParams.delete('token_hash') - redirectTo.searchParams.delete('type') - - if (token_hash && type) { - const { error } = await supabase.auth.verifyOtp({ type, token_hash }) - if (!error) { - redirectTo.searchParams.delete('next') - redirect(303, redirectTo) - } - } - - redirectTo.pathname = '/auth/error' - redirect(303, redirectTo) -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/auth/confirm/+server.ts" +lines={[[1, -1]]} +meta="name=src/routes/auth/confirm/+server.ts" +/> ### Authentication error page -If there is an error with confirming the token you will be redirect to this error page. +If there is an error with confirming the token, redirect the user to an error page. <$CodeTabs> - -```svelte name=src/routes/auth/error/+page.svelte -

Login error

-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/auth/error/+page.svelte" +lines={[[1, -1]]} +meta="name=src/routes/auth/error/+page.svelte" +/> ### Account page -After a user is signed in, they need to be able to edit their profile details and manage their account. +After a user signs in, they need to be able to edit their profile details page. Create a new `src/routes/account/+page.svelte` file with the content below. <$CodeTabs> -```svelte name=src/routes/account/+page.svelte - - - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- -
-
- -
-
-
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte" +lines={[[1, 3],[6,12],[15,38],[47,-1]]} +meta="name=src/routes/account/+page.svelte" +/> -Now create the associated `src/routes/account/+page.server.ts` file that will handle loading our data from the server through the `load` function -and handle all our form actions through the `actions` object. +Now, create the associated `src/routes/account/+page.server.ts` file that handles loading data from the server through the `load` function +and handle all form actions through the `actions` object. -```ts name=src/routes/account/+page.server.ts -import { fail, redirect } from '@sveltejs/kit' -import type { Actions, PageServerLoad } from './$types' - -export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => { - const { session } = await safeGetSession() - - if (!session) { - redirect(303, '/') - } - - const { data: profile } = await supabase - .from('profiles') - .select(`username, full_name, website, avatar_url`) - .eq('id', session.user.id) - .single() - - return { session, profile } -} - -export const actions: Actions = { - update: async ({ request, locals: { supabase, safeGetSession } }) => { - const formData = await request.formData() - const fullName = formData.get('fullName') as string - const username = formData.get('username') as string - const website = formData.get('website') as string - const avatarUrl = formData.get('avatarUrl') as string - - const { session } = await safeGetSession() - - const { error } = await supabase.from('profiles').upsert({ - id: session?.user.id, - full_name: fullName, - username, - website, - avatar_url: avatarUrl, - updated_at: new Date(), - }) - - if (error) { - return fail(500, { - fullName, - username, - website, - avatarUrl, - }) - } - - return { - fullName, - username, - website, - avatarUrl, - } - }, - signout: async ({ locals: { supabase, safeGetSession } }) => { - const { session } = await safeGetSession() - if (session) { - await supabase.auth.signOut() - redirect(303, '/') - } - }, -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.server.ts" +lines={[[1, -1]]} +meta="name=src/routes/+page.server.ts" +/> ### Launch! -Now that we have all the pages in place, run this in a terminal window: +With all the pages in place, run this command in a terminal: ```bash npm run dev @@ -636,144 +250,29 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma ### Create an upload widget -Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory: +Create an avatar for the user so that they can upload a profile photo. Start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory: <$CodeTabs> -```svelte name=src/routes/account/Avatar.svelte - - - -
- {#if avatarUrl} - {avatarUrl - {:else} -
- {/if} - - -
- - -
-
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/Avatar.svelte" +lines={[[1, -1]]} +meta="name=src/routes/account/Avatar.svelte" +/> ### Add the new widget -And then we can add the widget to the Account page: +Add the widget to the Account page: <$CodeTabs> -```svelte name=src/routes/account/+page.svelte - - - -
-
- - { - profileForm.requestSubmit(); - }} - /> - - - -
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte" +lines={[[1, 1], [4,4], [31,31],[39,46],[82,82]]} +meta="name=src/routes/account/+page.svelte" +/> diff --git a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx index 1705ea759e4..29764e59660 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-swift.mdx @@ -40,7 +40,7 @@ import Supabase let supabase = SupabaseClient( supabaseURL: URL(string: "YOUR_SUPABASE_URL")!, - supabaseKey: "YOUR_SUPABASE_ANON_KEY" + supabaseKey: "YOUR_SUPABASE_PUBLISHABLE_KEY" ) ``` diff --git a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx index f5226885807..a5a53669646 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-vue-3.mdx @@ -47,7 +47,7 @@ All we need are the API URL and the `anon` key that you copied [earlier](#get-th ```bash name=.env VITE_SUPABASE_URL=YOUR_SUPABASE_URL -VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +VITE_SUPABASE_PUBLISHABLE_KEY=YOUR_SUPABASE_PUBLISHABLE_KEY ``` @@ -61,7 +61,7 @@ on the browser, and that's completely fine since we have [Row Level Security](/d import { createClient } from '@supabase/supabase-js' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL -const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey) ``` diff --git a/apps/docs/content/guides/integrations/vercel-marketplace.mdx b/apps/docs/content/guides/integrations/vercel-marketplace.mdx index 5821eeccb07..0f753e5d208 100644 --- a/apps/docs/content/guides/integrations/vercel-marketplace.mdx +++ b/apps/docs/content/guides/integrations/vercel-marketplace.mdx @@ -47,10 +47,10 @@ POSTGRES_HOST POSTGRES_PASSWORD POSTGRES_DATABASE SUPABASE_SERVICE_ROLE_KEY -SUPABASE_ANON_KEY +SUPABASE_PUBLISHABLE_KEY SUPABASE_URL SUPABASE_JWT_SECRET -NEXT_PUBLIC_SUPABASE_ANON_KEY +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY NEXT_PUBLIC_SUPABASE_URL ``` diff --git a/apps/docs/content/guides/local-development/testing/overview.mdx b/apps/docs/content/guides/local-development/testing/overview.mdx index 0d00899b510..ddc5030f781 100644 --- a/apps/docs/content/guides/local-development/testing/overview.mdx +++ b/apps/docs/content/guides/local-development/testing/overview.mdx @@ -141,7 +141,7 @@ describe('Todos RLS', () => { const USER_1_ID = crypto.randomUUID() const USER_2_ID = crypto.randomUUID() - const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!) + const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_PUBLISHABLE_KEY!) beforeAll(async () => { // Setup test data specific to this test suite diff --git a/apps/docs/content/guides/platform/billing-faq.mdx b/apps/docs/content/guides/platform/billing-faq.mdx index 719cf942f4b..fa0dcde3872 100644 --- a/apps/docs/content/guides/platform/billing-faq.mdx +++ b/apps/docs/content/guides/platform/billing-faq.mdx @@ -64,9 +64,11 @@ If you upgrade your project to a larger instance for 10 hours and then downgrade Read more about [Compute usage](/docs/guides/platform/manage-your-usage/compute). -#### What is unified egress and how is it billed? +#### What is egress and how is it billed? -Unified egress refers to the total egress quota available to each organization. This quota can be utilized for various purposes such as Storage, Realtime, Auth, Functions, Supavisor, Log Drains and Database. Each plan includes a specific egress quota, and any additional usage beyond that quota is billed accordingly. +Egress refers to the total bandwidth (network traffic) quota available to each organization. This quota can be utilized for various purposes such as Storage, Realtime, Auth, Functions, Supavisor, Log Drains and Database. Each plan includes a specific egress quota, and any additional usage beyond that quota is billed accordingly. + +We differentiate between cached (served via our CDN from cache hits) and uncached egress and give quotas for each type and have varying pricing (cached egress is cheaper). Read more about [Egress usage](/docs/guides/platform/manage-your-usage/egress). @@ -128,7 +130,7 @@ The Fair Use Policy is generally applied to all projects of the restricted organ To remove restrictions, you will need to address the issue that caused the restriction. This could be reducing your usage, paying overdue invoices, updating your payment method, or any other issue that caused the restriction. Once the issue is resolved, the restriction will be lifted. -Restrictions due to usage limits are lifted with the next billing cycle as your quota refills at the beginning of each cycle. You can see when your current billing cycle ends on the [billing page](https://supabase.com/dashboard/org/_/billing) under "Upcoming Invoice". If your organization is on the Free Plan, you can also lift restrictions immediately by [upgrading](https://supabase.com/dashboard/org/_/billing?panel=subscriptionPlan) to Pro. +Restrictions due to usage limits are lifted with the next billing cycle as your quota refills at the beginning of each cycle. You can see when your current billing cycle ends on the [billing page](https://supabase.com/dashboard/org/_/billing) under "Upcoming Invoice". You can also lift restrictions immediately by [upgrading](https://supabase.com/dashboard/org/_/billing?panel=subscriptionPlan) to Pro (if on Free Plan) or by [disabling spend cap](https://supabase.com/dashboard/org/_/billing?panel=costControl) (if on Pro Plan with spend cap enabled). ## Reports and invoices diff --git a/apps/docs/content/guides/platform/custom-domains.mdx b/apps/docs/content/guides/platform/custom-domains.mdx index 32226013bb8..e4d4a5e8847 100644 --- a/apps/docs/content/guides/platform/custom-domains.mdx +++ b/apps/docs/content/guides/platform/custom-domains.mdx @@ -124,7 +124,7 @@ If you wish to use the new domain in client code, change the URL used in your Su import { createClient } from '@supabase/supabase-js' // Use a custom domain as the supabase URL -const supabase = createClient('https://api.example.com', 'public-anon-key') +const supabase = createClient('https://api.example.com', 'publishable-or-anon-key') ``` Similarly, your Edge Functions will now be available at `https://api.example.com/functions/v1/your_function_name`, and your Storage objects at `https://api.example.com/storage/v1/object/public/your_file_path.ext`. @@ -202,7 +202,7 @@ If you wish to use the new domain in client code, you can set it up like so: import { createClient } from '@supabase/supabase-js' // Use a custom domain as the supabase URL -const supabase = createClient('https://my-example-brand.supabase.co', 'public-anon-key') +const supabase = createClient('https://my-example-brand.supabase.co', 'publishable-or-anon-key') ``` When using [Sign in with Twitter](/docs/guides/auth/social-login/auth-twitter) make sure your frontend code is using the subdomain only. diff --git a/apps/docs/content/guides/platform/manage-your-usage/egress.mdx b/apps/docs/content/guides/platform/manage-your-usage/egress.mdx index 71a893036af..b5819a76234 100644 --- a/apps/docs/content/guides/platform/manage-your-usage/egress.mdx +++ b/apps/docs/content/guides/platform/manage-your-usage/egress.mdx @@ -55,25 +55,29 @@ Data pushed to the connected log drain. **Example:** You set up a log drain, each log sent to the log drain is considered egress. You can toggle the GZIP option to reduce egress, in case your provider supports it. +### Cached Egress + +Cached and uncached egress have independent quotas and independent pricing. Cached egress is egress that is served from our CDN via cache hits. Cached egress is typically incurred for storage through our [Smart CDN](/docs/guides/storage/cdn/smart-cdn). + ## How charges are calculated Egress is charged by gigabyte. Charges apply only for usage exceeding your subscription plan's quota. This quota is called the Unified Egress Quota because it can be used across all services (Database, Auth, Storage etc.). ### Usage on your invoice -Usage is shown as "Egress GB" on your invoice. +Usage is shown as "Egress GB" and "Cached Egress GB" on your invoice. ## Pricing - per GB per month. You are only charged for usage exceeding your subscription plan's -quota. + per GB per month for uncached egress, per GB per month +for cached egress. You are only charged for usage exceeding your subscription plan's quota. -| Plan | Unified Egress Quota | Over-Usage per month | -| ---------- | -------------------- | ----------------------------- | -| Free | 5 GB | - | -| Pro | 250 GB | per GB | -| Team | 250 GB | per GB | -| Enterprise | Custom | Custom | +| Plan | Egress Quota (Uncached / Cached) | Over-Usage per month (Uncached / Cached) | +| ---------- | -------------------------------- | ------------------------------------------------------------- | +| Free | 5 GB / 5 GB | - | +| Pro | 250 GB / 250 GB | per GB / per GB | +| Team | 250 GB / 250 GB | per GB / per GB | +| Enterprise | Custom | Custom | ## Billing examples @@ -86,22 +90,24 @@ The organization's Egress usage is within the quota, so no charges for Egress ap | Pro Plan | 1 | | | Compute Hours Micro | 744 hours | | | Egress | 200 GB | | +| Cached Egress | 230 GB | | | **Subtotal** | | **** | | Compute Credits | | - | | **Total** | | **** | ### Exceeding quota -The organization's Egress usage exceeds the quota by 50 GB, incurring charges for this additional usage. +The organization's Egress usage exceeds the uncached egress quota by 50 GB and the cached egress quota by 550 GB, incurring charges for this additional usage. | Line Item | Units | Costs | | ------------------- | --------- | -------------------------- | | Pro Plan | 1 | | | Compute Hours Micro | 744 hours | | | Egress | 300 GB | | -| **Subtotal** | | **** | +| Cached Egress | 800 GB | | +| **Subtotal** | | **** | | Compute Credits | | - | -| **Total** | | **** | +| **Total** | | **** | ## View usage @@ -118,7 +124,7 @@ You can view Egress usage on the [organization's usage page](https://supabase.co zoomable /> -In the Total Egress section, you can see the usage for the selected time period. Hover over a specific date to view a breakdown by service. +In the Total Egress section, you can see the usage for the selected time period. Hover over a specific date to view a breakdown by service. Note that this includes the cached egress. Unified Egress +Separately, you can see the cached egress right below: + +Unified Egress + ### Custom report 1. On the [reports page](https://supabase.com/dashboard/project/_/reports), click **New custom report** in the left navigation menu @@ -144,7 +160,7 @@ In the Total Egress section, you can see the usage for the selected time period. ## Debug usage -To better understand your Egress usage, identify what’s driving the most traffic. Check the most frequent database queries, or analyze the most requested API paths to pinpoint high-bandwidth endpoints. +To better understand your Egress usage, identify what’s driving the most traffic. Check the most frequent database queries, or analyze the most requested API paths to pinpoint high-egress endpoints. ### Frequent database queries diff --git a/apps/docs/content/guides/platform/upgrading.mdx b/apps/docs/content/guides/platform/upgrading.mdx index 0460df6c339..5d001b186ba 100644 --- a/apps/docs/content/guides/platform/upgrading.mdx +++ b/apps/docs/content/guides/platform/upgrading.mdx @@ -164,6 +164,7 @@ In projects using Postgres 17, the following extensions are deprecated: - `plls` - `plv8` - `timescaledb` +- `pgjwt` Existing projects on lower versions of Postgres are not impacted, and the extensions will continue to be supported on projects using Postgres 15, until the end of life of Postgres 15 on the Supabase platform. diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx index 4321d5e40c3..831f40e724a 100644 --- a/apps/docs/content/guides/realtime.mdx +++ b/apps/docs/content/guides/realtime.mdx @@ -18,16 +18,32 @@ Supabase provides a globally distributed [Realtime](https://github.com/supabase/ {[ { name: 'Multiplayer.dev', - description: 'Mouse movements and chat messages.', + description: 'Showcase application displaying cursor movements and chat messages using Broadcast.', href: 'https://multiplayer.dev', }, - ].map((x) => ( -
- - {x.description} - -
- ))} + { + name: 'Chat', + description: 'Supabase UI chat component using Broadcast to send message between users.', + href: 'https://supabase.com/ui/docs/nextjs/realtime-chat' + }, + { + name: 'Avatar Stack', + description: 'Supabase UI avatar stack component using Presence to track connected users.', + href: 'https://supabase.com/ui/docs/nextjs/realtime-avatar-stack' + }, + { + name: 'Realtime Cursor', + description: "Supabase UI realtime cursor component using Broadcast to share users' cursors to build collaborative applications.", + href: 'https://supabase.com/ui/docs/nextjs/realtime-cursor' + } +].map((x) => ( +
+ + {x.description} + +
+))} +
## Resources diff --git a/apps/docs/content/guides/realtime/authorization.mdx b/apps/docs/content/guides/realtime/authorization.mdx index 66c854efcb7..3dd1d59b336 100644 --- a/apps/docs/content/guides/realtime/authorization.mdx +++ b/apps/docs/content/guides/realtime/authorization.mdx @@ -50,13 +50,11 @@ Increased RLS complexity can impact database performance and connection time, le -## Helper functions - -You can use the following helper functions when writing RLS policies: +## Accessing request information ### `realtime.topic` -Returns the Channel topic the user is attempting to connect to. +You can use the `realtime.topic` helper function when writing RLS policies. It returns the Channel topic the user is attempting to connect to. ```sql create policy "authenticated can read all messages on topic" @@ -68,6 +66,21 @@ using ( ); ``` +### JWT claims + +The user claims can be accessed using the `current_setting` function. The claims are available as a JSON object in the `request.jwt.claims` setting. + +```sql +create policy "authenticated with supabase.io email can read all" +on "realtime"."messages" +for select +to authenticated +using ( + -- Only users with the email claim ending with @supabase.io + (((current_setting('request.jwt.claims'))::json ->> 'email') ~~ '%@supabase.io') +); +``` + ## Examples The following examples use this schema: diff --git a/apps/docs/content/guides/realtime/broadcast.mdx b/apps/docs/content/guides/realtime/broadcast.mdx index 38eb434e251..18bce679967 100644 --- a/apps/docs/content/guides/realtime/broadcast.mdx +++ b/apps/docs/content/guides/realtime/broadcast.mdx @@ -27,7 +27,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj import { createClient } from '@supabase/supabase-js' const SUPABASE_URL = 'https://.supabase.co' - const SUPABASE_KEY = '' + const SUPABASE_KEY = '' const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) ``` @@ -41,7 +41,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj void main() async { Supabase.initialize( url: 'https://.supabase.co', - anonKey: '', + anonKey: '', ); runApp(MyApp()); } @@ -56,7 +56,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj import Supabase let SUPABASE_URL = "https://.supabase.co" - let SUPABASE_KEY = "" + let SUPABASE_KEY = "" let supabase = SupabaseClient(supabaseURL: URL(string: SUPABASE_URL)!, supabaseKey: SUPABASE_KEY) ``` @@ -66,7 +66,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj ```kotlin val supabaseUrl = "https://.supabase.co" - val supabaseKey = "" + val supabaseKey = "" val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { install(Realtime) } @@ -80,7 +80,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj from supabase import acreate_client URL = "https://.supabase.co" - KEY = "" + KEY = "" async def create_supabase(): supabase = await acreate_client(URL, KEY) @@ -107,7 +107,7 @@ You can provide a callback for the `broadcast` channel to receive messages. This ```js // @noImplicitAny: false import { createClient } from '@supabase/supabase-js' - const supabase = createClient('https://.supabase.co', '') + const supabase = createClient('https://.supabase.co', '') // ---cut--- // Join a room/topic. Can be anything except for 'realtime'. @@ -366,6 +366,12 @@ This feature is in Public Beta. [Submit a support ticket](https://supabase.help) + + +All the messages sent using Broadcast from the Database are stored in `realtime.messages` table and will be deleted after 3 days. + + + You can send messages directly from your database using the `realtime.send()` function: {/* prettier-ignore */} @@ -691,3 +697,204 @@ You can pass configuration options while initializing the Supabase Client.
Use this to guarantee that the server has received the message before resolving `channelD.send`'s promise. If the `ack` config is not set to `true` when creating the channel, the promise returned by `channelD.send` will resolve immediately. + +### Send messages using REST calls + +You can also send a Broadcast message by making an HTTP request to Realtime servers. This is useful when you want to send messages from your server or client without having to first establish a WebSocket connection. + + + + + + This is currently available only in the Supabase JavaScript client version 2.37.0 and later. + + + + ```js + const channel = supabase.channel('test-channel') + + // No need to subscribe to channel + + channel + .send({ + type: 'broadcast', + event: 'test', + payload: { message: 'Hi' }, + }) + .then((resp) => console.log(resp)) + + // Remember to clean up the channel + + supabase.removeChannel(channel) + + ``` + + + + ```dart + // No need to subscribe to channel + + final channel = supabase.channel('test-channel'); + final res = await channel.sendBroadcastMessage( + event: "test", + payload: { + 'message': 'Hi', + }, + ); + print(res); + ``` + + + + ```swift + let myChannel = await supabase.channel("room-2") { + $0.broadcast.acknowledgeBroadcasts = true + } + + // No need to subscribe to channel + + await myChannel.broadcast(event: "test", message: ["message": "HI"]) + ``` + + + + ```kotlin + val myChannel = supabase.channel("room-2") { + broadcast { + acknowledgeBroadcasts = true + } + } + + // No need to subscribe to channel + + myChannel.broadcast(event = "test", buildJsonObject { + put("message", "Hi") + }) + ``` + + + + Unsupported in Python yet. + + + +## Trigger broadcast messages from your database + + + +This feature is currently in Public Alpha. If you have any issues [submit a support ticket](https://supabase.help). + + + +### How it works + +Broadcast Changes allows you to trigger messages from your database. To achieve it Realtime is directly reading your WAL (Write Append Log) file using a publication against the `realtime.messages` table so whenever a new insert happens a message is sent to connected users. + +It uses partitioned tables per day which allows the deletion your previous messages in a performant way by dropping the physical tables of this partitioned table. Tables older than 3 days old are deleted. + +Broadcasting from the database works like a client-side broadcast, using WebSockets to send JSON packages. [Realtime Authorization](/docs/guides/realtime/authorization) is required and enabled by default to protect your data. + +The database broadcast feature provides two functions to help you send messages: + +- `realtime.send` will insert a message into realtime.messages without a specific format. +- `realtime.broadcast_changes` will insert a message with the required fields to emit database changes to clients. This helps you set up triggers on your tables to emit changes. + +### Broadcasting a message from your database + +The `realtime.send` function provides the most flexibility by allowing you to broadcast messages from your database without a specific format. This allows you to use database broadcast for messages that aren't necessarily tied to the shape of a Postgres row change. + +```sql +SELECT realtime.send ( + '{}'::jsonb, -- JSONB Payload + 'event', -- Event name + 'topic', -- Topic + FALSE -- Public / Private flag +); +``` + +### Broadcast record changes + +#### Setup realtime authorization + +Realtime Authorization is required and enabled by default. To allow your users to listen to messages from topics, create a RLS (Row Level Security) policy: + +```sql +CREATE POLICY "authenticated can receive broadcasts" +ON "realtime"."messages" +FOR SELECT +TO authenticated +USING ( true ); + +``` + +See the [Realtime Authorization](/docs/guides/realtime/authorization) docs to learn how to set up more specific policies. + +#### Set up trigger function + +First, set up a trigger function that uses `realtime.broadcast_changes` to insert an event whenever it is triggered. The event is set up to include data on the schema, table, operation, and field changes that triggered it. + +For this example use case, we want to have a topic with the name `topic:` to which we're going to broadcast events. + +```sql +CREATE OR REPLACE FUNCTION public.your_table_changes() +RETURNS trigger +SECURITY DEFINER SET search_path = '' +AS $$ +BEGIN + PERFORM realtime.broadcast_changes( + 'topic:' || NEW.id::text, -- topic + TG_OP, -- event + TG_OP, -- operation + TG_TABLE_NAME, -- table + TG_TABLE_SCHEMA, -- schema + NEW, -- new record + OLD -- old record + ); + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +``` + +Of note are the Postgres native trigger special variables used: + +- `TG_OP` - the operation that triggered the function +- `TG_TABLE_NAME` - the table that caused the trigger +- `TG_TABLE_SCHEMA` - the schema of the table that caused the trigger invocation +- `NEW` - the record after the change +- `OLD` - the record before the change + +You can read more about them in this [guide](https://www.postgresql.org/docs/current/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER). + +#### Set up trigger + +Next, set up a trigger so the function runs whenever your target table has a change. + +```sql +CREATE TRIGGER broadcast_changes_for_your_table_trigger +AFTER INSERT OR UPDATE OR DELETE ON public.your_table +FOR EACH ROW +EXECUTE FUNCTION your_table_changes (); +``` + +As you can see, it will be broadcasting all operations so our users will receive events when records are inserted, updated or deleted from `public.your_table` . + +#### Listen on client side + +Finally, client side will requires to be set up to listen to the topic `topic:` to receive the events. + +```jsx +const gameId = 'id' +await supabase.realtime.setAuth() // Needed for Realtime Authorization +const changes = supabase + .channel(`topic:${gameId}`) + .on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload)) + .on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload)) + .on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload)) + .subscribe() +``` diff --git a/apps/docs/content/guides/realtime/concepts.mdx b/apps/docs/content/guides/realtime/concepts.mdx index b4fbda80fa5..558cd8ba9eb 100644 --- a/apps/docs/content/guides/realtime/concepts.mdx +++ b/apps/docs/content/guides/realtime/concepts.mdx @@ -21,7 +21,7 @@ When you initialize your Supabase Realtime client, you define a `topic` that uni ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://.supabase.co', '') +const supabase = createClient('https://.supabase.co', '') const roomOne = supabase.channel('room-one') // set your topic here ``` @@ -34,7 +34,7 @@ By default, channels are public and you need to set that you want to use a priva ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://.supabase.co', '') +const supabase = createClient('https://.supabase.co', '') const roomOne = supabase.channel('private-room-one', { config: { private: true } }) ``` @@ -44,7 +44,7 @@ Realtime Broadcast follows the [publish-subscribe pattern](https://en.wikipedia. ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://.supabase.co', '') +const supabase = createClient('https://.supabase.co', '') const roomOne = supabase.channel('room-one') // set your topic here // ---cut--- @@ -67,7 +67,7 @@ Presence can be used to share an individual's state with others within a Channel ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('https://.supabase.co', '') +const supabase = createClient('https://.supabase.co', '') const roomOne = supabase.channel('room-one') // set your topic here // ---cut--- diff --git a/apps/docs/content/guides/realtime/postgres-changes.mdx b/apps/docs/content/guides/realtime/postgres-changes.mdx index 395deda2828..993b6935fd6 100644 --- a/apps/docs/content/guides/realtime/postgres-changes.mdx +++ b/apps/docs/content/guides/realtime/postgres-changes.mdx @@ -91,8 +91,19 @@ In this example we'll set up a database table, secure it with Row Level Security Go to your project's [Publications settings](https://supabase.com/dashboard/project/_/database/publications), and under `supabase_realtime`, toggle on the tables you want to listen to. + Alternatively, add tables to the `supabase_realtime` publication by running the given SQL: + + + + ```sql + alter publication supabase_realtime + add table your_table_name; + ``` + + + @@ -128,7 +139,7 @@ In this example we'll set up a database table, secure it with Row Level Security const supabase = createClient( 'https://.supabase.co', - '' + '' ) ``` diff --git a/apps/docs/content/guides/realtime/presence.mdx b/apps/docs/content/guides/realtime/presence.mdx index f545643cb2d..2c599015f08 100644 --- a/apps/docs/content/guides/realtime/presence.mdx +++ b/apps/docs/content/guides/realtime/presence.mdx @@ -27,7 +27,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj import { createClient } from '@supabase/supabase-js' const SUPABASE_URL = 'https://.supabase.co' -const SUPABASE_KEY = '' +const SUPABASE_KEY = '' const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) ``` @@ -39,7 +39,7 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY) void main() { Supabase.initialize( url: 'https://.supabase.co', - anonKey: '', + anonKey: '', ); runApp(MyApp()); @@ -53,7 +53,7 @@ final supabase = Supabase.instance.client; ```swift let supabaseURL = "https://.supabase.co" -let supabaseKey = "" +let supabaseKey = "" let supabase = SupabaseClient(supabaseURL: URL(string: supabaseURL)!, supabaseKey: supabaseKey) let realtime = supabase.realtime @@ -64,7 +64,7 @@ let realtime = supabase.realtime ```kotlin val supabaseUrl = "https://.supabase.co" -val supabaseKey = "" +val supabaseKey = "" val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { install(Realtime) } @@ -77,7 +77,7 @@ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { from supabase import create_client SUPABASE_URL = 'https://.supabase.co' -SUPABASE_KEY = '' +SUPABASE_KEY = '' supabase = create_client(SUPABASE_URL, SUPABASE_KEY) ``` @@ -397,7 +397,7 @@ By default, Presence will generate a unique `UUIDv1` key on the server to track ```js import { createClient } from '@supabase/supabase-js' -const supabase = createClient('SUPABASE_URL', 'SUPABASE_ANON_KEY') +const supabase = createClient('SUPABASE_URL', 'SUPABASE_PUBLISHABLE_KEY') const channelC = supabase.channel('test', { config: { diff --git a/apps/docs/content/guides/storage/production/scaling.mdx b/apps/docs/content/guides/storage/production/scaling.mdx index c5f38e72eae..031ae6a62f9 100644 --- a/apps/docs/content/guides/storage/production/scaling.mdx +++ b/apps/docs/content/guides/storage/production/scaling.mdx @@ -24,6 +24,10 @@ Using the browser cache can effectively lower your egress since the asset remain You have the option to set a maximum upload size for your bucket. Doing this can prevent users from uploading and then downloading excessively large files. You can control the maximum file size by configuring this option at the [bucket level](/docs/guides/storage/buckets/creating-buckets). +#### Smart CDN + +By leveraging our [Smart CDN](/docs/guides/storage/cdn/smart-cdn), you can achieve a higher cache hit rate and therefore lower your egress cached, as we charge less for cached egress (see [egress pricing](/docs/guides/platform/manage-your-usage/egress#pricing)). + ## Optimize listing objects Once you have a substantial number of objects, you might observe that the `supabase.storage.list()` method starts to slow down. This occurs because the endpoint is quite generic and attempts to retrieve both folders and objects in a single query. While this approach is very useful for building features like the Storage viewer on the Supabase dashboard, it can impact performance with a large number of objects. diff --git a/apps/docs/content/guides/storage/serving/bandwidth.mdx b/apps/docs/content/guides/storage/serving/bandwidth.mdx index 740931c242d..021810276c4 100644 --- a/apps/docs/content/guides/storage/serving/bandwidth.mdx +++ b/apps/docs/content/guides/storage/serving/bandwidth.mdx @@ -8,44 +8,52 @@ sidebar_label: 'Bandwidth & Storage Egress' ## Bandwidth & Storage egress -Free Plan Organizations in Supabase have a limit of 5 GB of bandwidth. This limit is calculated by the sum of all the data transferred from the Supabase servers to the client. This includes all the data transferred from the database, storage, and functions. +Free Plan Organizations in Supabase have a limit of 10 GB of bandwidth (5 GB cached + 5 GB uncached). This limit is calculated by the sum of all the data transferred from the Supabase servers to the client. This includes all the data transferred from the database, storage, and functions. -### Checking Storage egress requests in Logs Explorer: +### Checking Storage egress requests in Logs Explorer We have a template query that you can use to get the number of requests for each object in [Logs Explorer](/dashboard/project/_/logs/explorer/templates). ```sql select - r.method as http_verb, - r.path as filepath, + request.method as http_verb, + request.path as filepath, + (responseHeaders.cf_cache_status = 'HIT') as cached, count(*) as num_requests from edge_logs - cross join unnest(metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.headers) as h -where (path like '%storage/v1/object/%' or path like '%storage/v1/render/%') and r.method = 'GET' -group by r.path, r.method + cross join unnest(metadata) as metadata + cross join unnest(metadata.request) as request + cross join unnest(metadata.response) as response + cross join unnest(response.headers) as responseHeaders +where + (path like '%storage/v1/object/%' or path like '%storage/v1/render/%') + and request.method = 'GET' +group by 1, 2, 3 order by num_requests desc limit 100; ``` Example of the output: -``` +```json [ - {"filepath":"/storage/v1/object/sign/large%20bucket/20230902_200037.gif", - "http_verb":"GET", - "num_requests":100 - }, - {"filepath":"/storage/v1/object/public/demob/Sports/volleyball.png", - "http_verb":"GET", - "num_requests":168 - } + { + "filepath": "/storage/v1/object/sign/large%20bucket/20230902_200037.gif", + "http_verb": "GET", + "cached": true, + "num_requests": 100 + }, + { + "filepath": "/storage/v1/object/public/demob/Sports/volleyball.png", + "http_verb": "GET", + "cached": false, + "num_requests": 168 + } ] ``` -### Calculating egress: +### Calculating egress If you already know the size of those files, you can calculate the egress by multiplying the number of requests by the size of the file. You can also get the size of the file with the following cURL: @@ -67,6 +75,6 @@ Total Egress = 395.76MB You can see that these values can get quite large, so it's important to keep track of the egress and optimize the files. -### Optimizing egress: +### Optimizing egress -If you are on the Pro Plan, you can use the [Supabase Image Transformations](/docs/guides/storage/image-transformations) to optimize the images and reduce the egress. +See our [scaling tips for egress](/docs/guides/storage/production/scaling#egress). diff --git a/apps/docs/content/guides/storage/serving/image-transformations.mdx b/apps/docs/content/guides/storage/serving/image-transformations.mdx index 52a2ad73d2c..952d6a79b11 100644 --- a/apps/docs/content/guides/storage/serving/image-transformations.mdx +++ b/apps/docs/content/guides/storage/serving/image-transformations.mdx @@ -259,7 +259,7 @@ response = supabase.storage.from_('bucket').download( When using the image transformation API, Storage will automatically find the best format supported by the client and return that to the client, without any code change. For instance, if you use Chrome when viewing a JPEG image and using transformation options, you'll see that images are automatically optimized as `webp` images. -As a result, this will lower the bandwidth that you send to your users and your application will load much faster. +As a result, this will lower the egress that you send to your users and your application will load much faster. diff --git a/apps/docs/content/guides/telemetry/logs.mdx b/apps/docs/content/guides/telemetry/logs.mdx index 18073b40cd7..d27742ef11a 100644 --- a/apps/docs/content/guides/telemetry/logs.mdx +++ b/apps/docs/content/guides/telemetry/logs.mdx @@ -227,7 +227,7 @@ const options = { }, }, } -const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', options) +const supabase = createClient('https://xyzcompany.supabase.co', 'publishable-or-anon-key', options) ``` ## Logs Explorer diff --git a/apps/docs/content/guides/telemetry/reports.mdx b/apps/docs/content/guides/telemetry/reports.mdx index 34fbd117671..49ab17a59aa 100644 --- a/apps/docs/content/guides/telemetry/reports.mdx +++ b/apps/docs/content/guides/telemetry/reports.mdx @@ -270,7 +270,7 @@ The Storage report provides visibility into how your Supabase Storage is being u | --------------- | ------------------------------------------ | ------------------------------------------------------ | | Total Requests | Overall request volume to Storage | Traffic patterns and usage trends | | Response Speed | Average response time for storage requests | Performance bottlenecks and optimization opportunities | -| Network Traffic | Ingress and egress bandwidth usage | Data transfer costs and CDN effectiveness | +| Network Traffic | Ingress and egress usage | Data transfer costs and CDN effectiveness | | Request Caching | Cache hit rates and miss patterns | CDN performance and cost optimization | | Top Routes | Most frequently accessed storage paths | Popular content and usage patterns | @@ -305,5 +305,5 @@ The API Gateway report analyzes traffic patterns and performance characteristics | Total Requests | Overall API request volume | Traffic patterns and growth trends | | Response Errors | Error rates with 4XX and 5XX status codes | API reliability and user experience issues | | Response Speed | Average API response times | Performance bottlenecks and optimization targets | -| Network Traffic | Request and response bandwidth usage | Data transfer patterns and cost implications | +| Network Traffic | Request and response egress usage | Data transfer patterns and cost implications | | Top Routes | Most frequently accessed API endpoints | Usage patterns and optimization priorities | diff --git a/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx b/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx index 78cd4f9dde7..974ce7498ac 100644 --- a/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx +++ b/apps/docs/content/troubleshooting/all-about-supabase-egress-a_Sg_e.mdx @@ -31,4 +31,10 @@ While pointing out the exact cause for egress may not be straightforward, there - Reduce the number of queries/calls by optimising client code or use caches to reduce the number of requests/queries being done: https://github.com/psteinroe/supabase-cache-helpers/ - In case of update/insert queries, if you don’t need the entire row to be returned, configure your ORM/queries to not return the entire row - In case of running manual backups through Supavisor, remove unneeded tables and/or reduce the frequency -- For Storage, if you start using the [Smart CDN](https://supabase.com/docs/guides/storage/cdn/smart-cdn) Storage Egress usage can be managed. You can also use the [Supabase Image Transformations](https://supabase.com/docs/guides/storage/image-transformations) to optimize the images and reduce the egress. +- For Storage, if you start using the [Smart CDN](https://supabase.com/docs/guides/storage/cdn/smart-cdn) Storage Egress usage can be reduced. You can also use the [Supabase Image Transformations](https://supabase.com/docs/guides/storage/image-transformations) to optimize the images and reduce the egress. + +**Cached vs uncached egress** + +We differentiate between cached and uncached egress. Cached egress refers to egress that is served via our CDN and hits the cache. Uncached egress, on the other hand, refers to egress that is not served from the cache and requires a fresh request to the origin server. + +Your plan includes a quota for both cached and uncached egress and these are independent. Cached egress is also cheaper in case you exceed your quota. diff --git a/apps/docs/content/troubleshooting/converted-github-account-to-organisation---lost-supabase-account-access-5wsE_1.mdx b/apps/docs/content/troubleshooting/converted-github-account-to-organisation---lost-supabase-account-access-5wsE_1.mdx index 838a4806b2d..6a2fbd47015 100644 --- a/apps/docs/content/troubleshooting/converted-github-account-to-organisation---lost-supabase-account-access-5wsE_1.mdx +++ b/apps/docs/content/troubleshooting/converted-github-account-to-organisation---lost-supabase-account-access-5wsE_1.mdx @@ -1,5 +1,5 @@ --- -title = "Converted Github account to organization - Lost Supabase account access" +title = "Converted GitHub account to organization - Lost Supabase account access" github_url = "https://github.com/orgs/supabase/discussions/21101" date_created = "2024-02-08T01:39:25+00:00" topics = [ "auth" ] diff --git a/apps/docs/content/troubleshooting/how-to-migrate-from-supabase-auth-helpers-to-ssr-package-5NRunM.mdx b/apps/docs/content/troubleshooting/how-to-migrate-from-supabase-auth-helpers-to-ssr-package-5NRunM.mdx index e3ede25bc25..0c324a7d9e6 100644 --- a/apps/docs/content/troubleshooting/how-to-migrate-from-supabase-auth-helpers-to-ssr-package-5NRunM.mdx +++ b/apps/docs/content/troubleshooting/how-to-migrate-from-supabase-auth-helpers-to-ssr-package-5NRunM.mdx @@ -32,7 +32,7 @@ import { createBrowserClient } from '@supabase/ssr'; export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! ); } @@ -45,7 +45,7 @@ export function createClient() { return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { cookies: { getAll() { @@ -78,7 +78,7 @@ export async function updateSession(request: NextRequest) { const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!, { cookies: { getAll() { diff --git a/apps/docs/features/docs/GuidesMdx.template.tsx b/apps/docs/features/docs/GuidesMdx.template.tsx index e7067e6815f..76fbe6d397c 100644 --- a/apps/docs/features/docs/GuidesMdx.template.tsx +++ b/apps/docs/features/docs/GuidesMdx.template.tsx @@ -90,7 +90,9 @@ const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideT )}
- {content && } + {content && ( + x} /> + )} {children}