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?

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:

RuleControls
listRuleselect and list queries
viewRuleview/:id lookups
createRuleinserts
updateRuleupdates and edits
deleteRuledeletes

Rule values

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

Variables

VariableDescription
auth.uidSigned-in user's ID. null if not logged in.
auth.roleUser's role from the role field.
auth.emailUser's email from JWT.
Column namesValue on the current row (e.g. owner, published).
new.*Incoming values in create/update rules (e.g. new.role).

Operators

OperatorMeaningExample
==equalsauth.uid == id
!=not equalsauth.uid != null
> < >= <=comparisonprice > 0
~LIKEemail ~ '%@example.com'
!~NOT LIKErole !~ '%admin'
!NOT (unary)!deleted_at
&ANDauth.uid == id & published == true
|ORpublished == true | auth.uid == author
@@full-text searcharticles @@ 'search term'

For the full expression syntax including SQL functions, see the Config Reference.

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"
  }
}