diff --git a/.github/workflows/label_prs.yml b/.github/workflows/label_prs.yml
index 7bd90fd102e72..9e9ac01756e90 100644
--- a/.github/workflows/label_prs.yml
+++ b/.github/workflows/label_prs.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: label
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
+ uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
- name: Comment when api-deploy-required is auto-applied
if: contains(steps.label.outputs.new-labels, 'api-deploy-required')
diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml
index 7ed452065a1a9..c517dc08bfe80 100644
--- a/.github/workflows/mirror.yml
+++ b/.github/workflows/mirror.yml
@@ -29,10 +29,10 @@ jobs:
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: us-east-1
- - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: public.ecr.aws
- - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml
index e40a023c52ccb..487fa98341634 100644
--- a/.github/workflows/publish_image.yml
+++ b/.github/workflows/publish_image.yml
@@ -46,7 +46,7 @@ jobs:
- uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
- name: Login to DockerHub
- uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
@@ -83,7 +83,7 @@ jobs:
tags: |
type=raw,value=${{ needs.settings.outputs.image_version }}_${{ env.arch }}
- - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
@@ -115,7 +115,7 @@ jobs:
steps:
- uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
- - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 1050707fc7490..168b1191370c2 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label Stale Issues
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
+ uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
with:
days-before-issue-stale: 30
stale-issue-message: 'After 30 days of inactivity, this issue has been marked as stale. Commenting (or other activity) will remove the stale label.'
diff --git a/.github/workflows/studio-e2e-test.yml b/.github/workflows/studio-e2e-test.yml
index 890ee7d2be283..9573189273d52 100644
--- a/.github/workflows/studio-e2e-test.yml
+++ b/.github/workflows/studio-e2e-test.yml
@@ -93,7 +93,7 @@ jobs:
with:
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
aws-region: us-east-1
- - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0
+ - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
if: steps.filter.outputs.studio == 'true' && !github.event.pull_request.head.repo.fork
with:
registry: public.ecr.aws
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 196a9cb40e8ee..b3a88a149eab5 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -422,6 +422,11 @@ export const gettingstarted: NavMenuConstant = {
url: '/guides/getting-started/quickstarts/ruby-on-rails' as `/${string}`,
enabled: !jsOnly,
},
+ {
+ name: 'Spring Boot',
+ url: '/guides/getting-started/quickstarts/spring-boot' as `/${string}`,
+ enabled: !jsOnly,
+ },
{
name: 'SolidJS',
url: '/guides/getting-started/quickstarts/solidjs',
diff --git a/apps/docs/content/guides/getting-started/quickstarts/spring-boot.mdx b/apps/docs/content/guides/getting-started/quickstarts/spring-boot.mdx
new file mode 100644
index 0000000000000..cc0015e5fcf4b
--- /dev/null
+++ b/apps/docs/content/guides/getting-started/quickstarts/spring-boot.mdx
@@ -0,0 +1,218 @@
+---
+title: 'Use Supabase with Spring Boot'
+subtitle: 'Learn how to create a Spring Boot project and connect it to your Supabase project.'
+breadcrumb: 'Framework Quickstarts'
+---
+
+
+
+## Prerequisites
+
+Before you begin, make sure you have:
+
+- Java 17 or later, which you can check with `java -version`
+- `curl` and `unzip`, to download and extract the generated project
+
+## 1. Create a Spring Boot project
+
+Use [Spring Initializr](https://start.spring.io) to scaffold a new project with the Web, Spring Data JPA, and Postgres Driver dependencies. Run the following from the directory where you keep your projects.
+
+```bash
+curl https://start.spring.io/starter.zip \
+ -d dependencies=web,data-jpa,postgresql \
+ -d type=maven-project \
+ -d language=java \
+ -d groupId=com.example \
+ -d artifactId=instruments \
+ -d name=instruments \
+ -o instruments.zip
+unzip instruments.zip -d instruments && cd instruments
+```
+
+## 2. Install Supabase's Agent Skills (optional)
+
+Supabase's [Agent Skills](/docs/guides/ai-tools/ai-skills) is a curated set of instructions that give your AI agent procedural knowledge about working with Supabase.
+
+Install them so your AI coding agent can produce more accurate, reliable code using current Supabase patterns, such as authentication, server-side rendering, and database migrations, rather than relying solely on training data.
+
+To install, run the following command in the root of your project:
+
+```bash
+npx skills add supabase/agent-skills
+```
+
+## 3. Set up the Postgres connection details
+
+Go to [database.new](https://database.new) and create a new Supabase project. Save your database password securely.
+
+When your project is up and running, navigate to its dashboard and click on [Connect](/dashboard/project/_?showConnect=true&method=session).
+
+
+
+The Transaction pooler (port `6543`) doesn't work as your app's main data source, because Spring Data JPA uses Hibernate, which relies on server-side prepared statements. Use the Session pooler, or the direct connection string if you're in an [IPv6 environment](/docs/guides/troubleshooting/supabase--your-network-ipv4-and-ipv6-compatibility-cHe3BP) or have the [IPv4 Add-On](/docs/guides/platform/ipv4-address).
+
+
+
+Under the **Session pooler** (port `5432`), select the **JDBC** tab and copy the connection string. Replace the password placeholder with your saved database password, and [percent-encode](https://en.wikipedia.org/wiki/Percent-encoding) any reserved characters it contains, such as `&`, `#`, `?`, or a space.
+
+
+
+You can reset your database password in your [Database Settings](/dashboard/project/_/database/settings) if you do not have it.
+
+
+
+The connection string contains your database password, and `application.properties` is committed with your project. Set the string as an environment variable instead, and set it the same way on whatever platform you deploy to.
+
+```bash
+export SUPABASE_DB_URL='jdbc:postgresql://xxxx.pooler.supabase.com:5432/postgres?user=postgres.xxxx&password=[YOUR-PASSWORD]&sslmode=require'
+```
+
+The string you copied doesn't set `sslmode`, so add it. The driver defaults to `prefer`, which falls back to sending your data in plaintext if the encrypted attempt fails. You can also [enforce SSL](/docs/guides/platform/ssl-enforcement) on the database side.
+
+Then reference the variable, along with the driver, in `src/main/resources/application.properties`.
+
+```text name=src/main/resources/application.properties
+spring.datasource.url=${SUPABASE_DB_URL}
+spring.datasource.driver-class-name=org.postgresql.Driver
+spring.jpa.hibernate.ddl-auto=update
+```
+
+If the app fails to start with `Unable to determine Dialect without JDBC metadata`, Hibernate couldn't open a connection at all. Look above that line in the logs for the real cause, most commonly `password authentication failed`.
+
+## 4. Change the default schema
+
+By default Hibernate creates tables in the `public` schema. We recommend changing this as Supabase exposes the `public` schema as a [data API](/docs/guides/api).
+
+Create the schema from the [Table Editor](/dashboard/project/_/editor) as your app will need it before start. Then point **Hibernate** at it in `application.properties`.
+
+```text name=src/main/resources/application.properties
+spring.jpa.properties.hibernate.default_schema=app
+```
+
+## 5. Create an entity and repository
+
+Spring Data JPA maps Java classes to database tables. Create an `Instrument` entity in `src/main/java/com/example/instruments/Instrument.java`. With `spring.jpa.hibernate.ddl-auto=update` set, Hibernate creates the `instruments` table for you when the app starts.
+
+```java name=src/main/java/com/example/instruments/Instrument.java
+package com.example.instruments;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name = "instruments")
+public class Instrument {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ public Instrument() {}
+
+ public Instrument(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+Create an `InstrumentRepository` interface in the same package. Extending `JpaRepository` gives you `findAll`, `save`, and other query methods without writing any implementation.
+
+```java name=src/main/java/com/example/instruments/InstrumentRepository.java
+package com.example.instruments;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface InstrumentRepository extends JpaRepository {}
+```
+
+## 6. Seed sample data
+
+Add a `CommandLineRunner` bean to `InstrumentsApplication.java` that saves some sample instruments the first time the app starts.
+
+```java name=src/main/java/com/example/instruments/InstrumentsApplication.java
+package com.example.instruments;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication
+public class InstrumentsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(InstrumentsApplication.class, args);
+ }
+
+ @Bean
+ CommandLineRunner seedInstruments(InstrumentRepository instrumentRepository) {
+ return args -> {
+ if (instrumentRepository.count() == 0) {
+ instrumentRepository.save(new Instrument("violin"));
+ instrumentRepository.save(new Instrument("viola"));
+ instrumentRepository.save(new Instrument("cello"));
+ }
+ };
+ }
+}
+```
+
+## 7. Query data from the app
+
+Create an `InstrumentController` that fetches every row from the `instruments` table through the repository and returns it as JSON.
+
+```java name=src/main/java/com/example/instruments/InstrumentController.java
+package com.example.instruments;
+
+import java.util.List;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class InstrumentController {
+
+ private final InstrumentRepository instrumentRepository;
+
+ public InstrumentController(InstrumentRepository instrumentRepository) {
+ this.instrumentRepository = instrumentRepository;
+ }
+
+ @GetMapping("/instruments")
+ public List getInstruments() {
+ return instrumentRepository.findAll();
+ }
+}
+```
+
+## 8. Start the app
+
+Run the Spring Boot app, and go to http://localhost:8080/instruments in your browser. You should see the list of instruments.
+
+```bash
+./mvnw spring-boot:run
+```
+
+## Next steps
+
+- Set up [Auth](/docs/guides/auth) for your app
+- Replace `ddl-auto` with [database migrations](/docs/guides/deployment/database-migrations) before going to production
+- [Insert more data](/docs/guides/database/import-data) into your database
+- Upload and serve static files using [Storage](/docs/guides/storage)
diff --git a/apps/docs/data/ai-prompts.data.ts b/apps/docs/data/ai-prompts.data.ts
index 74cf6930033e6..788b3acf364e5 100644
--- a/apps/docs/data/ai-prompts.data.ts
+++ b/apps/docs/data/ai-prompts.data.ts
@@ -201,6 +201,30 @@ database.new and run the instruments table SQL. Then:
REFERENCE
https://supabase.com/docs/guides/getting-started/quickstarts/solidjs.md`,
+ 'spring-boot': `Help me add Supabase to my Spring Boot project. Create a Supabase project at
+database.new. Then:
+1. Run \`curl https://start.spring.io/starter.zip -d dependencies=web,data-jpa,postgresql
+ -d type=maven-project -d language=java -d groupId=com.example -d artifactId=instruments
+ -d name=instruments -o instruments.zip\` and unzip it to scaffold the project.
+2. Copy the JDBC connection string for the Session pooler (port 5432) from the Supabase
+ Connect panel and export it as a \`SUPABASE_DB_URL\` environment variable, so the
+ password stays out of source control. Set \`spring.datasource.url=\${SUPABASE_DB_URL}\`
+ and \`spring.datasource.driver-class-name\` in \`application.properties\`. Avoid the
+ Transaction pooler (port 6543) since Hibernate relies on prepared statements.
+3. Set \`spring.jpa.hibernate.ddl-auto=update\` and
+ \`spring.jpa.properties.hibernate.default_schema\` in \`application.properties\`, so
+ Hibernate creates tables outside the \`public\` schema that Supabase exposes as a data API.
+4. Create an \`Instrument\` JPA entity mapped to the \`instruments\` table with
+ \`@Table(name = "instruments")\`, and an \`InstrumentRepository\` extending
+ \`JpaRepository\`.
+5. Add a \`CommandLineRunner\` bean to \`InstrumentsApplication\` that seeds the table
+ with a few instruments the first time the app starts.
+6. Create an \`InstrumentController\` with a \`GET /instruments\` endpoint that returns
+ \`instrumentRepository.findAll()\`.
+7. Run \`./mvnw spring-boot:run\` and open http://localhost:8080/instruments.
+
+REFERENCE
+https://supabase.com/docs/guides/getting-started/quickstarts/spring-boot.md`,
sveltekit: `Help me add Supabase to my SvelteKit project. Create a Supabase project at
database.new and run the instruments table SQL. Then:
1. Run \`npx sv create my-app\` to scaffold the app.
diff --git a/apps/studio/.github/eslint-rule-baselines.json b/apps/studio/.github/eslint-rule-baselines.json
index bb9dff36ebbd9..e19a374737311 100644
--- a/apps/studio/.github/eslint-rule-baselines.json
+++ b/apps/studio/.github/eslint-rule-baselines.json
@@ -2,7 +2,7 @@
"rules": {
"react-hooks/exhaustive-deps": 160,
"import/no-anonymous-default-export": 57,
- "@tanstack/query/exhaustive-deps": 9,
+ "@tanstack/query/exhaustive-deps": 89,
"@typescript-eslint/no-explicit-any": 875,
"no-restricted-imports": 0,
"no-restricted-exports": 193,
@@ -208,11 +208,86 @@
"@tanstack/query/exhaustive-deps": {
"components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx": 1,
"components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx": 2,
+ "data/auth/index-worker-status-query.ts": 1,
+ "data/auth/user-query.ts": 1,
+ "data/auth/user-search-indexes-query.ts": 1,
+ "data/auth/users-count-query.ts": 1,
+ "data/auth/users-infinite-query.ts": 1,
"data/branches/branch-diff-query.ts": 1,
+ "data/config/disk-breakdown-query.ts": 1,
+ "data/config/project-upgrade-status-query.ts": 1,
+ "data/database-cron-jobs/database-cron-job-query.ts": 1,
+ "data/database-cron-jobs/database-cron-jobs-count-estimate-query.ts": 1,
+ "data/database-cron-jobs/database-cron-jobs-count-query.ts": 1,
+ "data/database-cron-jobs/database-cron-jobs-infinite-query.ts": 1,
+ "data/database-cron-jobs/database-cron-jobs-minimal-infinite-query.ts": 1,
+ "data/database-cron-jobs/database-cron-jobs-runs-infinite-query.ts": 1,
+ "data/database-cron-jobs/database-cron-timezone-query.ts": 1,
+ "data/database-event-triggers/database-event-triggers-query.ts": 1,
+ "data/database-extensions/database-extensions-query.ts": 1,
+ "data/database-functions/database-functions-query.ts": 1,
+ "data/database-indexes/indexes-query.ts": 1,
+ "data/database-integrations/stripe/sync-state-query.ts": 1,
+ "data/database-policies/database-policies-query.ts": 1,
+ "data/database-publications/database-publications-query.ts": 1,
+ "data/database-queues/database-queue-messages-infinite-query.ts": 1,
+ "data/database-queues/database-queues-expose-postgrest-status-query.ts": 1,
+ "data/database-queues/database-queues-metrics-query.ts": 1,
+ "data/database-queues/database-queues-query.ts": 1,
+ "data/database-roles/database-roles-query.ts": 1,
+ "data/database-triggers/database-triggers-query.ts": 2,
+ "data/database/activity-query.ts": 1,
+ "data/database/constraints-query.ts": 1,
+ "data/database/database-size-query.ts": 1,
"data/database/foreign-key-constraints-query.ts": 1,
- "data/database/schemas-query.ts": 1,
- "data/entity-types/entity-types-infinite-query.ts": 1,
- "data/tables/tables-query.ts": 2
+ "data/database/keywords-query.ts": 1,
+ "data/database/max-connections-query.ts": 1,
+ "data/database/migrations-query.ts": 1,
+ "data/database/retrieve-index-advisor-result-query.ts": 1,
+ "data/database/retrieve-index-from-select-query.ts": 1,
+ "data/database/schemas-query.ts": 2,
+ "data/database/supamonitor-enabled-query.ts": 1,
+ "data/database/table-columns-query.ts": 1,
+ "data/database/table-definition-query.ts": 1,
+ "data/database/table-index-advisor-query.ts": 1,
+ "data/database/view-definition-query.ts": 1,
+ "data/entity-types/entity-types-infinite-query.ts": 2,
+ "data/enumerated-types/enumerated-types-query.ts": 1,
+ "data/fdw/fdws-query.ts": 1,
+ "data/foreign-tables/foreign-tables-query.ts": 1,
+ "data/invoices/invoices-query.ts": 1,
+ "data/logs/unified-log-inspection-query.ts": 1,
+ "data/materialized-views/materialized-views-query.ts": 1,
+ "data/misc/get-default-region-query.ts": 1,
+ "data/oauth-server-apps/oauth-openid-configuration-query.ts": 1,
+ "data/organizations/organization-audit-logs-query.ts": 1,
+ "data/organizations/organization-billing-subscription-preview.ts": 1,
+ "data/organizations/organization-creation-preview.ts": 1,
+ "data/organizations/organization-credit-top-up-preview.ts": 1,
+ "data/pg-graphql/schema-comment-query.ts": 1,
+ "data/privileges/column-privileges-query.ts": 1,
+ "data/privileges/exposed-functions-query.ts": 1,
+ "data/privileges/exposed-tables-query.ts": 1,
+ "data/privileges/table-privileges-query.ts": 1,
+ "data/profile/profile-audit-logs-query.ts": 1,
+ "data/projects/project-detail-query.ts": 1,
+ "data/projects/project-type-generation-query.ts": 1,
+ "data/read-replicas/replica-lag-query.ts": 1,
+ "data/sql/ongoing-queries-query.ts": 1,
+ "data/storage/bucket-objects-infinite-query.ts": 1,
+ "data/storage/iceberg-namespace-tables-query.ts": 1,
+ "data/storage/public-buckets-with-select-policies-query.ts": 1,
+ "data/table-editor/table-editor-query.ts": 1,
+ "data/table-rows/table-rows-count-query.ts": 1,
+ "data/table-rows/table-rows-query.ts": 1,
+ "data/tables/table-names-query.ts": 1,
+ "data/tables/table-retrieve-query.ts": 1,
+ "data/tables/tables-query.ts": 4,
+ "data/usage/org-usage-query.ts": 1,
+ "data/usage/resource-warnings-query.ts": 1,
+ "data/vault/vault-secret-decrypted-value-query.ts": 1,
+ "data/vault/vault-secrets-query.ts": 1,
+ "data/views/views-query.ts": 1
},
"@typescript-eslint/no-explicit-any": {
"components/grid/SupabaseGrid.tsx": 1,
diff --git a/apps/studio/components/grid/components/header/filter/FilterPopoverNew.tsx b/apps/studio/components/grid/components/header/filter/FilterPopoverNew.tsx
index 07a5a4aff6446..dc8a5e96c39eb 100644
--- a/apps/studio/components/grid/components/header/filter/FilterPopoverNew.tsx
+++ b/apps/studio/components/grid/components/header/filter/FilterPopoverNew.tsx
@@ -143,7 +143,6 @@ export const FilterPopoverNew = ({
useEffect(() => {
syncFromFilters()
- // eslint-disable-next-line react-hooks/exhaustive-deps -- useEffectEvent fn intentionally not a dep (eslint-plugin-react-hooks v5 doesn't recognize stable useEffectEvent yet)
}, [filters])
const columns = useMemo(() => snap.table?.columns ?? [], [snap.table?.columns])
diff --git a/apps/studio/components/grid/components/header/filter/FilterRow.tsx b/apps/studio/components/grid/components/header/filter/FilterRow.tsx
index 6881a292dd062..09283e041eda1 100644
--- a/apps/studio/components/grid/components/header/filter/FilterRow.tsx
+++ b/apps/studio/components/grid/components/header/filter/FilterRow.tsx
@@ -1,6 +1,6 @@
import { ChevronDown, X } from 'lucide-react'
import { KeyboardEvent, memo } from 'react'
-import { Button, Input } from 'ui'
+import { Button, Input, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { FilterOperatorOptions } from './Filter.constants'
import { DropdownControl } from '@/components/grid/components/common/DropdownControl'
@@ -93,12 +93,18 @@ const FilterRow = ({ filter, filterIdx, onChange, onDelete, onKeyDown }: FilterR
}
onKeyDown={onKeyDown}
/>
- }
- onClick={() => onDelete(filterIdx)}
- />
+
+
+ }
+ onClick={() => onDelete(filterIdx)}
+ aria-label="Remove filter"
+ />
+
+ Remove filter
+
)
}
diff --git a/apps/studio/components/grid/components/header/sort/SortRow.tsx b/apps/studio/components/grid/components/header/sort/SortRow.tsx
index 61642b4de2779..23a737c3bd86c 100644
--- a/apps/studio/components/grid/components/header/sort/SortRow.tsx
+++ b/apps/studio/components/grid/components/header/sort/SortRow.tsx
@@ -2,7 +2,7 @@ import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { GripVertical, X } from 'lucide-react'
import { memo } from 'react'
-import { Button, Switch } from 'ui'
+import { Button, Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import type { Sort } from '@/components/grid/types'
import { useTableEditorTableStateSnapshot } from '@/state/table-editor-table'
@@ -53,19 +53,26 @@ const SortRow = ({ index, columnName, sort, onDelete, onToggle }: SortRowProps)