From e88a3723e135e50d444f63cf2b8d4db115d4b68c Mon Sep 17 00:00:00 2001
From: Sean Oliver <882952+seanoliver@users.noreply.github.com>
Date: Fri, 31 Jul 2026 15:37:05 -0700
Subject: [PATCH 1/2] feat(studio): add PostHog session replay with
masked-by-default policy (#48515)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
Telemetry feature.
## What is the current behavior?
- Session replay is off, and nothing in the code keeps it off.
- `packages/common/posthog-client.ts` sets no recording config at all.
- So PostHog's project setting alone decides, for every app sharing that
project.
- Studio, www and docs share one project.
- Studio shows customer data almost everywhere: SQL editor, table rows,
connection strings, API keys.
- posthog-js masks inputs by default. It does not mask rendered text.
- [GROWTH-1055](https://linear.app/supabase/issue/GROWTH-1055)
## What is the new behavior?
- `posthogClient.init()` takes a masking config, and disables recording
when it gets none.
- Studio passes one behind `NEXT_PUBLIC_POSTHOG_SESSION_REPLAY`.
- Every other app passes nothing, so it never loads the recorder.
- Studio masks all text and all inputs.
- `data-ph-capture="true"` opts one element's text back in. Unused so
far.
- Canvas is blocked, because it records as images that text masking
cannot reach.
- Query strings and fragments are stripped from recorded URLs, where
auth callbacks carry tokens.
- Request and response bodies are never recorded.
- Console logs are never recorded, since masking only reaches DOM text.
- Masking is set in code, so PostHog's settings cannot loosen it.
- Consent gating is unchanged. Nothing records before a user accepts.
## Additional context
- Recording needs three things: this env var, the PostHog project
toggle, and user consent.
- All three are off or unset, so merging this changes nothing at
runtime.
- `NEXT_PUBLIC_POSTHOG_SESSION_REPLAY` goes into Vercel on Preview scope
first, to test on a preview build.
- Production scope comes later, once we are ready to record there.
- `NEXT_PUBLIC_*` is inlined at build time, so each scope needs a
rebuild afterwards.
- Text inside HTML attributes (`title`, `alt`, `href`) is still recorded
as-is.
- posthog-js exposes no hook for masking attributes, so covering it
needs `ph-no-capture` per component.
- Staging has no server-side masking config, so that is where this gets
verified.
- Plan: enable recording on staging, verify masked text on a preview,
then decide on production.
- Network timing stays on for the dashboard performance work. Payloads
stay off.
- Tests cover both masking functions and the config values.
## Screenshots
https://github.com/user-attachments/assets/aa064a04-f977-4453-a3da-2fe0cdcead08
## Summary by CodeRabbit
## Summary by CodeRabbit
* **New Features**
* Added privacy-focused session replay for Studio.
* Text and form inputs are masked by default, with explicit opt-in
capture.
* Network recordings remove query strings and fragments.
* Headers, request bodies, canvas data, and console logs are excluded.
* **Bug Fixes**
* Improved whitespace and capture-attribute handling during masking.
* Session replay remains disabled without a masking policy or explicit
enablement.
---
apps/studio/lib/session-replay.test.ts | 162 +++++++++++++++++++++++++
apps/studio/lib/session-replay.ts | 50 ++++++++
apps/studio/lib/telemetry.tsx | 2 +
packages/common/posthog-client.ts | 35 +++++-
packages/common/telemetry.tsx | 23 +++-
5 files changed, 266 insertions(+), 6 deletions(-)
create mode 100644 apps/studio/lib/session-replay.test.ts
create mode 100644 apps/studio/lib/session-replay.ts
diff --git a/apps/studio/lib/session-replay.test.ts b/apps/studio/lib/session-replay.test.ts
new file mode 100644
index 0000000000000..eac2fa5864619
--- /dev/null
+++ b/apps/studio/lib/session-replay.test.ts
@@ -0,0 +1,162 @@
+import { buildSessionRecordingConfig, type CapturedNetworkRequest } from 'common'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { maskReplayNetworkRequest, maskReplayText, SESSION_REPLAY_CONFIG } from './session-replay'
+
+const elementWith = (attributes: Record) => {
+ const element = document.createElement('span')
+ Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, value))
+ return element
+}
+
+const networkRequest = (name: string): CapturedNetworkRequest => ({
+ name,
+ entryType: 'resource',
+ startTime: 0,
+ duration: 0,
+})
+
+describe('maskReplayText', () => {
+ it('masks text by default', () => {
+ expect(maskReplayText('postgresql://postgres:hunter2@db.abc.supabase.co:5432')).toBe(
+ '*'.repeat('postgresql://postgres:hunter2@db.abc.supabase.co:5432'.length)
+ )
+ })
+
+ it('masks text when no element is given', () => {
+ expect(maskReplayText('secret', undefined)).toBe('******')
+ })
+
+ it('masks based on trimmed length so whitespace is not leaked', () => {
+ expect(maskReplayText(' abc ')).toBe('***')
+ })
+
+ it('captures text opted in with data-ph-capture', () => {
+ const element = elementWith({ 'data-ph-capture': 'true' })
+ expect(maskReplayText('Table editor', element)).toBe('Table editor')
+ })
+
+ it('masks text when data-ph-capture is not exactly "true"', () => {
+ expect(maskReplayText('secret', elementWith({ 'data-ph-capture': 'false' }))).toBe('******')
+ expect(maskReplayText('secret', elementWith({ 'data-ph-capture': '' }))).toBe('******')
+ expect(maskReplayText('secret', elementWith({ 'data-ph-capture': 'TRUE' }))).toBe('******')
+ })
+
+ it('masks text on elements carrying unrelated data attributes', () => {
+ expect(maskReplayText('secret', elementWith({ 'data-capture': 'true' }))).toBe('******')
+ })
+})
+
+describe('maskReplayNetworkRequest', () => {
+ it('strips query strings', () => {
+ expect(
+ maskReplayNetworkRequest(networkRequest('https://api.supabase.com/v1/x?token=abc')).name
+ ).toBe('https://api.supabase.com/v1/x')
+ })
+
+ it('strips fragments, which carry GoTrue access tokens on auth callbacks', () => {
+ expect(
+ maskReplayNetworkRequest(networkRequest('https://supabase.com/dashboard#access_token=abc'))
+ .name
+ ).toBe('https://supabase.com/dashboard')
+ })
+
+ it('strips from the first separator when both are present', () => {
+ expect(maskReplayNetworkRequest(networkRequest('https://x.com/a?b=1#c=2')).name).toBe(
+ 'https://x.com/a'
+ )
+ expect(maskReplayNetworkRequest(networkRequest('https://x.com/a#c=2?b=1')).name).toBe(
+ 'https://x.com/a'
+ )
+ })
+
+ it('leaves URLs without a query string or fragment alone', () => {
+ expect(maskReplayNetworkRequest(networkRequest('https://x.com/project/abc/editor')).name).toBe(
+ 'https://x.com/project/abc/editor'
+ )
+ })
+
+ it('returns the request rather than dropping it, so timings are still captured', () => {
+ const request = networkRequest('https://x.com/a?b=1')
+ expect(maskReplayNetworkRequest(request)).toBe(request)
+ })
+})
+
+describe('SESSION_REPLAY_CONFIG', () => {
+ it('masks all text and inputs', () => {
+ expect(SESSION_REPLAY_CONFIG.maskTextSelector).toBe('*')
+ expect(SESSION_REPLAY_CONFIG.maskAllInputs).toBe(true)
+ expect(SESSION_REPLAY_CONFIG.maskTextFn).toBe(maskReplayText)
+ })
+
+ it('never records request or response payloads', () => {
+ expect(SESSION_REPLAY_CONFIG.recordHeaders).toBe(false)
+ expect(SESSION_REPLAY_CONFIG.recordBody).toBe(false)
+ })
+
+ it('never records canvas, which text masking cannot reach', () => {
+ expect(SESSION_REPLAY_CONFIG.captureCanvas).toEqual({ recordCanvas: false })
+ })
+
+ it('strips sensitive URL parts via maskReplayNetworkRequest', () => {
+ expect(SESSION_REPLAY_CONFIG.maskCapturedNetworkRequestFn).toBe(maskReplayNetworkRequest)
+ })
+})
+
+describe('buildSessionRecordingConfig', () => {
+ it('disables recording when given no policy', () => {
+ const config = buildSessionRecordingConfig()
+
+ expect(config.disable_session_recording).toBe(true)
+ expect(config).not.toHaveProperty('session_recording')
+ })
+
+ it('disables recording when the policy is undefined', () => {
+ const config = buildSessionRecordingConfig(undefined)
+
+ expect(config.disable_session_recording).toBe(true)
+ expect(config).not.toHaveProperty('session_recording')
+ })
+
+ it('enables recording and forwards the policy when given one', () => {
+ const config = buildSessionRecordingConfig(SESSION_REPLAY_CONFIG)
+
+ expect(config.disable_session_recording).toBe(false)
+ expect(config.session_recording).toBe(SESSION_REPLAY_CONFIG)
+ })
+
+ it.each([undefined, SESSION_REPLAY_CONFIG])(
+ 'never records console logs, which masking cannot reach (%#)',
+ (sessionReplay) => {
+ expect(buildSessionRecordingConfig(sessionReplay).enable_recording_console_log).toBe(false)
+ }
+ )
+})
+
+describe('IS_SESSION_REPLAY_ENABLED', () => {
+ beforeEach(() => {
+ vi.resetModules()
+ })
+
+ afterEach(() => {
+ vi.unstubAllEnvs()
+ })
+
+ it('is true only for the exact string "true"', async () => {
+ vi.stubEnv('NEXT_PUBLIC_POSTHOG_SESSION_REPLAY', 'true')
+ const { IS_SESSION_REPLAY_ENABLED } = await import('./session-replay')
+ expect(IS_SESSION_REPLAY_ENABLED).toBe(true)
+ })
+
+ it.each(['false', '', 'TRUE', '1'])('is false for %o', async (value) => {
+ vi.stubEnv('NEXT_PUBLIC_POSTHOG_SESSION_REPLAY', value)
+ const { IS_SESSION_REPLAY_ENABLED } = await import('./session-replay')
+ expect(IS_SESSION_REPLAY_ENABLED).toBe(false)
+ })
+
+ it('is false when unset', async () => {
+ vi.stubEnv('NEXT_PUBLIC_POSTHOG_SESSION_REPLAY', undefined)
+ const { IS_SESSION_REPLAY_ENABLED } = await import('./session-replay')
+ expect(IS_SESSION_REPLAY_ENABLED).toBe(false)
+ })
+})
diff --git a/apps/studio/lib/session-replay.ts b/apps/studio/lib/session-replay.ts
new file mode 100644
index 0000000000000..db7283d1dee75
--- /dev/null
+++ b/apps/studio/lib/session-replay.ts
@@ -0,0 +1,50 @@
+import type { CapturedNetworkRequest, SessionRecordingOptions } from 'common'
+
+/**
+ * Enables session replay in Studio. Recording also requires "Record user
+ * sessions" in PostHog, which www and docs share.
+ */
+export const IS_SESSION_REPLAY_ENABLED = process.env.NEXT_PUBLIC_POSTHOG_SESSION_REPLAY === 'true'
+
+/**
+ * Setting `data-ph-capture="true"` on an element opts its text in to session
+ * recording. All text is opted out by default.
+ */
+const CAPTURE_DATASET_KEY = 'phCapture'
+
+/**
+ * Returns asterisks for all text except text inside elements marked
+ * `data-ph-capture="true"`.
+ */
+export function maskReplayText(text: string, element?: HTMLElement): string {
+ if (element?.dataset[CAPTURE_DATASET_KEY] === 'true') return text
+ return '*'.repeat(text.trim().length)
+}
+
+/**
+ * Strips query strings and fragments from recorded URLs, which posthog-js applies
+ * to page URLs as well as network requests. Auth callbacks carry tokens in the
+ * fragment.
+ */
+export function maskReplayNetworkRequest(request: CapturedNetworkRequest): CapturedNetworkRequest {
+ if (request.name) {
+ const separatorIndex = request.name.search(/[?#]/)
+ if (separatorIndex !== -1) {
+ request.name = request.name.slice(0, separatorIndex)
+ }
+ }
+ return request
+}
+
+export const SESSION_REPLAY_CONFIG: SessionRecordingOptions = {
+ // Match posthog-js defaults, but set here so the PostHog UI can't relax them.
+ maskAllInputs: true,
+ maskTextSelector: '*',
+ maskTextFn: maskReplayText,
+ // Keeps network capture to URL, status and timing. Overrides the PostHog UI.
+ recordHeaders: false,
+ recordBody: false,
+ // Canvas is captured as images, which text masking can't reach.
+ captureCanvas: { recordCanvas: false },
+ maskCapturedNetworkRequestFn: maskReplayNetworkRequest,
+}
diff --git a/apps/studio/lib/telemetry.tsx b/apps/studio/lib/telemetry.tsx
index cdf1a5b1c0011..cd7f53762a1f0 100644
--- a/apps/studio/lib/telemetry.tsx
+++ b/apps/studio/lib/telemetry.tsx
@@ -6,6 +6,7 @@ import { useConsentToast } from 'ui-patterns/consent'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { API_URL, IS_PLATFORM } from '@/lib/constants'
+import { IS_SESSION_REPLAY_ENABLED, SESSION_REPLAY_CONFIG } from '@/lib/session-replay'
export function Telemetry() {
// Although this is "technically" breaking the rules of hooks
@@ -70,6 +71,7 @@ export function Telemetry() {
hasAcceptedConsent={hasAcceptedConsent}
enabled={IS_PLATFORM}
organizationSlug={organization?.slug}
+ sessionReplay={IS_SESSION_REPLAY_ENABLED ? SESSION_REPLAY_CONFIG : undefined}
/>
)
}
diff --git a/packages/common/posthog-client.ts b/packages/common/posthog-client.ts
index 9374535025c77..19dad1f57f79c 100644
--- a/packages/common/posthog-client.ts
+++ b/packages/common/posthog-client.ts
@@ -1,7 +1,13 @@
-import posthog, { PostHogConfig } from 'posthog-js'
+import posthog, {
+ type CapturedNetworkRequest,
+ type PostHogConfig,
+ type SessionRecordingOptions,
+} from 'posthog-js'
import { safeSessionStorage } from './safe-storage'
+export type { CapturedNetworkRequest, SessionRecordingOptions }
+
// Limit the max number of queued events
// (e.g. if a user navigates around a lot before accepting consent)
const MAX_PENDING_EVENTS = 20
@@ -23,6 +29,30 @@ interface PostHogClientConfig {
uiHost?: string
}
+interface PostHogInitOptions {
+ hasConsent?: boolean
+ /**
+ * Masking policy for session replay. Omit to disable recording, which every app
+ * sharing this PostHog project does unless it passes a policy of its own.
+ */
+ sessionReplay?: SessionRecordingOptions
+}
+
+/**
+ * Enables session recording when given a masking config, and disables it when
+ * given nothing.
+ */
+export function buildSessionRecordingConfig(
+ sessionReplay?: SessionRecordingOptions
+): Partial {
+ return {
+ disable_session_recording: !sessionReplay,
+ // Console output is not in the DOM, so text masking cannot reach it.
+ enable_recording_console_log: false,
+ ...(sessionReplay && { session_recording: sessionReplay }),
+ }
+}
+
class PostHogClient {
/** True after posthog.init() is called (prevents double-init) */
private initStarted = false
@@ -50,7 +80,7 @@ class PostHogClient {
}
}
- init(hasConsent: boolean = true) {
+ init({ hasConsent = true, sessionReplay }: PostHogInitOptions = {}) {
if (this.initStarted || typeof window === 'undefined' || !hasConsent) return
if (!this.config.apiKey) {
@@ -64,6 +94,7 @@ class PostHogClient {
autocapture: false, // We'll manually track events
capture_pageview: false, // We'll manually track pageviews
capture_pageleave: false, // We'll manually track page leaves
+ ...buildSessionRecordingConfig(sessionReplay),
loaded: (posthog) => {
// Apply pending properties that were set before PostHog
// initialized due to poor connection or user not accepting
diff --git a/packages/common/telemetry.tsx b/packages/common/telemetry.tsx
index b92aae494cca1..4e8e13aa9f897 100644
--- a/packages/common/telemetry.tsx
+++ b/packages/common/telemetry.tsx
@@ -21,7 +21,13 @@ import {
} from './first-referrer-cookie'
import { ensurePlatformSuffix, isBrowser } from './helpers'
import { useFirstTouchStore, useParams } from './hooks'
-import { posthogClient, type ClientTelemetryEvent } from './posthog-client'
+import {
+ buildSessionRecordingConfig,
+ posthogClient,
+ type CapturedNetworkRequest,
+ type ClientTelemetryEvent,
+ type SessionRecordingOptions,
+} from './posthog-client'
import { TelemetryEvent } from './telemetry-constants'
import {
clearFirstTouchData,
@@ -30,7 +36,13 @@ import {
} from './telemetry-first-touch-store'
import { getSharedTelemetryData, getTelemetryCookieOptions } from './telemetry-utils'
-export { posthogClient, type ClientTelemetryEvent }
+export {
+ buildSessionRecordingConfig,
+ posthogClient,
+ type CapturedNetworkRequest,
+ type ClientTelemetryEvent,
+ type SessionRecordingOptions,
+}
export const TelemetryTagManager = () => {
const { hasAccepted } = useConsentState()
@@ -255,12 +267,15 @@ export const PageTelemetry = ({
enabled = true,
organizationSlug,
projectRef,
+ sessionReplay,
}: {
API_URL: string
hasAcceptedConsent: boolean
enabled?: boolean
organizationSlug?: string
projectRef?: string
+ /** Masking policy for session replay. Omit to disable recording. */
+ sessionReplay?: SessionRecordingOptions
}) => {
const router = useRouter()
@@ -315,9 +330,9 @@ export const PageTelemetry = ({
useEffect(() => {
if (hasAcceptedConsent && IS_PLATFORM) {
- posthogClient.init(true)
+ posthogClient.init({ sessionReplay })
}
- }, [hasAcceptedConsent, IS_PLATFORM])
+ }, [hasAcceptedConsent, IS_PLATFORM, sessionReplay])
// Waiting for router.isReady before sending to avoid dynamic route placeholders
useEffect(() => {
From 3a3661019f4f7dd79a5941bc379741bf45396047 Mon Sep 17 00:00:00 2001
From: Nik Richers
Date: Fri, 31 Jul 2026 16:00:47 -0700
Subject: [PATCH 2/2] docs: update architecture diagram and references from
Kong to Envoy (#48557)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.
YES
## What kind of change does this PR introduce?
This is a docs update. The shared architecture diagram and several docs
pages still described Kong as Supabase's API gateway, even though the
hosted platform has run Envoy since 2025. Both diagram variants are
rebuilt with real, accessible text — the originals rendered every label
as an outlined vector path with zero `` elements — so the gateway
name can be kept current going forward, and the platform-facing prose
that named Kong directly is updated to Envoy.
Closes DOCS-1262.
## What is the current behavior?
- The architecture diagram (used on the Architecture overview, Auth
architecture, Self-hosting Docker, and Contributing guide pages) shows
"KONG / docs.konghq.com" as the gateway box
- The Architecture overview page has a "Kong (API gateway)" component
section
- The Auth architecture page states "Kong API gateway. This is shared
between all Supabase products."
- `README.md` and `apps/docs/public/humans.txt` credit Kong instead of
Envoy
## What is the new behavior?
- Rebuilt `supabase-architecture.svg` and
`supabase-architecture--light.svg` with real `` elements; the
gateway box now reads "ENVOY / envoyproxy.io" with identical layout,
colors, and shadows otherwise
- Updated the diagram alt text and the "Kong (API gateway)" section (now
"Envoy (API gateway)", with the correct docs link, license, and
language) on the Architecture overview page
- Updated the "Kong API gateway" bullet and diagram alt text on the Auth
architecture page
- Updated the Kong credit to Envoy in `README.md` and
`apps/docs/public/humans.txt`
**Intentionally excluded:**
- Self-hosted Docker Compose pages (`docker.mdx`, `enable-mcp.mdx`,
`self-hosted-auth-keys.mdx`, `self-hosted-envoy.mdx`,
`self-hosted-functions.mdx`, `self-hosted-proxy-https.mdx`) — these
describe the self-hosted stack, which still defaults to Kong today and
is already owned by an open PR (#48153) that flips that default
- `i18n/README.*.md` (29 files) — translation risk without
native-speaker review; only the English `README.md` was updated
## Open questions
- [ ] #48153 merges and the self-hosted default actually flips to Envoy
— once it does, revisit the self-hosting Docker Compose pages excluded
from this PR and the self-hosting-analytics reference TODO
- [ ] Confirm whether all legacy platform instances have fully migrated
to Envoy — until then, this PR's wording says "Envoy" without claiming
Kong is gone everywhere (some legacy instances may still silently be on
Kong)
- [ ] Current Envoy response header names confirmed for the logs guide
TODO (`x-kong-proxy-latency` / `x-kong-upstream-latency`)
- [ ] i18n README translations (29 files) follow up separately with
native-speaker review
## Additional context
- Verification: rendered both new SVGs with `rsvg-convert` and visually
diffed against the originals — layout, spacing, colors, and shadows are
pixel-equivalent; only the top-box label text changed
| Check | Result |
| --- | --- |
| `rsvg-convert` render, dark variant | pass — diagram unchanged except
gateway label |
| `rsvg-convert` render, light variant | pass — diagram unchanged except
gateway label |
| Preview URL, Architecture overview | pass — 200 |
| Preview URL, Auth architecture | pass — 200 |
### Before & After
#### [Architecture
overview](https://supabase.com/docs/guides/getting-started/architecture)
| [Before
(production)](https://supabase.com/docs/guides/getting-started/architecture)
| [After (PR
preview)](https://docs-git-nikrichers-docs-1262-architecture-docs-84e339-supabase.vercel.app/docs/guides/getting-started/architecture)
|
| --- | --- |
|

|

|
#### [Auth
architecture](https://supabase.com/docs/guides/auth/architecture)
| [Before
(production)](https://supabase.com/docs/guides/auth/architecture) |
[After (PR
preview)](https://docs-git-nikrichers-docs-1262-architecture-docs-84e339-supabase.vercel.app/docs/guides/auth/architecture)
|
| --- | --- |
|

|

|
### Test plan
- [ ] Diagram renders correctly in both light and dark mode on the
preview
- [ ] "Envoy (API gateway)" section reads correctly on the Architecture
overview page
- [ ] Auth architecture bullet reads "Envoy API gateway"
- [ ] The two TODO-marked follow-ups (logs guide,
self-hosting-analytics) are acceptable to leave for later rather than
block this PR
---------
Co-authored-by: Nik Richers
Co-authored-by: Miranda Limonczenko
---
README.md | 2 +-
.../docs/content/guides/auth/architecture.mdx | 4 +-
.../guides/getting-started/architecture.mdx | 14 ++---
apps/docs/public/humans.txt | 2 +-
.../img/supabase-architecture--light.svg | 51 +++++++++++++------
.../docs/public/img/supabase-architecture.svg | 51 +++++++++++++------
6 files changed, 81 insertions(+), 43 deletions(-)
diff --git a/README.md b/README.md
index 08120545543e0..0b5a5e6f4556c 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and
- [Storage](https://github.com/supabase/storage-api) a RESTful API for managing files in S3, with Postgres handling permissions.
- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API.
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
-- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
+- [Envoy](https://github.com/envoyproxy/envoy) is a cloud-native, high-performance edge and service proxy.
#### Client libraries
diff --git a/apps/docs/content/guides/auth/architecture.mdx b/apps/docs/content/guides/auth/architecture.mdx
index b4c0a53092b64..3665526c40c12 100644
--- a/apps/docs/content/guides/auth/architecture.mdx
+++ b/apps/docs/content/guides/auth/architecture.mdx
@@ -6,12 +6,12 @@ subtitle: 'The architecture behind Supabase Auth.'
There are four major layers to Supabase Auth:
1. [Client layer.](#client-layer) This can be one of the Supabase client SDKs, or manually made HTTP requests using the HTTP client of your choice.
-1. Kong API gateway. This is shared between all Supabase products.
+1. Envoy API gateway. This is shared between all Supabase products.
1. [Auth service](#auth-service) (formerly known as GoTrue).
1. [Postgres database.](#postgres) This is shared between all Supabase products.
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -171,4 +171,23 @@
+
+ENVOY
+envoyproxy.io
+POSTGRESQL
+postgresql.org
+GoTrue
+PostgREST
+Realtime
+Storage
+pg-meta
+Functions
+pg_graphql
+/auth
+/rest
+/realtime
+/storage
+/pg
+/functions
+/graphql
diff --git a/apps/docs/public/img/supabase-architecture.svg b/apps/docs/public/img/supabase-architecture.svg
index 2b0ad6325a99a..6b4dfd3dc8f15 100644
--- a/apps/docs/public/img/supabase-architecture.svg
+++ b/apps/docs/public/img/supabase-architecture.svg
@@ -15,69 +15,69 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -171,4 +171,23 @@
+
+ENVOY
+envoyproxy.io
+POSTGRESQL
+postgresql.org
+GoTrue
+PostgREST
+Realtime
+Storage
+pg-meta
+Functions
+pg_graphql
+/auth
+/rest
+/realtime
+/storage
+/pg
+/functions
+/graphql