From bece7836aa8c982ae4729125a693c32d083b1c62 Mon Sep 17 00:00:00 2001 From: Clayton Date: Sun, 11 Jan 2026 20:31:27 -0800 Subject: [PATCH 01/15] Adding "Clayton Kast" to list of contributors (#40191) --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index bde08b9dec2b4..412cfb7ebd664 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -41,6 +41,7 @@ Chris Gwilliams Chris Martin Chris Stockton Chris Ward +Clayton Kast Craig Cannon Cuong Do Danny White From 86f4bea83dfdcfe220899ceba2c50ea15857ec32 Mon Sep 17 00:00:00 2001 From: Sebastien Chapuis Date: Mon, 12 Jan 2026 05:56:05 +0100 Subject: [PATCH 02/15] docs: Fix documentation for `realtime.send` call (#39839) The json payload is the first parameter: https://github.com/supabase/realtime/blob/5290aa117baabcd9d8c324e6c22574c348d35295/lib/realtime/tenants/repo/migrations/20250714121412_broadcast_send_error_logging.ex#L7 --- apps/docs/content/guides/realtime/getting_started.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/guides/realtime/getting_started.mdx b/apps/docs/content/guides/realtime/getting_started.mdx index 7e24d00d2a264..8860fbbf94a06 100644 --- a/apps/docs/content/guides/realtime/getting_started.mdx +++ b/apps/docs/content/guides/realtime/getting_started.mdx @@ -501,14 +501,14 @@ BEGIN -- Send custom notification when new message is created IF TG_OP = 'INSERT' THEN PERFORM realtime.send( - 'room:' || NEW.room_id::text || ':notifications', - 'message_created', jsonb_build_object( 'message_id', NEW.id, 'user_id', NEW.user_id, 'room_id', NEW.room_id, 'created_at', NEW.created_at ), + 'message_created', + 'room:' || NEW.room_id::text || ':notifications', true -- private channel ); END IF; From 3643bb87708c327245cb6d749e8ea081fed967fc Mon Sep 17 00:00:00 2001 From: Bhavesh Muleva <66737092+Bhavesh-Muleva@users.noreply.github.com> Date: Mon, 12 Jan 2026 10:27:42 +0530 Subject: [PATCH 03/15] docs: Update jwts.mdx (#39423) fixed JWT page in doc Co-authored-by: Chris Chinchilla From ebfc40f4ea7e86e03ba82e311277258fdefdd693 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 12 Jan 2026 13:05:36 +0800 Subject: [PATCH 04/15] fix(studio): Twitter (Deprecated) oauth provider enabled status (#41844) fix(studio): twitter (deprecated) oauth provider enabled status --- .../components/interfaces/Auth/AuthProvidersForm/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx index cd57b5140c9fc..2594920618123 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/index.tsx @@ -107,6 +107,8 @@ export const AuthProvidersForm = () => { isActive = authConfig && (authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] } else if (providerSchema.title.includes('X / Twitter (OAuth 2.0)')) { isActive = authConfig && (authConfig as any)['EXTERNAL_X_ENABLED'] + } else if (providerSchema.title === 'Twitter (Deprecated)') { + isActive = authConfig && (authConfig as any)['EXTERNAL_TWITTER_ENABLED'] } else { isActive = authConfig && From 87d66136204f4144aac6cece43f694dec65d30b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 12 Jan 2026 12:26:56 +0700 Subject: [PATCH 05/15] chore: move cached egress under storage on pricing page (#41815) --- .../components/Pricing/PricingTableRow.tsx | 4 ++-- packages/shared-data/pricing.ts | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/www/components/Pricing/PricingTableRow.tsx b/apps/www/components/Pricing/PricingTableRow.tsx index e4a72efa18472..460833a24b138 100644 --- a/apps/www/components/Pricing/PricingTableRow.tsx +++ b/apps/www/components/Pricing/PricingTableRow.tsx @@ -38,8 +38,8 @@ export const pricingTooltips: PricingTooltips = { 'database.egress': { main: 'Billing is based on the total sum of all outgoing traffic (includes Database, Storage, Realtime, Auth, API, Edge Functions, Supavisor, Log Drains) in GB throughout your billing period. Excludes cache hits.', }, - 'database.cachedEgress': { - main: 'Billing is based on the total sum of any outgoing traffic (includes Database, Storage, API, Edge Functions) in GB throughout your billing period that is served from our CDN cache.', + 'storage.cachedEgress': { + main: 'Billing is based on the total sum of outgoing Storage traffic in GB throughout your billing period that is served from our CDN cache.', }, 'auth.totalUsers': { main: 'The maximum number of users your project can have', diff --git a/packages/shared-data/pricing.ts b/packages/shared-data/pricing.ts index 888d3cd7dcf5e..495798f6e3863 100644 --- a/packages/shared-data/pricing.ts +++ b/packages/shared-data/pricing.ts @@ -37,7 +37,6 @@ export type FeatureKey = | 'database.pausing' | 'database.branching' | 'database.egress' - | 'database.cachedEgress' | 'auth.totalUsers' | 'auth.maus' | 'auth.userDataOwnership' @@ -58,6 +57,7 @@ export type FeatureKey = | 'storage.size' | 'storage.customAccessControls' | 'storage.maxFileSize' + | 'storage.cachedEgress' | 'storage.cdn' | 'storage.transformations' | 'storage.byoc' @@ -194,17 +194,6 @@ export const pricing: Pricing = { }, usage_based: true, }, - { - key: 'database.cachedEgress', - title: 'Cached Egress', - plans: { - free: '5 GB included', - pro: ['250 GB included', 'then $0.03 per GB'], - team: ['250 GB included', 'then $0.03 per GB'], - enterprise: 'Custom', - }, - usage_based: true, - }, ], }, auth: { @@ -418,6 +407,17 @@ export const pricing: Pricing = { }, usage_based: true, }, + { + key: 'storage.cachedEgress', + title: 'Cached Egress', + plans: { + free: '5 GB included', + pro: ['250 GB included', 'then $0.03 per GB'], + team: ['250 GB included', 'then $0.03 per GB'], + enterprise: 'Custom', + }, + usage_based: true, + }, { key: 'storage.customAccessControls', title: 'Custom access controls', From 32e1fedc79fdc352f9bc0d2a123e8d32b0d58776 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 12 Jan 2026 13:30:04 +0800 Subject: [PATCH 06/15] (chore) studIo: Refactor use query state with select part 01 (#41819) * Refactor to remove usage of useQueryStateWithSelect in auth policies and vector bucket * Refactor to remove usage of useQueryStateWithSelect in edge functionn secrets --- .../EdgeFunctionSecrets.tsx | 82 +++++++++++-------- .../VectorBuckets/DeleteVectorTableModal.tsx | 57 +++++++++---- .../VectorBucketDetails/index.tsx | 35 +++----- apps/studio/components/ui/SchemaSelector.tsx | 2 +- .../pages/project/[ref]/auth/policies.tsx | 43 +++++----- 5 files changed, 123 insertions(+), 96 deletions(-) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx index 71387ce21f242..dc0c0c866adf5 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx @@ -1,6 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Search } from 'lucide-react' -import { useRef, useState } from 'react' +import { useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' @@ -9,7 +9,7 @@ import NoPermission from 'components/ui/NoPermission' import { useSecretsDeleteMutation } from 'data/secrets/secrets-delete-mutation' import { useSecretsQuery } from 'data/secrets/secrets-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { handleErrorOnDelete, useQueryStateWithSelect } from 'hooks/misc/useQueryStateWithSelect' +import { parseAsString, useQueryState } from 'nuqs' import { Badge, Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' @@ -22,9 +22,6 @@ export const EdgeFunctionSecrets = () => { const { ref: projectRef } = useParams() const [searchString, setSearchString] = useState('') - // Track the ID being deleted to exclude it from error checking - const deletingSecretNameRef = useRef(null) - const { can: canReadSecrets, isLoading: isLoadingSecretsPermissions } = useAsyncCheckPermissions( PermissionAction.FUNCTIONS_SECRET_READ, '*' @@ -32,7 +29,7 @@ export const EdgeFunctionSecrets = () => { const { can: canUpdateSecrets } = useAsyncCheckPermissions(PermissionAction.SECRETS_WRITE, '*') const { - data, + data = [], error, isPending: isLoading, isSuccess, @@ -44,32 +41,26 @@ export const EdgeFunctionSecrets = () => { { enabled: canReadSecrets } ) - const { setValue: setSelectedSecretToEdit, value: selectedSecretToEdit } = - useQueryStateWithSelect({ - urlKey: 'edit', - select: (secretName: string) => - secretName ? data?.find((secret) => secret.name === secretName) : undefined, - enabled: !!data, - onError: () => toast.error(`Secret not found`), - }) - - const { setValue: setSelectedSecretToDelete, value: selectedSecretToDelete } = - useQueryStateWithSelect({ - urlKey: 'delete', - select: (secretName: string) => - secretName ? data?.find((secret) => secret.name === secretName) : undefined, - enabled: !!data, - onError: (_error, selectedId) => - handleErrorOnDelete(deletingSecretNameRef, selectedId, `Secret not found`), - }) - - const { mutate: deleteSecret, isPending: isDeleting } = useSecretsDeleteMutation({ + const [selectedIdToEdit, setSelectedIdToEdit] = useQueryState( + 'edit', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) + const selectedSecretToEdit = data.find((secret) => secret.name === selectedIdToEdit) + + const [selectedIdToDelete, setSelectedIdToDelete] = useQueryState( + 'delete', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) + const selectedSecretToDelete = data.find((secret) => secret.name === selectedIdToDelete) + + const { + mutate: deleteSecret, + isPending: isDeleting, + isSuccess: isSuccessDelete, + } = useSecretsDeleteMutation({ onSuccess: (_, variables) => { toast.success(`Successfully deleted secret “${variables.secrets[0]}”`) - setSelectedSecretToDelete(null) - }, - onError: () => { - deletingSecretNameRef.current = null + setSelectedIdToDelete(null) }, }) @@ -90,6 +81,26 @@ export const EdgeFunctionSecrets = () => { const showLoadingState = isLoadingSecretsPermissions || (canReadSecrets && isLoading) + useEffect(() => { + if (!!selectedIdToEdit && isSuccess && !selectedSecretToEdit) { + toast(`Secret ${selectedIdToEdit} cannot be found`) + setSelectedIdToEdit(null) + } + }, [isSuccess, selectedIdToEdit, selectedSecretToEdit, setSelectedIdToEdit]) + + useEffect(() => { + if (!!selectedIdToDelete && isSuccess && !selectedSecretToDelete && !isSuccessDelete) { + toast(`Secret ${selectedIdToDelete} cannot be found`) + setSelectedIdToDelete(null) + } + }, [ + isSuccess, + isSuccessDelete, + selectedIdToDelete, + selectedSecretToDelete, + setSelectedIdToDelete, + ]) + return ( <> {showLoadingState ? ( @@ -119,7 +130,7 @@ export const EdgeFunctionSecrets = () => { className="w-full md:w-80" placeholder="Search for a secret" value={searchString} - onChange={(e: any) => setSearchString(e.target.value)} + onChange={(e) => setSearchString(e.target.value)} icon={} /> @@ -135,8 +146,8 @@ export const EdgeFunctionSecrets = () => { setSelectedSecretToEdit(secret.name)} - onSelectDelete={() => setSelectedSecretToDelete(secret.name)} + onSelectEdit={() => setSelectedIdToEdit(secret.name)} + onSelectDelete={() => setSelectedIdToDelete(secret.name)} /> )) ) : secrets.length === 0 && searchString.length > 0 ? ( @@ -171,7 +182,7 @@ export const EdgeFunctionSecrets = () => { setSelectedSecretToEdit(null)} + onClose={() => setSelectedIdToEdit(null)} /> { confirmLabel="Delete secret" confirmLabelLoading="Deleting secret" title={`Delete secret “${selectedSecretToDelete?.name}”`} - onCancel={() => setSelectedSecretToDelete(null)} + onCancel={() => setSelectedIdToDelete(null)} onConfirm={() => { if (selectedSecretToDelete) { - deletingSecretNameRef.current = selectedSecretToDelete.name deleteSecret({ projectRef, secrets: [selectedSecretToDelete.name] }) } }} diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorTableModal.tsx b/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorTableModal.tsx index 352fbcc6e20b8..c1677b0ce952c 100644 --- a/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorTableModal.tsx +++ b/apps/studio/components/interfaces/Storage/VectorBuckets/DeleteVectorTableModal.tsx @@ -1,27 +1,31 @@ +import { parseAsString, useQueryState } from 'nuqs' +import { useEffect } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import { useFDWDropForeignTableMutation } from 'data/fdw/fdw-drop-foreign-table-mutation' import { useVectorBucketIndexDeleteMutation } from 'data/storage/vector-bucket-index-delete-mutation' -import { VectorBucketIndex } from 'data/storage/vector-buckets-indexes-query' +import { useVectorBucketsIndexesQuery } from 'data/storage/vector-buckets-indexes-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' +import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal' import { useS3VectorsWrapperInstance } from './useS3VectorsWrapperInstance' -interface DeleteVectorTableModalProps { - visible: boolean - table?: VectorBucketIndex - onClose: () => void -} - -export const DeleteVectorTableModal = ({ - visible, - table, - onClose, -}: DeleteVectorTableModalProps) => { - const { bucketId } = useParams() +export const DeleteVectorTableModal = () => { + const { ref: projectRef, bucketId } = useParams() const { data: project } = useSelectedProjectQuery() + const [selectedTableIdToDelete, setSelectedTableIdToDelete] = useQueryState( + 'deleteTable', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) + + const { data, isSuccess: isSuccessIndexes } = useVectorBucketsIndexesQuery({ + projectRef, + vectorBucketName: bucketId, + }) + const allIndexes = data?.indexes ?? [] + const table = allIndexes.find((index) => index.indexName === selectedTableIdToDelete) + const { data: wrapperInstance } = useS3VectorsWrapperInstance({ bucketId }) const foreignTable = wrapperInstance?.tables?.find((x) => x.name === table?.indexName) @@ -29,7 +33,11 @@ export const DeleteVectorTableModal = ({ onError: () => {}, }) - const { mutate: deleteIndex, isPending: isDeleting } = useVectorBucketIndexDeleteMutation({ + const { + mutate: deleteIndex, + isPending: isDeleting, + isSuccess: isSuccessDelete, + } = useVectorBucketIndexDeleteMutation({ onSuccess: (_, vars) => { try { if (!!foreignTable) { @@ -41,7 +49,7 @@ export const DeleteVectorTableModal = ({ }) } toast.success(`Table "${vars.indexName}" deleted successfully`) - onClose() + setSelectedTableIdToDelete(null) } catch (error: any) { toast.success( `Table "${vars.indexName}" deleted successfully, but its corresponding foreign table failed to clean up: ${error.message}` @@ -61,14 +69,27 @@ export const DeleteVectorTableModal = ({ }) } + useEffect(() => { + if (!!selectedTableIdToDelete && isSuccessIndexes && !table && !isSuccessDelete) { + toast(`Table ${selectedTableIdToDelete} cannot be found in your bucket`) + setSelectedTableIdToDelete(null) + } + }, [ + isSuccessIndexes, + selectedTableIdToDelete, + table, + setSelectedTableIdToDelete, + isSuccessDelete, + ]) + return ( setSelectedTableIdToDelete(null)} > {/* [Joshen] Can probably beef up more details here - what are potential side effects of deleting a table */}

This action cannot be undone.

diff --git a/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx b/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx index 9388f689dfd9b..37a1df3714730 100644 --- a/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx +++ b/apps/studio/components/interfaces/Storage/VectorBuckets/VectorBucketDetails/index.tsx @@ -1,8 +1,8 @@ import { MoreVertical, Search, Trash2 } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' -import { parseAsBoolean, useQueryState } from 'nuqs' -import { useRef, useState } from 'react' +import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' +import { useState } from 'react' import { useParams } from 'common' import { @@ -15,7 +15,6 @@ import { import AlertError from 'components/ui/AlertError' import { useVectorBucketQuery } from 'data/storage/vector-bucket-query' import { useVectorBucketsIndexesQuery } from 'data/storage/vector-buckets-indexes-query' -import { handleErrorOnDelete, useQueryStateWithSelect } from 'hooks/misc/useQueryStateWithSelect' import { SqlEditor, TableEditor } from 'icons' import { Button, @@ -54,14 +53,15 @@ export const VectorBucketDetails = () => { const { ref: projectRef, bucketId } = useParams() const { data: _bucket, isSuccess } = useSelectedVectorBucket() - // Track the ID being deleted to exclude it from error checking - const deletingTableIdRef = useRef(null) - const [filterString, setFilterString] = useState('') const [showDeleteModal, setShowDeleteModal] = useQueryState( 'delete', parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true }) ) + const [_, setSelectedTableIdToDelete] = useQueryState( + 'deleteTable', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) const { data: bucket, @@ -73,21 +73,16 @@ export const VectorBucketDetails = () => { { enabled: isSuccess && !!_bucket } ) - const { data, isPending: isLoadingIndexes } = useVectorBucketsIndexesQuery({ + const { + data, + isPending: isLoadingIndexes, + isSuccess: isSuccessIndexes, + } = useVectorBucketsIndexesQuery({ projectRef, vectorBucketName: bucket?.vectorBucketName, }) const allIndexes = data?.indexes ?? [] - const { setValue: setSelectedTableToDelete, value: selectedTableToDelete } = - useQueryStateWithSelect({ - urlKey: 'deleteTable', - select: (id: string) => (id ? allIndexes.find((index) => index.indexName === id) : undefined), - enabled: !!allIndexes.length, - onError: (_error, selectedId) => - handleErrorOnDelete(deletingTableIdRef, selectedId, `Table not found`), - }) - const filteredList = filterString.length === 0 ? allIndexes @@ -280,7 +275,7 @@ export const VectorBucketDetails = () => { className="flex items-center space-x-2" onClick={(e) => { e.stopPropagation() - setSelectedTableToDelete(index.indexName) + setSelectedTableIdToDelete(index.indexName) }} > @@ -326,11 +321,7 @@ export const VectorBucketDetails = () => { )} - setSelectedTableToDelete(null)} - /> + { parseAsString.withDefault('').withOptions({ history: 'replace', clearOnDefault: true }) ) const deferredSearchString = useDeferredValue(searchString) + + const [selectedIdToEdit, setSelectedIdToEdit] = useQueryState( + 'edit', + parseAsString.withOptions({ history: 'push', clearOnDefault: true }) + ) + const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() const { data: postgrestConfig } = useProjectPostgrestConfigQuery({ projectRef: project?.ref }) @@ -128,23 +133,16 @@ const AuthPoliciesPage: NextPageWithLayout = () => { ) const { - data: policies, + data: policies = [], isPending: isLoadingPolicies, isError: isPoliciesError, + isSuccess: isPoliciesSuccess, error: policiesError, } = useDatabasePoliciesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) - - const { setValue: setSelectedPolicyIdToEdit, value: selectedPolicyIdToEdit } = - useQueryStateWithSelect({ - urlKey: 'edit', - select: (id: string) => - id ? policies?.find((policy) => policy.id.toString() === id) : undefined, - enabled: !!policies, - onError: () => toast.error(`Policy not found`), - }) + const selectedPolicyToEdit = policies.find((policy) => policy.id.toString() === selectedIdToEdit) const { data: tables, @@ -179,7 +177,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { const handleSelectCreatePolicy = useCallback( (table: string) => { setSelectedTable(table) - setSelectedPolicyIdToEdit(null) + setSelectedIdToEdit(null) setShowCreatePolicy(true) if (isInlineEditorEnabled) { @@ -219,7 +217,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { setEditorPanelTemplates(templates) openSidebar(SIDEBAR_KEYS.EDITOR_PANEL) } else { - setSelectedPolicyIdToEdit(policy.id.toString()) + setSelectedIdToEdit(policy.id.toString()) } }, [isInlineEditorEnabled, openSidebar] @@ -252,7 +250,14 @@ const AuthPoliciesPage: NextPageWithLayout = () => { isRlsBannerDismissed, ]) - const isUpdatingPolicy = !!selectedPolicyIdToEdit + useEffect(() => { + if (selectedIdToEdit && isPoliciesSuccess && !selectedPolicyToEdit) { + toast(`Policy ID ${selectedIdToEdit} cannot be found`) + setSelectedIdToEdit(null) + } + }, [selectedIdToEdit, selectedPolicyToEdit, isPoliciesSuccess, setSelectedIdToEdit]) + + const isUpdatingPolicy = !!selectedIdToEdit if (isPermissionsLoaded && !canReadPolicies) { return @@ -340,15 +345,15 @@ const AuthPoliciesPage: NextPageWithLayout = () => { )} { setSelectedTable(undefined) if (isUpdatingPolicy) { - setSelectedPolicyIdToEdit(null) + setSelectedIdToEdit(null) } else { setShowCreatePolicy(false) } From 8705a2f2fd8825ad486dfbce8ed0406fc849bd5f Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Mon, 12 Jan 2026 00:34:27 -0500 Subject: [PATCH 07/15] fix(studio): warn when wrapper is changed (#41845) When a wrapper is changed (e.g., by changing a table name, the entire wrapper is destroyed and recreated. This also destroys dependent objects like functions, but the user is not warned. Add a confirmation modal warning that this will happen. --- .../Wrappers/EditWrapperSheet.tsx | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx index c659e6d4e3935..42cf788d56051 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/EditWrapperSheet.tsx @@ -62,6 +62,8 @@ export const EditWrapperSheet = ({ undefined ) const [formErrors, setFormErrors] = useState<{ [k: string]: string }>({}) + const [isUpdateConfirmationOpen, setIsUpdateConfirmationOpen] = useState(false) + const [pendingFormState, setPendingFormState] = useState | null>(null) const hasChangesRef = useRef(false) const initialValues = { @@ -94,16 +96,14 @@ export const EditWrapperSheet = ({ if (wrapper_name.length === 0) errors.name = 'Please provide a name for your wrapper' if (!wrapperMeta.canTargetSchema && wrapperTables.length === 0) errors.tables = 'Please add at least one table' - if (!isEmpty(errors)) return setFormErrors(errors) + if (!isEmpty(errors)) { + setFormErrors(errors) + return + } - updateFDW({ - projectRef: project?.ref, - connectionString: project?.connectionString, - wrapper, - wrapperMeta, - formState: { ...values, server_name: `${wrapper_name}_server` }, - tables: wrapperTables, - }) + setFormErrors({}) + setPendingFormState({ ...values, server_name: `${wrapper_name}_server` }) + setIsUpdateConfirmationOpen(true) } const checkIsDirty = useCallback(() => hasChangesRef.current, []) @@ -362,6 +362,41 @@ export const EditWrapperSheet = ({ + { + setIsUpdateConfirmationOpen(false) + setPendingFormState(null) + onClose() + }} + onConfirm={() => { + if (pendingFormState === null) return + updateFDW({ + projectRef: project?.ref, + connectionString: project?.connectionString, + wrapper, + wrapperMeta, + formState: pendingFormState, + tables: wrapperTables, + }) + setIsUpdateConfirmationOpen(false) + setPendingFormState(null) + }} + > +

+ Saving changes will drop the existing wrapper and recreate it. Foreign servers and tables + will be recreated, and dependent objects like functions or views that reference those + tables may need to be updated manually afterwards. +

+

Are you sure you want to continue?

+
+ Date: Mon, 12 Jan 2026 12:49:35 +0700 Subject: [PATCH 08/15] docs: Add 'pgjwt' to spelling rule list (#41847) Add 'pgjwt' to spelling rule list --- supa-mdx-lint/Rule003Spelling.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index 912a50d853111..33579159a53d2 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -268,6 +268,7 @@ allow_list = [ "PGAudit", "PGroonga", "PgBouncer", + "pgjwt", "PHI", "Pico", "PingIdentity", From e164b108c0a8799a1656c70d478932194953e5fd Mon Sep 17 00:00:00 2001 From: Jack <101360909+mahj79@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:23:47 -0600 Subject: [PATCH 09/15] =?UTF-8?q?docs:=20Update=20pgjwt=20documentation=20?= =?UTF-8?q?with=20link=20to=20Supabase=20JWT=20Handling=20Documen=E2=80=A6?= =?UTF-8?q?=20(#40272)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update pgjwt documentation with link to Supabase JWT Handling Documentation Added notes about JWT handling in Supabase. It might seem stupid but for whatever reason I though I needed the extension turned on and delayed upgrading my database because of this. I read through the documentation and finally realized it has zero impact on the JWT functionality built in. Figured someone else out there may not realize this and is delaying upgrading theis postgres version, so I added a note at the top clarifying how Supabase handles JWT with a link to the documentation. * Update apps/docs/content/guides/database/extensions/pgjwt.mdx * Update pgjwt documentation regarding Postgres versions Clarified information about pgjwt extension usage based on Postgres version. --------- Co-authored-by: Chris Chinchilla Co-authored-by: Chris Chinchilla --- apps/docs/content/guides/database/extensions/pgjwt.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/guides/database/extensions/pgjwt.mdx b/apps/docs/content/guides/database/extensions/pgjwt.mdx index 2c03381b09931..ae9d72898fe0d 100644 --- a/apps/docs/content/guides/database/extensions/pgjwt.mdx +++ b/apps/docs/content/guides/database/extensions/pgjwt.mdx @@ -4,7 +4,11 @@ title: 'pgjwt: JSON Web Tokens' description: 'Encode and decode JWTs in PostgreSQL' --- -{/* supa-mdx-lint-disable-next-line Rule004ExcludeWords */} + + +Supabase creates and handles JWT for you. It is built into the platform. **If you use Postgres version 15 or earlier**, you don't need the pgjwt extension, and it is safe to disable. For more information on how Supabase handles JWTs, read the [Supabase and JWTs documentation](/docs/guides/auth/jwts#supabase-and-jwts) + + From 718fa372d4bac11d0b507bd82a9daf284eb12e9b Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Mon, 12 Jan 2026 03:52:21 -0500 Subject: [PATCH 10/15] fix(studio): functions egress not showing in daily breakdown chart (#41848) The daily breakdown chart doesn't show functions egress because the key we're looking for doesn't match the key being sent by API. --- apps/studio/data/analytics/org-daily-stats-query.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/studio/data/analytics/org-daily-stats-query.ts b/apps/studio/data/analytics/org-daily-stats-query.ts index d2742f603d681..536210d65009f 100644 --- a/apps/studio/data/analytics/org-daily-stats-query.ts +++ b/apps/studio/data/analytics/org-daily-stats-query.ts @@ -10,7 +10,7 @@ export enum EgressType { AUTH = 'egress_auth', STORAGE = 'egress_storage', REALTIME = 'egress_realtime', - FUNCTIONS = 'egress_functions', + FUNCTIONS = 'egress_function', SUPAVISOR = 'egress_supavisor', LOGDRAIN = 'egress_logdrain', } From 140e74435dcf3f19035781ebc2c6712d7d9d9e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 12 Jan 2026 16:03:25 +0700 Subject: [PATCH 11/15] docs: rename Realtime quotas to Limits (#41816) We use the quota term for actual billing quotas, so using "Quota" as a term for configurable Realtime limits is confusing. While there are varying per-plan limits, they are not used for billing. Realtime Quotas on plans refer to Realtime Messages and Realtime Peak Connections --- .../NavigationMenu.constants.ts | 2 +- .../guides/deployment/going-into-prod.mdx | 6 +- .../guides/realtime/getting_started.mdx | 2 +- apps/docs/content/guides/realtime/limits.mdx | 63 +++++++++++++++++++ apps/docs/content/guides/realtime/quotas.mdx | 63 ------------------- .../content/guides/storage/vector/limits.mdx | 2 +- .../troubleshooting/exhaust-disk-io.mdx | 2 +- ...ncurrent-peak-connections-quota-jdDqcp.mdx | 2 +- .../Organization/Usage/Usage.constants.tsx | 8 +-- apps/www/lib/redirects.js | 9 ++- 10 files changed, 82 insertions(+), 77 deletions(-) create mode 100644 apps/docs/content/guides/realtime/limits.mdx delete mode 100644 apps/docs/content/guides/realtime/quotas.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 3290cb1f14688..977fb6993628e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1871,7 +1871,7 @@ export const realtime: NavMenuConstant = { name: 'Deep dive', url: undefined, items: [ - { name: 'Quotas', url: '/guides/realtime/quotas', enabled: billingEnabled }, + { name: 'Limits', url: '/guides/realtime/limits', enabled: billingEnabled }, { name: 'Pricing', url: '/guides/realtime/pricing' as `/${string}`, diff --git a/apps/docs/content/guides/deployment/going-into-prod.mdx b/apps/docs/content/guides/deployment/going-into-prod.mdx index ed14427080c7b..1b23ede39a9b0 100644 --- a/apps/docs/content/guides/deployment/going-into-prod.mdx +++ b/apps/docs/content/guides/deployment/going-into-prod.mdx @@ -84,10 +84,10 @@ After developing your project and deciding it's production ready, you should run | Create or Verify an MFA challenge | `/auth/v1/factors/:id/challenge` `/auth/v1/factors/:id/verify` | IP Address | 15 requests per minute (with bursts up to 30 requests) | | Anonymous sign-ins | `/auth/v1/signup`[^2] | IP Address | 30 requests per hour (with bursts up to 30 requests) | -### Realtime quotas +### Realtime limits -- Review the [Realtime quotas](/docs/guides/realtime/quotas). -- If you need quotas increased you can always [contact support](/dashboard/support/new). +- Review the [Realtime limits](/docs/guides/realtime/limits). +- If you need limits increased you can always [contact support](/dashboard/support/new). ### Abuse prevention diff --git a/apps/docs/content/guides/realtime/getting_started.mdx b/apps/docs/content/guides/realtime/getting_started.mdx index 8860fbbf94a06..b473d2abcf94c 100644 --- a/apps/docs/content/guides/realtime/getting_started.mdx +++ b/apps/docs/content/guides/realtime/getting_started.mdx @@ -688,7 +688,7 @@ Now that you understand the basics, dive deeper into each feature: - **[Architecture](/docs/guides/realtime/architecture)** - Understand how Realtime works under the hood - **[Benchmarks](/docs/guides/realtime/benchmarks)** - Performance characteristics and scaling considerations -- **[Quotas](/docs/guides/realtime/quotas)** - Usage limits and best practices +- **[Limits](/docs/guides/realtime/limits)** - Usage limits and best practices ### Integration guides diff --git a/apps/docs/content/guides/realtime/limits.mdx b/apps/docs/content/guides/realtime/limits.mdx new file mode 100644 index 0000000000000..5aea01f820251 --- /dev/null +++ b/apps/docs/content/guides/realtime/limits.mdx @@ -0,0 +1,63 @@ +--- +id: 'limits' +title: 'Realtime Limits' +description: 'Understanding Realtime limits' +sidebar_label: 'Limits' +--- + +Our cluster supports millions of concurrent connections and message throughput for production workloads. + + + +Upgrade your plan to increase your limits. Without a spend cap, or on an Enterprise plan, some limits are still in place to protect budgets. All limits are configurable per project. [Contact support](/dashboard/support/new) if you need your limits increased. + + + +## Limits by plan + +| | Free | Pro | Pro (no spend cap) | Team | Enterprise | +| ----------------------------------------------------------------------------------- | -------- | -------- | ------------------ | -------- | ---------- | +| **Concurrent connections** | 200 | 500 | 10,000 | 10,000 | 10,000+ | +| **Messages per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ | +| **Channel joins per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ | +| **Channels per connection** | 100 | 100 | 100 | 100 | 100+ | +| **Presence keys per object** | 10 | 10 | 10 | 10 | 10+ | +| **Presence messages per second** | 20 | 50 | 1,000 | 1,000 | 1,000+ | +| **Broadcast payload size** | 256 KB | 3,000 KB | 3,000 KB | 3,000 KB | 3,000+ KB | +| **Postgres change payload size ([**read more**](#postgres-changes-payload-limit))** | 1,024 KB | 1,024 KB | 1,024 KB | 1,024 KB | 1,024+ KB | + +Beyond the Free and Pro Plan you can customize your limits by [contacting support](/dashboard/support/new). + +## Limit errors + +When you exceed a limit, errors will appear in the backend logs and client-side messages in the WebSocket connection. + +- **Logs**: check the [Realtime logs](/dashboard/project/_/database/realtime-logs) inside your project Dashboard. +- **WebSocket errors**: Use your browser's developer tools to find the WebSocket initiation request and view individual messages. + + + +You can use the [Realtime Inspector](https://realtime.supabase.com/inspector/new) to reproduce an error and share those connection details with Supabase support. + + +Some limits can cause a Channel join to be refused. Realtime will reply with one of the following WebSocket messages: + +### `too_many_channels` + +Too many channels currently joined for a single connection. + +### `too_many_connections` + +Too many total concurrent connections for a project. + +### `too_many_joins` + +Too many Channel joins per second. + +### `tenant_events` + +Connections will be disconnected if your project is generating too many messages per second. `supabase-js` will reconnect automatically when the message throughput decreases below your plan limit. An `event` is a WebSocket message delivered to, or sent from a client. + +## Postgres changes payload limit + +When this limit is reached, the `new` and `old` record payloads only include the fields with a value size of less than or equal to 64 bytes. diff --git a/apps/docs/content/guides/realtime/quotas.mdx b/apps/docs/content/guides/realtime/quotas.mdx deleted file mode 100644 index a7b4a7e6d937f..0000000000000 --- a/apps/docs/content/guides/realtime/quotas.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -id: 'quotas' -title: 'Realtime Quotas' -description: 'Understanding Realtime quotas' -sidebar_label: 'Quotas' ---- - -Our cluster supports millions of concurrent connections and message throughput for production workloads. - - - -Upgrade your plan to increase your quotas. Without a spend cap, or on an Enterprise plan, some quotas are still in place to protect budgets. All quotas are configurable per project. [Contact support](/dashboard/support/new) if you need your quotas increased. - - - -## Quotas by plan - -| | Free | Pro | Pro (no spend cap) | Team | Enterprise | -| -------------------------------------------------------------------------------------- | ----- | ----- | ------------------ | ------ | ---------- | -| **Concurrent connections** | 200 | 500 | 10,000 | 10,000 | 10,000+ | -| **Messages per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ | -| **Channel joins per second** | 100 | 500 | 2,500 | 2,500 | 2,500+ | -| **Channels per connection** | 100 | 100 | 100 | 100 | 100+ | -| **Presence keys per object** | 10 | 10 | 10 | 10 | 10+ | -| **Presence messages per second** | 20 | 50 | 1,000 | 1,000 | 1,000+ | -| **Broadcast payload size KB** | 256 | 3,000 | 3,000 | 3,000 | 3,000+ | -| **Postgres change payload size KB ([**read more**](#postgres-changes-payload-quota))** | 1,024 | 1,024 | 1,024 | 1,024 | 1,024+ | - -Beyond the Free and Pro Plan you can customize your quotas by [contacting support](/dashboard/support/new). - -## Quota errors - -When you exceed a quota, errors will appear in the backend logs and client-side messages in the WebSocket connection. - -- **Logs**: check the [Realtime logs](/dashboard/project/_/database/realtime-logs) inside your project Dashboard. -- **WebSocket errors**: Use your browser's developer tools to find the WebSocket initiation request and view individual messages. - - - -You can use the [Realtime Inspector](https://realtime.supabase.com/inspector/new) to reproduce an error and share those connection details with Supabase support. - - -Some quotas can cause a Channel join to be refused. Realtime will reply with one of the following WebSocket messages: - -### `too_many_channels` - -Too many channels currently joined for a single connection. - -### `too_many_connections` - -Too many total concurrent connections for a project. - -### `too_many_joins` - -Too many Channel joins per second. - -### `tenant_events` - -Connections will be disconnected if your project is generating too many messages per second. `supabase-js` will reconnect automatically when the message throughput decreases below your plan quota. An `event` is a WebSocket message delivered to, or sent from a client. - -## Postgres changes payload quota - -When this quota is reached, the `new` and `old` record payloads only include the fields with a value size of less than or equal to 64 bytes. diff --git a/apps/docs/content/guides/storage/vector/limits.mdx b/apps/docs/content/guides/storage/vector/limits.mdx index 0b0883367af39..71bacfcf935fd 100644 --- a/apps/docs/content/guides/storage/vector/limits.mdx +++ b/apps/docs/content/guides/storage/vector/limits.mdx @@ -1,6 +1,6 @@ --- title: 'Vector Bucket Limits' -subtitle: 'Understanding capacity, quotas, and billing for vector buckets.' +subtitle: 'Understanding capacity, limits, and billing for vector buckets.' --- diff --git a/apps/docs/content/troubleshooting/exhaust-disk-io.mdx b/apps/docs/content/troubleshooting/exhaust-disk-io.mdx index b397180a60899..9a9b1597f5d9f 100644 --- a/apps/docs/content/troubleshooting/exhaust-disk-io.mdx +++ b/apps/docs/content/troubleshooting/exhaust-disk-io.mdx @@ -9,7 +9,7 @@ database_id = "4844905d-1456-44a1-858e-7a4995e5054c" Disk IO refers to two metrics: throughput in Megabits per Second and IOPS which are Input/Output Operations per Second. Depending on the compute add-on of your instance you will have [different baseline performances](/docs/guides/platform/compute-add-ons#compute-size). -Smaller compute instances can burst and exceed their baseline performance for a short quota of time every day. This is represented as your Disk IO Budget and once your Disk IO Budget is consumed, your instance reverts back to its baseline performance. Learn more about [choosing the right compute instance for consistent disk performance](/docs/guides/platform/compute-add-ons#choosing-the-right-compute-instance-for-consistent-disk-performance). +Smaller compute instances can burst and exceed their baseline performance for a short period of time every day. This is represented as your Disk IO Budget and once your Disk IO Budget is consumed, your instance reverts back to its baseline performance. Learn more about [choosing the right compute instance for consistent disk performance](/docs/guides/platform/compute-add-ons#choosing-the-right-compute-instance-for-consistent-disk-performance). ## Depleting your disk IO budget diff --git a/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx b/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx index be05dc98a95ac..2cacce34d5b06 100644 --- a/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx +++ b/apps/docs/content/troubleshooting/realtime-concurrent-peak-connections-quota-jdDqcp.mdx @@ -12,4 +12,4 @@ For example, if you have a chat application that uses Supabase Realtime and you This quota applies to all Supabase projects, including self-hosted projects, but you can increase it depending on your use case. For hosted Supabase projects, select the plan that fits your Realtime usage and reach out if you need custom quotas. For those self-hosting Supabase, you can set those limits yourself by setting the `max_concurrent_users` field on the tenant record (see: https://supabase.com/docs/guides/self-hosting/realtime/config). -You can learn more about Realtime quotas here: https://supabase.com/docs/guides/realtime/quotas#quotas-by-plan +You can learn more about Realtime limits here: https://supabase.com/docs/guides/realtime/limits#limits-by-plan diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx index 9ea413993637b..e9a196b921d5f 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx @@ -334,8 +334,8 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ chartDescription: 'The data refreshes every hour.', links: [ { - name: 'Realtime Quotas', - url: `${DOCS_URL}/guides/realtime/quotas`, + name: 'Realtime Limits', + url: `${DOCS_URL}/guides/realtime/limits`, }, ], }, @@ -353,8 +353,8 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ chartDescription: 'The data refreshes every hour.', links: [ { - name: 'Realtime Quotas', - url: `${DOCS_URL}/guides/realtime/quotas`, + name: 'Realtime Limits', + url: `${DOCS_URL}/guides/realtime/limits`, }, ], }, diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 1f19a987c7449..8eac3b7e071f7 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -2176,7 +2176,12 @@ module.exports = [ { permanent: true, source: '/docs/guides/realtime/rate-limits', - destination: '/docs/guides/realtime/quotas', + destination: '/docs/guides/realtime/limits', + }, + { + permanent: true, + source: '/docs/guides/realtime/quotas', + destination: '/docs/guides/realtime/limits', }, { permanent: true, @@ -2206,7 +2211,7 @@ module.exports = [ { permanent: true, source: '/docs/guides/realtime/guides/client-side-throttling', - destination: '/docs/guides/realtime/quotas', + destination: '/docs/guides/realtime/limits', }, { permanent: true, From 1f9b9504a701140163fc8c8c2fac70f870924146 Mon Sep 17 00:00:00 2001 From: "neinja.dev" Date: Mon, 12 Jan 2026 10:03:44 +0100 Subject: [PATCH 12/15] docs: Remove the resource that links to the page the user is already on. (#41697) Removed duplicate link to Row Level Security and Supabase Auth. --- .../docs/content/guides/database/postgres/row-level-security.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx index 2abe63dc067b6..ce0c5a8b58c81 100644 --- a/apps/docs/content/guides/database/postgres/row-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx @@ -534,6 +534,5 @@ This prevents the policy `( (select auth.uid()) = user_id )` from running for an ## More resources - [Testing your database](/docs/guides/database/testing) -- [Row Level Security and Supabase Auth](/docs/guides/database/postgres/row-level-security) - [RLS Guide and Best Practices](https://github.com/orgs/supabase/discussions/14576) - Community repo on testing RLS using [pgTAP and dbdev](https://github.com/usebasejump/supabase-test-helpers/tree/main) From aad40b8848395a227b09cff6dd514529e271b542 Mon Sep 17 00:00:00 2001 From: Jayan Ratna Date: Mon, 12 Jan 2026 22:25:18 +1300 Subject: [PATCH 13/15] fix: HTML formatting in email templates guide (#40369) Co-authored-by: Chris Chinchilla --- apps/docs/content/guides/auth/auth-email-templates.mdx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/docs/content/guides/auth/auth-email-templates.mdx b/apps/docs/content/guides/auth/auth-email-templates.mdx index 2d13408de0061..291ab4e0aef56 100644 --- a/apps/docs/content/guides/auth/auth-email-templates.mdx +++ b/apps/docs/content/guides/auth/auth-email-templates.mdx @@ -136,9 +136,9 @@ const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'ema - Create your own custom email link to redirect the user to a page where they can click on a button to confirm the action ```html -Confirm your signup + + Confirm your signup + ``` - The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL. @@ -156,7 +156,8 @@ You can customize the email link in the email template to redirect the user to a ```html Accept the invite +> + Accept the invite ``` From 4e0db9f72d767707df22dc3bd123f8de7531406b Mon Sep 17 00:00:00 2001 From: GaryAustin1 <54564956+GaryAustin1@users.noreply.github.com> Date: Mon, 12 Jan 2026 03:36:15 -0600 Subject: [PATCH 14/15] docs: Update pgvector extension creation syntax (#40087) Added schema specification to pgvector extension creation. This is needed or vector extension is installed in public schema. --- apps/docs/content/guides/ai/langchain.mdx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/guides/ai/langchain.mdx b/apps/docs/content/guides/ai/langchain.mdx index bb950bab43dbe..a3d709b0ec3a1 100644 --- a/apps/docs/content/guides/ai/langchain.mdx +++ b/apps/docs/content/guides/ai/langchain.mdx @@ -29,7 +29,9 @@ Prepare you database with the relevant tables: ```sql -- Enable the pgvector extension to work with embedding vectors -create extension vector; +create extension vector +with + schema extensions; -- Create a table to store your documents create table documents ( From a760a9cc964ea4c96a8d6a6e1eaa499cf84e02c9 Mon Sep 17 00:00:00 2001 From: tonjo Date: Mon, 12 Jan 2026 11:00:21 +0100 Subject: [PATCH 15/15] docs: Fix syntax for channel creation in Python example (#39212) Wrong Python syntax fixed Co-authored-by: Chris Chinchilla --- apps/docs/content/guides/realtime/getting_started.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/guides/realtime/getting_started.mdx b/apps/docs/content/guides/realtime/getting_started.mdx index b473d2abcf94c..d9ba70b818077 100644 --- a/apps/docs/content/guides/realtime/getting_started.mdx +++ b/apps/docs/content/guides/realtime/getting_started.mdx @@ -197,7 +197,7 @@ let channel = supabase.channel("room:lobby:messages") { ```python # Create a channel with a descriptive topic name -channel = supabase.channel('room:lobby:messages', params={config={private= True }}) +channel = supabase.channel('room:lobby:messages', params={'config': {'private': True }}) ```