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