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.
How rules work
Add a rules extension to any table you want to protect:
{
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 usersnullor omitted blocks with403(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.
Example patterns
1. Private user profile
Each user can only read and edit their own row.
{
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.
{
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.
{
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.
{
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.
{
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:
{
"values": {
"title": "Hello",
"author": "CURRENT_USER_ID"
}
}