## What kind of change does this PR introduce?
Chore that references DEPR-394.
## What is the current behavior?
This stack is consolidating RHF key/value array UIs in smaller
reviewable steps.
After PR 1, Database Webhooks HTTP parameters and Database Functions
configuration parameters still use separate bespoke implementations.
## What is the new behavior?
- reuses the shared `KeyValueFieldArray` for Database Webhooks HTTP
parameters
- reuses the shared `KeyValueFieldArray` for Database Functions
configuration parameters
- keeps existing form shapes and submission logic intact
- adds focused tests for those two follow-up consumers
## Additional context
This is PR 2 of a 3-PR stack for DEPR-394.
Base PR: #43938
### Summary
This PR updates the billing address required modal so it also shows for
users with billing read access but without billing write access, such as
developers and users with read-only acess.
For users with BILLING_WRITE, the existing blocking behavior remains in
place:
- the modal has no visible close button
- it closes only after a successful billing address update
For users with BILLING_READ but not BILLING_WRITE, the modal now:
- the modal includes a visible close button
- no editable billing form is shown
- the copy tells the user to ask an organization administrator or owner
to update the billing address
<img width="1280" height="626" alt="Screenshot 2026-03-24 at 7 18 49 PM"
src="https://github.com/user-attachments/assets/ddc5e90d-c7d8-4d67-a138-59a5a5baf71b"
/>
### Manual testing
#### Admins/owners
1. As a user with BILLING_WRITE, open an org that is missing a billing
address.
2. Confirm the modal appears with the billing address form.
3. Confirm there is no visible close button.
4. Submit a valid billing address and verify the modal closes after a
successful save.
For testing whether an update successfully closes the modal, you can:
- You can create a free org without a billing address
- You will need to tweak this logic (on the frontend)
```
const shouldShow = Boolean(
IS_PLATFORM &&
// showMissingAddressModal &&
org &&
// org.plan.id !== 'free' &&
org.organization_missing_address &&
!org.billing_partner &&
permissionsLoaded &&
canViewModal
)
```
- Tweak the backend logic which computes `organization_missing_address`
to exclude free orgs
#### Developers/users with readp-only access
1. As a user with BILLING_READ but not BILLING_WRITE, open an org that
is missing a billing address.
2. Confirm the modal appears with informational copy only and no
editable form.
3. Click the close button and verify the modal dismisses.
## Problem
React Query cache keys for permissions and organizations are generic
(`['permissions']`, `['organizations']`) with no user identifier. When a
new session is established without going through the explicit sign-out
flow (e.g. clicking an email verification link while still logged in as
another account), stale data from the previous user persists in cache.
The new user ends up seeing the old user's permissions and org list,
which makes the project creation form appear enabled. When submitted,
the backend rejects with "not an owner" because the token belongs to the
new account which has no ownership of the old org.
A hard refresh clears the in-memory React Query state and forces a fresh
fetch, which is why it resolved the issue.
## Fix
Scope `permissionKeys.list` and `organizationKeys.list` by user ID so
different accounts never share the same cache slot. When a different
user logs in, their queries simply get fresh cache entries and never see
data from the previous session. Updated all invalidation and
`setQueriesData` call sites across 18 files to pass the current user ID.
## How to test
I don't think it can be tested in the preview environment sadly since
the request to verify has to go to auth.supabase.green instead of
vercel-ref.supabase.green
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## 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?
Since queue operations is a feature users can opt-out of, we need to
make it cleaner to toggle between queuing vs straight edits. To do this,
refactor all the operations into a single hook and reference it in
places where we mutate the rows.
## Testing
- Test edit cells, rows, and deletes for non queue operations
- Test edit cells, rows, and deletes for queue operations, also double
check modifying the same rows that are not yet added
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
### Summary
This PR removes the visible close button from the billing address
required modal to make it non dismissable, since the majority of users
on paid orgs are still not filling their billing address.
The modal already prevents dismissal via Esc and outside/backdrop
interaction, and this change preserves that behavior. The existing
submit flow is unchanged, so the modal still closes after a successful
billing address update.
### Manual testing:
1. Open Studio in a state where the billing address required modal
appears.
2. Confirm the top-right close button is no longer shown.
3. Press Esc and verify the modal remains open.
4. Click outside the modal / on the backdrop and verify the modal
remains open.
5. Submit a valid billing address and verify the modal closes after a
successful save.
For testing whether an update successfully closes the modal, you can:
- You can create a free org without a billing address
- You will need to tweak this logic (on the frontend)
```
const shouldShow = Boolean(
IS_PLATFORM &&
// showMissingAddressModal &&
org &&
// org.plan.id !== 'free' &&
org.organization_missing_address &&
!org.billing_partner &&
permissionsLoaded &&
canBillingWrite
)
```
- Tweak the backend logic which computes `organization_missing_address`
to exclude free orgs
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Summary
Customers were upgrading to Pro to clear service restrictions, unaware
that restrictions aren't lifted immediately when the billing cycle
resets — there's a delay. This adds a clarifying note to both the docs
FAQ and the Studio restricted alert so users know what to expect.
Note: upgrading your plan or disabling spend cap *does* lift
restrictions immediately (unchanged). The delay only applies to the
billing-cycle-reset path for usage-limit violations.
## Changes
- `billing-faq.mdx`: Removes the implication that restrictions clear the
moment the billing period resets; adds "Note that there may be a short
delay after your billing period resets before restrictions are fully
lifted"
- `Restriction.tsx`: Updates the restricted alert to make clear that
upgrading lifts restrictions immediately, while the billing-reset path
may have a delay
## Testing
To test the Studio banner, find or create an org with
`restriction_status === 'restricted'`, or temporarily hardcode
`shownAlert = 'restricted'` in `Restriction.tsx` to preview:
- [x] Restricted alert reads clearly and distinguishes immediate
(upgrade) vs delayed (billing reset) paths
- [x] Docs FAQ answer under "How can I remove restrictions" reads
correctly with the new caveat
## Linear
- fixes GROWTH-704
## Context
Related to the queue table operations feature preview
Adding a "cancel" action in the save queue operation bar for convenience
to clear all changes (instead of having to go into the review panel)
Also aligning the positioning of the CTAs to match the review panel
- "Review" imo is a secondary action, while "Save" or "Cancel" are the
primary ones
- Hence am shifting the "review" CTA to the left, contextually beside
the number of pending changes text
<img width="449" height="100" alt="image"
src="https://github.com/user-attachments/assets/c3faa6c1-e244-40ee-b251-44ab1e785c6e"
/>
- This also aligns with the CTA placements in the review panel
<img width="502" height="71" alt="image"
src="https://github.com/user-attachments/assets/35b7de0a-dbf4-4e8a-acef-53508c9b13b9"
/>
- Also removed plural grammar for the button CTAs - thinking thats not
necessary, wanna keep button CTA texts short and sweet + The "x pending
change(s)" also captures the plurality
## Context
Shifts all remaining dashboard queries into pg-meta so that we
centralize all manually written queries in one place
Having them in packages/pg-meta also allows us to write tests for them
## To test
Just needs a smoke test on
- Role Impersonation
- Lints
- Data API
- Database
- Enumerated Types
- Integrations
- Foreign Data Wrappers
- Vault
## Description
Fixes#43800
When editing SMTP settings while custom SMTP is already enabled, the
form footer was incorrectly displaying "Rate limit for sending emails
will be increased to 30". The rate limit is only set during the initial
enable transition, so the message and submit behavior were out of sync.
## Changes
This fix aligns the footer message with the submit logic (Option A from
the issue):
- **Enabling SMTP**: Shows rate limit increase message
- **Already enabled, editing settings**: Shows "Custom SMTP settings
will be updated"
- **Disabling SMTP**: Shows rate limit reduction message
## Testing
1. Go to Authentication → SMTP Settings
2. Enable custom SMTP and save (rate limit is set to 30)
3. Edit any SMTP field (e.g. change host or port)
4. Footer now correctly shows "Custom SMTP settings will be updated"
instead of the rate limit message
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: vj2303 <vishnu.jangid@ax-ia.ai>
Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
## Context
Edge functions code editor which uses the FileExplorerAndEditor
component doesn't resize when the viewport shrinks
This PR fixes that + also refactors the FileExplorerAndEditor a bit to
resolve the lint issue with `nodeRenderer` by refactoring the prop into
a renderer that uses `useCallback`
## What kind of change does this PR introduce?
Chore that references DEPR-394.
## What is the current behavior?
Key/value editors for headers are implemented separately in multiple
places.
## What is the new behavior?
DEPR-394 is consolidating repeated RHF field-array UIs across Studio and
the design system.
- adds a shared `KeyValueFieldArray` component in `ui-patterns`
- adds a shared `httpHeaderAddActions` helper for preset header rows
- migrates the key/value header editors in:
- Platform Webhooks
- Cron Jobs HTTP headers
- Database Webhooks HTTP headers
- documents the key/value pattern in the design system with:
- a dedicated fragment page
- updated forms guidance
- updated form pattern demos
| Preview |
| --- |
| <img width="1102" height="420" alt="CleanShot 2026-03-23 at 12 22
18@2x"
src="https://github.com/user-attachments/assets/f8d23ff9-7063-462f-8074-b400561f77e9"
/> |
## Additional context
This is PR 1 of a 3-PR stack for DEPR-394.
## What kind of change does this PR introduce?
UI fix.
## What is the current behavior?
Form contents overflow horizontally on `SSOConfig`’s `AttributeMapping`
section.
## What is the new behavior?
These form contents no longer overflow.
| Before | After |
| --- | --- |
| <img width="1096" height="997" alt="SSO Organization Settings Toolshed
Supabase-BB366F67-15C9-40A3-8CB7-C0DA7363A2EC"
src="https://github.com/user-attachments/assets/daabcba2-408a-4d44-8bf6-beb0c21d12ed"
/> | <img width="1096" height="997" alt="SSO Organization Settings
Toolshed Supabase-550B04A9-5B3C-4BCE-9CE3-C4B5F516BE35"
src="https://github.com/user-attachments/assets/741f6178-7633-4c6d-87cd-6fdd5d0e4606"
/> |
---------
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
## Summary
Fixes several high-impact Sentry errors reported in production.
### Fixed Issues
- **[SUPABASE-APP-EJ3](https://supabase.sentry.io/issues/7356937474/)**
— `TypeError: Cannot read properties of undefined (reading 'direct')`.
`connectionStringPooler` could be `undefined` when the connection source
doesn't match any key in the connection strings map. Added an early
return guard in `resolveConnectionString`.
- **[SUPABASE-APP-B17](https://supabase.sentry.io/issues/7117468199/)**
— `RangeError: Invalid time zone specified: Etc/Unknown`.
`dayjs.tz.guess()` returns `"Etc/Unknown"` for some users with
misconfigured browser/OS timezones. Added a shared
`guessLocalTimezone()` helper that validates the guessed timezone via
`Intl.DateTimeFormat` and falls back to UTC. Applied across all 4 call
sites.
- **[SUPABASE-APP-BCM](https://supabase.sentry.io/issues/7192934901/)**
— `TypeError: Cannot convert undefined or null to object`.
`Object.entries(definition.properties)` crashed when a JSON schema
definition existed but had no `properties` field. Updated the guard to
check `definition?.properties` instead of just `definition`.
- https://supabase.sentry.io/issues/7357780302/?project=5459134
- https://supabase.sentry.io/issues/7358344652/?project=5459134
- https://supabase.sentry.io/issues/7096737077/?project=5459134
## Test plan
- [ ] Verify connect dialog renders without errors when connection data
is still loading
- [ ] Verify API docs Entity view handles schema definitions without
properties
- [ ] Verify charts/tooltips display correct timezone labels
---------
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
## 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 prototype for private apps UI. There are no endpoints at the
minute, just wanted to see what a potential flow could look like.
## Problem
When you have many tables, it's hard to follow the relations between
them in the Schema Visualiser
## Solution
When selecting an edge (the line between tables), highlight it along
with the related tables and columns to make it easier.
Also, if there is enough space, display a popover showing the relation
details
## Screencasts
https://github.com/user-attachments/assets/11d35fa7-3674-4f13-b77f-8ebe25c66b04
## Summary
- fix the Studio SQL row formatter to emit raw boolean and numeric
literals instead of quoted strings
- serialize text array members as SQL string literals inside
`ARRAY[...]`
- escape fallback string formats outside `text`/`varchar`, and add
regression coverage for that path
Closes#44024
## Test plan
- [x] Ran a direct `tsx` smoke against `formatTableRowsToSQL()` for the
`storage.buckets` case and confirmed it now emits `true`, `false`,
`10485760`, and `ARRAY['image/*']`
- [x] Ran focused formatter smokes for JSON escaping, text arrays, and
fallback string formats like `citext`
- [x] `pnpm --filter studio test -- TableEntity.utils.test.ts`
Note: the targeted Vitest run is still blocked in this environment
before the test executes (`localStorage.getItem is not a function`).
---------
Co-authored-by: Alaister Young <alaister@users.noreply.github.com>
## Problem
The Query Performance page loaded all results in a single query with a
fixed limit of 20 rows, giving users no way to browse beyond the first
page. There was also no way to control how many rows were shown at once.
## Fix
adds pagination
## How to test
- Navigate to `/observability/query-performance` in Studio
- scroll to bottom
- should automatically load more results
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## What kind of change does this PR introduce?
Minor polish
## What is the current behavior?
We have a privacy policy notice that uses `Alert_Shadcn_` with bespoke
links and badge.
## What is the new behavior?
- Reuse `Admonition` and `InlineLink` components
- Use sentence case as per our copywriting guidelines
- Tighten copywriting
| Before | After |
| --- | --- |
| <img width="1024" height="563" alt="Projects Boring Org Supabase"
src="https://github.com/user-attachments/assets/43fd9d4f-21e2-461b-92e3-d75578ae2ddd"
/> | <img width="1024" height="563" alt="Projects Boring Org
Supabase-27BBA2B8-4F90-4FB5-852D-B92B38AF5055"
src="https://github.com/user-attachments/assets/c0c09aae-c110-46cb-86ad-f6df557e1a4f"
/> |
## Additional context
Legal should sign off on this too.
## Context
Adds a "source" param in the new Connect sheet to allow selecting read
replicas - this was present in the old Connect dialog
<img width="962" height="713" alt="image"
src="https://github.com/user-attachments/assets/3c805e41-e8df-431b-83d0-e5f227d68475"
/>
Selecting sources will update the connect steps at the bottom -
connection string, host etc
Adds `chatId` (the chat UUID) to the three assistant PostHog events so
we can count distinct chat sessions in PostHog. This naming is
consistent with how we name the field in Braintrust logs.
Also migrates from the deprecated `useSendEventMutation` hook to
`useTrack` — groups (project/org) are still sent automatically by
`useTrack`.
Closes AI-519
### Summary
This PR adds support for Isle of Man customers to use GB VAT tax IDs.
Isle of Man uses the UK VAT system, so Orb requires country: 'GB' for
validation - but the billing address country is IM.
- Adds an IM VAT entry to `TAX_IDS` with a new `taxCountryIso2` override
field, so the UI shows "IM VAT" while sending country: 'GB' to Orb
- Introduces `getEffectiveTaxCountry()` to resolve the correct country
code for Orb
- Introduces `resolveStoredTaxId()` to map stored Orb data back to the
correct UI entry, using billing address country to disambiguate IM vs GB
- Updates all three billing forms to use these utilities: Billing
Settings page, Add Payment Method modal and Update Billing Address modal
### Testing
#### Automated tests
I've added unit tests in this PR
#### Manual tests
You can use the following country/city/postal code combinations to test:
- Isle of Man, Douglas, IM1 2LE
- United Kingdom, Crumlin, BT29 4AA
**Billing Settings page**
- Select Isle of Man as billing country → tax ID dropdown shows "Isle of
Man - IM VAT"
- Select United Kingdom as billing country → tax ID dropdown shows
"United Kingdom - UK VAT" / "United Kingdom - GB VAT" (not "IM VAT")
- Select IM VAT, enter a valid GB VAT number, save → check network tab,
Orb receives country: 'GB'
- Reload page for an IM customer with existing tax ID → "IM VAT" is
pre-selected (not "UK VAT")
- Reload page for a GB customer with existing tax ID → "UK VAT" is
pre-selected (no regression)
- Change country from IM to GB → available tax IDs update, IM VAT no
longer shown
**Add Payment Method modal**
- Open modal for an IM customer with existing tax ID → "IM VAT" is
pre-selected
- Check "I'm purchasing as a business", select Isle of Man in Stripe
address → tax ID dropdown shows "IM VAT"
- Check "I'm purchasing as a business", select United Kingdom in Stripe
address → tax ID dropdown shows "UK VAT" / "GB VAT"
- Submit with IM VAT → check network tab, tax ID is saved with country:
'GB'
- Open modal for a GB customer → no regression, "UK VAT" shown as before
**Update Billing Address modal**
Appears for paid orgs missing a billing address
- Open modal for an IM customer with existing tax ID → "IM VAT" is
pre-selected
- Open modal for a GB customer with existing tax ID → "UK VAT" is
pre-selected
- Fill in IM address + IM VAT and save → saves correctly, modal closes
---------
Co-authored-by: Ignacio Dobronich <ignacio@dobronich.com>
## 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?
show the "enable" button if the `GET /auth/v1/admin/custom-providers`
returns 404 (without a json body). this is temporary as I want to enable
the rollout of this feature now, and there could be projects which
didn't get the latest auth server release(should be completed by next
week).
## What is the current behavior?
We only show a generic error message.
## What is the new behavior?
Show custom provider specific message and add CTA to enable the custom
providers (simply trigger a PATCH config request to trigger auth config
updates)
## Additional context
<img width="1454" height="390" alt="image-IPleA6ze@2x"
src="https://github.com/user-attachments/assets/b0cb4df8-2499-4749-900c-b78543a72800"
/>
This error is shown after 2 retries of the `GET
/auth/v1/admin/custom-providers`, would be nice to have if we show this
error message without retries.
## Context
Table Editor - hide "Is unique" configuration for a column that's a
primary key - unnecessary to configure a column as unique if it's
already a primary key (redundant index is created)
## Changes involved
- TableEditor side panel: No longer shows is unique option under
configuration popover if column is a PK
- ColumnEditor side panel: Disable is unique + is nullable option if
column is a PK (Added tooltip)
<img width="810" height="151" alt="image"
src="https://github.com/user-attachments/assets/0f877706-1a56-44b7-864e-ba30efbb67f7"
/>
Also chucked in some minor refactors:
- Refactored some `any` types
- Refactored Column to use the latest Checkbox component
## Problem
The edge function logs table at
`/observability/logs/edge-functions-logs` did not show which edge
function was called. Users had to open individual log entries to find
the function name, even though the information is available in the log
data.
## Fix
- Added `request.pathname` to the SQL SELECT query for
`function_edge_logs` so the pathname is returned with each row.
- Added `extractEdgeFunctionName` utility that parses the last path
segment from the pathname (e.g. `/functions/v1/hello-world-1` becomes
`hello-world-1`).
- Updated `FunctionsEdgeColumnRender` to display the function name
between the HTTP method and the log ID columns.
- Added unit tests for `extractEdgeFunctionName` covering normal paths,
null/undefined, non-string values, and pathnames without slashes.
<img width="1472" height="776" alt="CleanShot 2026-03-19 at 13 18 47@2x"
src="https://github.com/user-attachments/assets/e437f7df-7aab-4ea5-b421-95bab3923605"
/>
## How to test
- Navigate to Logs and Analytics, then Edge Functions logs.
- Confirm each row now shows the function name (e.g. `hello-world-1`)
between the HTTP method and the log ID.
- Confirm rows without a pathname (e.g. non-platform or self-hosted)
still render without errors.
- Confirm clicking a row still opens the log detail panel correctly.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Problem
We want `ui-patterns` to contain only visual components but
https://github.com/supabase/supabase/pull/43577 introduced a component
with logic about navigation
## Solution
Extract the logic part into a hook and use it in the component.
Next step will be to remove this component and use
`<DiscardChangesConfirmationDialog>` directly along the hook where
needed
## How to test
Check that the fixes from #43577 still work:
On staging, for each case:
- Authentication email templates
- Observability reports
- Edge functions creation
- Edge functions edition
Test:
- Modify the template/report/function
- Navigate away using either the sidebar link, the browser back button
or closing the tab
- Cancel navigation in the confirmation dialog
- Navigation should be prevented and you should not loose your changes
Test:
- Modify the template/report/function
- Navigate away using either the sidebar link, the browser back button
or closing the tab
- Confirm navigation in the confirmation dialog
- Navigation should not be prevented and you should have lost your
changes
## Context
If you've got a queued operation on a table, then switch tables, the
dashboard stalls for a few seconds before finally rendering the selected
table.
This was caused by an infinite loop in a useEffect calling
`reapplyOptimisticUpdates` due to `dataUpdatedAt` being in the
dependency array (which comes from react query)
Removing `dataUpdatedAt` would resolve the issue, but then it won't
solve the following comment in the code:
`// This ensures pending changes remain visible when switching tabs or
after data refresh`
## Changes involved
Opting to refactor the logic for rendering data in the Grid instead
- Avoid manipulating the data directly from the query client
- Manual changes to the query client are hard to track when debugging
- Ideally the data in the query client are exactly what is coming from
the API
- Instead just compute the row data if there's operations applied, and
render that in the grid
```
const baseRows = data?.rows ?? EMPTY_ARR
const rows = formatGridDataWithOperationValues({ operations, rows:
baseRows })
```
- Simplifies operations logic + data in react query remains as the
source of truth
- This also improves perceived performance, as previously we'd need to
invalidate the query client if we're removing any operation.
- A lot more apparent with the undo operation introduced
[here](https://github.com/supabase/supabase/pull/43957)
- Whereas now, we'll just revert back to whatever's in the query client
(and still do the invalidation behind the scenes) so things feel faster
## Context
Shifting more dashboard queries into pg-meta so that we centralize all
manually written queries in one place
Having them in packages/pg-meta also allows us to write tests for them
## To test
Just needs a smoke test on
- Table Editor
- Fetching entities
- Viewing definition
- SQL Editor
- View ongoing queries
- Abort queries
- Integrations
- Queues
- Database
- Migrations
-Triggers (Updating)
Fixes#42089
Supersedes #43352 and
https://github.com/supabase/postgres-meta/pull/1049
## Problem
When creating a table in the Dashboard, if a column-specific error
occurs (e.g., invalid enum value or broken foreign key), the Dashboard
displays an error toast, but the side panel remains open. In the
background, the table is actually created (partially), leading to a
_42P07: relation already exists_ error if the user tries to click _Save_
again.
## Solution
We actually had nested transactions which is not supported by Postgres.
- [x] Allow generating SQL without transactions from `@supabase/pg-meta`
- [x] Fix the table creation
- [x] Fix some accessibility issues
- [x] Add tests
## How to test
1. Click _New table_ in the Table editor
2. Add a column with an _int8_ type and set its default value to
`bazinga`
3. Click _Save_
You should see an error.
4. Fix the default value by setting it to `10`
5. Click _Save_
Results:
- You should see a success message about the table creation
- The table should have the column with the correct default value
<img width="1543" height="981" alt="image"
src="https://github.com/user-attachments/assets/8e6c37c5-15c7-4bc4-be87-477e36a62a65"
/>
Switches from two to one column layout for details page and removes an
inconsistent background colour in header. Moves critical detail
information from page into header (url and deploy dates/counts)