# Supabase

Create, find, update and delete rows in a Supabase project's tables, call its database functions, and store files in Supabase Storage.

## Connect Supabase

1. In the sidebar, open **Connections** and press **Add connection**.
2. Type `supabase` and choose **Supabase**.
3. Fill in the fields. Each one says **Required** or **Optional**.
4. Press **Test and save**. GoRunner checks the connection before it saves anything.

The sheet asks for:

- **Project URL · Required**: https://\<project ref>.supabase.co, or your self-hosted Supabase address.
- **API key · Required**: A secret key (sb_secret_…) or legacy service_role key; a publishable or anon key works within Row Level Security.

Enter the project’s URL and an API key from the Supabase dashboard (Project Settings → API Keys). A secret key (sb_secret_…, or the legacy service_role key) acts with full access and is not held back by Row Level Security; a publishable key (sb_publishable_…, or the legacy anon key) can only do what your Row Level Security policies allow anonymous users. Listing tables and columns needs a secret key.

[Connections](https://docs.gorunner.io/build/connections) says how to sign in again, rename or delete it. In a product that embeds GoRunner, **Add connection** can be missing: then [your provider makes connections](https://docs.gorunner.io/embed/customer-connections) for you.

## Start a workflow

Add a trigger and choose **Supabase**. The trigger uses the same connection as the steps.

### On Row Added or Updated

Checks a Supabase table about once a minute and starts a run for each row added, or changed, since the check before. The first check after publishing only takes note of what is there (it starts nothing); changing the trigger starts over the same way. It reads every row of the table (at most 5,000; add conditions for a bigger one), and a row that leaves the conditions and comes back counts as added again.

- **Table · Required**: The table (or view) to watch.
- **Trigger on · Required**: Which changes start a run. Options: Row added, Row updated, Row added or updated. Default: Row added.
- **Key Column · Optional**: A column whose value is unique per row and never changes, usually the primary key. Default: id.
- **Schema · Optional**: The database schema, when it is not public.
- **Only Rows Matching · Optional**: Watch only the rows that meet all of these conditions. Empty watches every row.
- **Ignore Changes To · Optional**: Columns whose changes alone do not count as an update, comma-separated (such as last_seen_at, view_count).

A later step can use its data, such as `{{trigger.change_type}}`, `{{trigger.id}}`. See [Use data from earlier steps](https://docs.gorunner.io/build/use-data-from-earlier-steps).

## Do things in Supabase

Add a **Supabase** step, choose a **Resource**, then an **Operation**. Choose a row to see its fields.

- [Row](https://docs.gorunner.io/apps/supabase#fields-f-row): Create · Get · Get Many · Update · Create or Update · Delete
- [Table](https://docs.gorunner.io/apps/supabase#fields-f-table): Get Many · Get Columns
- [Database Function](https://docs.gorunner.io/apps/supabase#fields-f-function): Call
- [Storage Bucket](https://docs.gorunner.io/apps/supabase#fields-f-bucket): Get Many
- [Storage File](https://docs.gorunner.io/apps/supabase#fields-f-file): Upload · Download · Get Many · Create Signed URL · Move · Delete

Later steps read a step’s output by its name: see [Use data from earlier steps](https://docs.gorunner.io/build/use-data-from-earlier-steps).

## Limits

- A reply from Supabase can be up to 16 MB (a file download can have its own limit). A larger one fails the step; it is never cut short.
- When Supabase says to slow down (429), the step waits as long as it asks, if the step’s time allows. Otherwise it fails and says when to try again.

Not in your list? See [When an app isn’t here](https://docs.gorunner.io/apps/when-an-app-isnt-here#q-not-in-list).

## For developers

In the API, which calls apps pieces, Supabase is `pieceId: "supabase"` with `actionId: "supabase"`, set by `resource` and an `operation_…` field, such as `operation_bucket`. Its trigger is `supabasetrigger` / `supabase_poll`. [Get an app](https://docs.gorunner.io/api/apps/get-app) returns every field.

It can change something outside the run (`writes_external`). See [How versions and side effects work](https://docs.gorunner.io/apps#q-versions).

### Supabase › Row

### Create

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Columns · Required**: The values to write, by column name, such as {"email": "ada@example.com", "status": "active"}. Create and Create or Update also take a list of such objects (all with the same columns) to write many rows at once.
- **Return Rows · Optional**: Return the rows as they are after the change. Off returns only how many rows changed (no read access needed). On by default.

### Get

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Select Rows · Optional**: Which rows: by conditions, or by a filter in PostgREST's own syntax. Options: By conditions, By a PostgREST filter. Default: By conditions.
- **Must Match · Optional**: Options: All conditions, Any condition. Default: All conditions. Shows when **Select Rows** is **By conditions**.
- **Filter · Optional**: Query parameters in PostgREST syntax, such as status=eq.active&age=gte.18 or or=(plan.eq.pro,seats.gt.10). Written as it is: to filter by values from earlier steps, use conditions instead. Shows when **Select Rows** is **By a PostgREST filter**.
- **Conditions · Optional**: The conditions a row must meet. Get Many with none returns every row; Update and Delete need at least one. Shows when **Select Rows** is **By conditions**.
- **Return Columns · Optional**: The columns to return, comma-separated. Empty returns all.

### Get Many

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Select Rows · Optional**: Which rows: by conditions, or by a filter in PostgREST's own syntax. Options: By conditions, By a PostgREST filter. Default: By conditions.
- **Must Match · Optional**: Options: All conditions, Any condition. Default: All conditions. Shows when **Select Rows** is **By conditions**.
- **Filter · Optional**: Query parameters in PostgREST syntax, such as status=eq.active&age=gte.18 or or=(plan.eq.pro,seats.gt.10). Written as it is: to filter by values from earlier steps, use conditions instead. Shows when **Select Rows** is **By a PostgREST filter**.
- **Conditions · Optional**: The conditions a row must meet. Get Many with none returns every row; Update and Delete need at least one. Shows when **Select Rows** is **By conditions**.
- **Return Columns · Optional**: The columns to return, comma-separated. Empty returns all.
- **Sort · Optional**: Order the rows by these columns, in turn.
- **Return All · Optional**: Read every row, page by page (at most 5000; past that the step fails rather than stop early). Off by default.
- **Limit · Optional**: How many rows to return (1–5000). Default: 50. Shows when **Return All** is off.

### Update

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Columns · Required**: The values to write, by column name, such as {"email": "ada@example.com", "status": "active"}. Create and Create or Update also take a list of such objects (all with the same columns) to write many rows at once.
- **Select Rows · Optional**: Which rows: by conditions, or by a filter in PostgREST's own syntax. Options: By conditions, By a PostgREST filter. Default: By conditions.
- **Must Match · Optional**: Options: All conditions, Any condition. Default: All conditions. Shows when **Select Rows** is **By conditions**.
- **Filter · Optional**: Query parameters in PostgREST syntax, such as status=eq.active&age=gte.18 or or=(plan.eq.pro,seats.gt.10). Written as it is: to filter by values from earlier steps, use conditions instead. Shows when **Select Rows** is **By a PostgREST filter**.
- **Conditions · Optional**: The conditions a row must meet. Get Many with none returns every row; Update and Delete need at least one. Shows when **Select Rows** is **By conditions**.
- **Return Rows · Optional**: Return the rows as they are after the change. Off returns only how many rows changed (no read access needed). On by default.

### Create or Update

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Columns · Required**: The values to write, by column name, such as {"email": "ada@example.com", "status": "active"}. Create and Create or Update also take a list of such objects (all with the same columns) to write many rows at once.
- **Match On · Optional**: The column(s) with a unique constraint that say a row already exists, comma-separated. Empty uses the primary key (which the values must then include).
- **Keep Existing Rows · Optional**: Leave a row that already exists as it is, instead of updating it. Off by default.
- **Return Rows · Optional**: Return the rows as they are after the change. Off returns only how many rows changed (no read access needed). On by default.

### Delete

- **Table · Required**: The table (or view) to work with.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).
- **Select Rows · Optional**: Which rows: by conditions, or by a filter in PostgREST's own syntax. Options: By conditions, By a PostgREST filter. Default: By conditions.
- **Must Match · Optional**: Options: All conditions, Any condition. Default: All conditions. Shows when **Select Rows** is **By conditions**.
- **Filter · Optional**: Query parameters in PostgREST syntax, such as status=eq.active&age=gte.18 or or=(plan.eq.pro,seats.gt.10). Written as it is: to filter by values from earlier steps, use conditions instead. Shows when **Select Rows** is **By a PostgREST filter**.
- **Conditions · Optional**: The conditions a row must meet. Get Many with none returns every row; Update and Delete need at least one. Shows when **Select Rows** is **By conditions**.
- **Return Rows · Optional**: Return the rows as they are after the change. Off returns only how many rows changed (no read access needed). On by default.

### Supabase › Table

### Get Many

- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).

### Get Columns

- **Table · Required**: The table whose columns to read.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).

### Supabase › Database Function

### Call

- **Function · Required**: The database function to call.
- **Arguments · Optional**: The function's arguments by name, such as {"user_id": 42}. Empty calls it without arguments.
- **Schema · Optional**: The database schema, when it is not public. It must be one the project's Data API exposes (Project Settings → Data API).

### Supabase › Storage Bucket

### Get Many

Nothing to fill in.

### Supabase › Storage File

### Upload

- **Bucket · Required**: The storage bucket.
- **File Path · Required**: The file's path in the bucket, such as invoices/2026/inv-0042.pdf. Delete also takes a list of paths from an earlier step.
- **File · Required**: Upload a file, or switch to an expression to use one from an earlier step (such as {{steps.download.result}}). At most 11 MB (and within the bucket's own size limit).
- **Replace Existing File · Optional**: Overwrite a file already at this path. Off, an existing file makes the upload fail. Off by default.
- **Cache Seconds · Optional**: How long browsers and the CDN may cache the file. Empty keeps Supabase's default (3600).

### Download

- **Bucket · Required**: The storage bucket.
- **File Path · Required**: The file's path in the bucket, such as invoices/2026/inv-0042.pdf. Delete also takes a list of paths from an earlier step.
- **File Name · Optional**: The name the downloaded file gets. Empty uses the last part of the path.

### Get Many

- **Bucket · Required**: The storage bucket.
- **Folder · Optional**: The folder to list, such as invoices/2026. Empty lists the bucket's top level (folders appear as items without an id).
- **Search Text · Optional**: Only names that hold this text.
- **Return All · Optional**: Read every file, page by page (at most 5000; past that the step fails rather than stop early). Off by default.
- **Limit · Optional**: How many files to return (1–5000). Default: 100. Shows when **Return All** is off.

### Create Signed URL

- **Bucket · Required**: The storage bucket.
- **File Path · Required**: The file's path in the bucket, such as invoices/2026/inv-0042.pdf. Delete also takes a list of paths from an earlier step.
- **Valid For (seconds) · Optional**: How long the link works. Default: 3600.

### Move

- **Bucket · Required**: The storage bucket.
- **File Path · Required**: The file's path in the bucket, such as invoices/2026/inv-0042.pdf. Delete also takes a list of paths from an earlier step.
- **New Path · Required**: Where the file goes, such as archive/inv-0042.pdf.
- **New Bucket · Optional**: Another bucket to move it to. Empty keeps it in the same bucket.

### Delete

- **Bucket · Required**: The storage bucket.
- **File Path · Required**: The file's path in the bucket, such as invoices/2026/inv-0042.pdf. Delete also takes a list of paths from an earlier step.

Source: https://docs.gorunner.io/apps/supabase
