---
title: "Row-Level Security (RLS)"
description: "teenybase rules are row-level security: expressions like auth.uid == owner_id that compile to SQL WHERE clauses, with patterns for common access cases."
url: https://teenybase.com/docs/guides/rls
last_updated: 2026-04-19
---

> Documentation index: https://teenybase.com/llms.txt lists every teenybase docs page with a link to its Markdown version. All docs in one file: https://teenybase.com/llms-full.txt

# Row-Level Security (RLS)

Rules are teenybase's row-level security (RLS). Each rule is an expression that compiles to a SQL WHERE clause. When a request hits your API, teenybase injects the rule into the query so users can only access rows that match.

> **Using a coding agent?** Paste this prompt:
>
> Write teenybase rules for this app using simple copy-paste patterns. Cover private user profiles, owner-only records, public published posts with private drafts, admin-only tables, and soft delete.

## How rules work

Add a `rules` extension to any table you want to protect:

```ts
{
  name: 'rules',
  listRule: '...',
  viewRule: '...',
  createRule: '...',
  updateRule: '...',
  deleteRule: '...',
}
```

Each rule controls one type of operation:

| Rule | Controls |
|---|---|
| `listRule` | `select` and `list` queries |
| `viewRule` | `view/:id` lookups |
| `createRule` | inserts |
| `updateRule` | updates and edits |
| `deleteRule` | deletes |

### Rule values

- `'true'` allows everyone, including unauthenticated users
- `null` or omitted blocks with `403` (admin users bypass this)
- Any expression evaluates per row

### Variables

| Variable | Description |
|---|---|
| `auth.uid` | Signed-in user's ID. `null` if not logged in. |
| `auth.role` | User's role from the `role` field. |
| `auth.email` | User's email from JWT. |
| Column names | Value on the current row (e.g. `owner`, `published`). |
| `new.*` | Incoming values in create/update rules (e.g. `new.role`). |

### Operators

| Operator | Meaning | Example |
|---|---|---|
| `==` | equals | `auth.uid == id` |
| `!=` | not equals | `auth.uid != null` |
| `>` `<` `>=` `<=` | comparison | `price > 0` |
| `~` | LIKE | `email ~ '%@example.com'` |
| `!~` | NOT LIKE | `role !~ '%admin'` |
| `!` | NOT (unary) | `!deleted_at` |
| `&` | AND | `auth.uid == id & published == true` |
| `\|` | OR | `published == true \| auth.uid == author` |
| `@@` | full-text search | `articles @@ 'search term'` |

For the full expression syntax including SQL functions, see the [Config Reference](https://teenybase.com/docs/reference/config-reference#expression-syntax).

## Example patterns

### 1. Private user profile

Each user can only read and edit their own row.

```ts
{
  name: 'users',
  extensions: [
    {
      name: 'rules',
      createRule: 'true',
      listRule: 'auth.uid == id',
      viewRule: 'auth.uid == id',
      updateRule: 'auth.uid == id',
      deleteRule: 'auth.uid == id',
    },
  ],
}
```

`createRule` must be `'true'` because sign-up inserts the user row before the new user has a session.

### 2. Public published posts

Anyone can read published posts. Authors can also see their own unpublished posts.

```ts
{
  name: 'posts',
  fields: [
    ...baseFields,
    { name: 'author', type: 'text', sqlType: 'text' },
    { name: 'published', type: 'bool', sqlType: 'boolean', default: '0' },
  ],
  extensions: [
    {
      name: 'rules',
      listRule: 'published == true | auth.uid == author',
      viewRule: 'published == true | auth.uid == author',
      createRule: 'auth.uid == author',
      updateRule: 'auth.uid == author',
      deleteRule: 'auth.uid == author',
    },
  ],
}
```

Create requests must send the signed-in user's ID as `author`.

### 3. Owner-only records

For notes, uploads, tasks, or any private user data.

```ts
{
  name: 'photos',
  fields: [
    ...baseFields,
    { name: 'owner', type: 'text', sqlType: 'text' },
    { name: 'image', type: 'file', sqlType: 'text' },
  ],
  extensions: [
    {
      name: 'rules',
      listRule: 'auth.uid == owner',
      viewRule: 'auth.uid == owner',
      createRule: 'auth.uid == owner',
      updateRule: 'auth.uid == owner',
      deleteRule: 'auth.uid == owner',
    },
  ],
}
```

Inserts must send `owner: CURRENT_USER_ID`.

### 4. Admin-only table

For internal data that regular users should not access.

```ts
{
  name: 'admin_panel_data',
  extensions: [
    {
      name: 'rules',
      listRule: "auth.role == 'admin'",
      viewRule: "auth.role == 'admin'",
      createRule: "auth.role == 'admin'",
      updateRule: "auth.role == 'admin'",
      deleteRule: "auth.role == 'admin'",
    },
  ],
}
```

The `role` field from `authFields` is available as `auth.role` in rules.

### 5. Soft delete

Hide records instead of deleting them. Block hard deletes via the API.

```ts
{
  name: 'posts',
  fields: [
    ...baseFields,
    { name: 'author', type: 'text', sqlType: 'text' },
    { name: 'deleted', type: 'bool', sqlType: 'boolean', default: '0' },
  ],
  extensions: [
    {
      name: 'rules',
      listRule: 'deleted == false & auth.uid == author',
      viewRule: 'deleted == false & auth.uid == author',
      createRule: 'auth.uid == author',
      updateRule: 'auth.uid == author',
      deleteRule: null,
    },
  ],
}
```

"Delete" a record by updating `deleted` to `true`. The `deleteRule: null` prevents actual deletion.

## Rules don't set ownership

Rules only check values. They don't set them. If your create rule says `auth.uid == author`, teenybase checks whether `author` in the request body matches the signed-in user's ID. It will not fill in `author` for you. Your frontend must send it:

```json
{
  "values": {
    "title": "Hello",
    "author": "CURRENT_USER_ID"
  }
}
```
