home / skills / vm0-ai / vm0-skills / supabase

supabase skill

/supabase

This skill helps you perform Supabase REST API CRUD operations via curl, enabling filtered reads, inserts, updates, deletes, and upserts.

npx playbooks add skill vm0-ai/vm0-skills --skill supabase

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.2 KB
---
name: supabase
description: Supabase REST API via curl. Use this skill for database CRUD operations, filtering, pagination, and real-time data management.
vm0_secrets:
  - SUPABASE_SECRET_KEY
vm0_vars:
  - SUPABASE_URL
  - SUPABASE_PUBLISHABLE_KEY
---

# Supabase REST API

Use the Supabase REST API via direct `curl` calls to **perform database CRUD operations**.

Supabase auto-generates a RESTful API from your PostgreSQL database schema using [PostgREST](https://postgrest.org/).

> Official docs: `https://supabase.com/docs/guides/api`

---

## When to Use

Use this skill when you need to:

- **Read data** from Supabase tables with filtering and pagination
- **Insert rows** into tables (single or bulk)
- **Update rows** based on conditions
- **Delete rows** from tables
- **Upsert data** (insert or update)
- **Query with complex filters** using PostgREST operators

---

## Prerequisites

1. Create a Supabase project at https://supabase.com
2. Go to **Project Settings → API Keys**
3. Click **Create new API Keys** if needed
4. Copy the **Project URL** and keys

```bash
export SUPABASE_URL="https://your-project-ref.supabase.co"
export SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."
export SUPABASE_SECRET_KEY="sb_secret_..."
```

**API Keys:**

| Key Type | Format | Use Case |
|----------|--------|----------|
| Publishable | `sb_publishable_...` | Client-side, respects Row Level Security (RLS) |
| Secret | `sb_secret_...` | Server-side only, bypasses RLS |

> **Note:** Legacy `anon` and `service_role` JWT keys still work but are deprecated. Use the new `sb_publishable_` and `sb_secret_` keys instead.

---


> **Important:** When using `$VAR` in a command that pipes to another command, wrap the command containing `$VAR` in `bash -c '...'`. Due to a Claude Code bug, environment variables are silently cleared when pipes are used directly.
> ```bash
> bash -c 'curl -s "https://api.example.com" -H "Authorization: Bearer $API_KEY"'
> ```

## How to Use

Base URL: `${SUPABASE_URL}/rest/v1`

All requests require the `apikey` header with your API key.

---

### 1. Read All Rows

Get all rows from a table:

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?select=*" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 2. Select Specific Columns

Get only specific columns:

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?select=id,name,email" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 3. Filter with Operators

Filter rows using PostgREST operators.

**Equal to:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?status=eq.active" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Greater than:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/products?price=gt.100" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Multiple conditions (AND):**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?age=gte.18&status=eq.active" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Available Operators:**

| Operator | Meaning | Example |
|----------|---------|---------|
| `eq` | Equals | `?status=eq.active` |
| `neq` | Not equals | `?status=neq.deleted` |
| `gt` | Greater than | `?age=gt.18` |
| `gte` | Greater than or equal | `?age=gte.21` |
| `lt` | Less than | `?price=lt.100` |
| `lte` | Less than or equal | `?price=lte.50` |
| `like` | Pattern match (use `*` for `%`) | `?name=like.*john*` |
| `ilike` | Case-insensitive pattern | `?name=ilike.*john*` |
| `in` | In list | `?id=in.(1,2,3)` |
| `is` | Is null/true/false | `?deleted_at=is.null` |

---

### 4. OR Conditions

Use `or` for OR logic:

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?or=(status.eq.active,status.eq.pending)" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 5. Ordering

Sort results.

**Ascending:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?order=created_at.asc" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Descending:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?order=created_at.desc" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Multiple columns:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?order=status.asc,created_at.desc" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 6. Pagination

Use `limit` and `offset`.

**First 10 rows:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?limit=10" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Page 2 (rows 11-20):**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?limit=10&offset=10" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 7. Get Row Count

Use `Prefer: count=exact` header:

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?select=*" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}" -H "Prefer: count=exact" -I | grep -i "content-range"'
```

---

### 8. Insert Single Row

Write to `/tmp/supabase_request.json`:

```json
{
  "name": "John Doe",
  "email": "[email protected]"
}
```

Then run:

```bash
bash -c 'curl -s -X POST "${SUPABASE_URL}/rest/v1/users" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Content-Type: application/json" -H "Prefer: return=representation" -d @/tmp/supabase_request.json'
```

---

### 9. Insert Multiple Rows

Write to `/tmp/supabase_request.json`:

```json
[
  {"name": "John", "email": "[email protected]"},
  {"name": "Jane", "email": "[email protected]"}
]
```

Then run:

```bash
bash -c 'curl -s -X POST "${SUPABASE_URL}/rest/v1/users" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Content-Type: application/json" -H "Prefer: return=representation" -d @/tmp/supabase_request.json'
```

---

### 10. Update Rows

Update rows matching a filter.

Write to `/tmp/supabase_request.json`:

```json
{
  "status": "inactive"
}
```

Then run:

```bash
bash -c 'curl -s -X PATCH "${SUPABASE_URL}/rest/v1/users?id=eq.1" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Content-Type: application/json" -H "Prefer: return=representation" -d @/tmp/supabase_request.json'
```

---

### 11. Upsert (Insert or Update)

Use `Prefer: resolution=merge-duplicates`.

Write to `/tmp/supabase_request.json`:

```json
{
  "id": 1,
  "name": "John Updated",
  "email": "[email protected]"
}
```

Then run:

```bash
bash -c 'curl -s -X POST "${SUPABASE_URL}/rest/v1/users" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Content-Type: application/json" -H "Prefer: resolution=merge-duplicates,return=representation" -d @/tmp/supabase_request.json'
```

---

### 12. Delete Rows

Delete rows matching a filter:

```bash
bash -c 'curl -s -X DELETE "${SUPABASE_URL}/rest/v1/users?id=eq.1" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Prefer: return=representation"'
```

---

### 13. Query Related Tables

Embed related data using foreign keys.

**Get posts with their author:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/posts?select=*,author:users(*)" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

**Get users with their posts:**

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/users?select=*,posts(*)" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 14. Full-Text Search

Search text columns:

```bash
bash -c 'curl -s "${SUPABASE_URL}/rest/v1/posts?title=fts.hello" -H "apikey: ${SUPABASE_PUBLISHABLE_KEY}"'
```

---

### 15. Call RPC Functions

Call PostgreSQL functions.

Write to `/tmp/supabase_request.json`:

```json
{
  "param1": "value1"
}
```

Then run:

```bash
bash -c 'curl -s -X POST "${SUPABASE_URL}/rest/v1/rpc/my_function" -H "apikey: ${SUPABASE_SECRET_KEY}" -H "Content-Type: application/json" -d @/tmp/supabase_request.json'
```

---

## Response Headers

| Header | Description |
|--------|-------------|
| `Content-Range` | Row range and total count (e.g., `0-9/100`) |
| `Preference-Applied` | Confirms applied preferences |

---

## Guidelines

1. **Use publishable key** for read operations with RLS enabled
2. **Use secret key** only server-side for write operations or admin access
3. **Enable RLS** on tables for security when using publishable key
4. **Use `select`** to limit returned columns for better performance
5. **Add indexes** on frequently filtered columns
6. **Use `Prefer: return=representation`** to get inserted/updated rows back
7. **Avoid full-table operations** without filters to prevent accidental data loss

---

## API Reference

- Supabase API Docs: https://supabase.com/docs/guides/api
- API Keys Guide: https://supabase.com/docs/guides/api/api-keys
- PostgREST Docs: https://postgrest.org/en/stable/
- API Settings: https://supabase.com/dashboard/project/_/settings/api-keys

Overview

This skill provides ready-to-run curl commands for interacting with Supabase's auto-generated REST API (PostgREST) to perform CRUD, filtering, pagination, and basic real-time related queries. It focuses on practical shell examples for read, insert, update, upsert, delete, relations, full-text search, RPC calls, and counting rows. Use it to quickly script server-side database operations with publishable or secret keys.

How this skill works

The skill issues HTTP requests to ${SUPABASE_URL}/rest/v1 using curl and the appropriate Supabase API key in headers. Queries use PostgREST URL parameters and headers (e.g., select, order, limit, operators, Prefer) to control projection, filtering, pagination, and response behavior. For write operations and admin-level access, use the secret key server-side; for client reads under RLS, use the publishable key.

When to use it

  • Fetch table rows with filters, sorting, pagination, or related embeds
  • Insert single or bulk rows and return created records
  • Update, upsert (merge duplicates), or delete rows with condition-based filters
  • Call PostgreSQL RPC functions exposed via REST
  • Get exact row counts using Prefer: count=exact for pagination UIs

Best practices

  • Store SUPABASE_URL and keys in environment variables; never commit keys
  • Use publishable key for client reads with RLS enabled; use secret key only server-side
  • Always include select to limit returned columns and improve performance
  • Add indexes on frequently filtered or ordered columns
  • Use Prefer: return=representation when you need the modified rows returned
  • Avoid unfiltered write or delete requests to prevent accidental data loss

Example use cases

  • List first 10 active users sorted by creation date for an admin panel
  • Bulk-insert new signups from a CSV via a server-side script
  • Patch a user record to deactivate an account with id filter
  • Upsert product data from an external feed to keep inventory synchronized
  • Call a stored procedure via RPC to run server-side business logic

FAQ

Which key should I use for reads and writes?

Use the publishable key for client-side reads when RLS is enabled; use the secret key server-side for writes or admin actions.

How do I get total row counts for pagination?

Send Prefer: count=exact and inspect the Content-Range header or use -I with curl to read headers.