Skip to content

Row-Level Security

Pulsabase allows you to define Row-Level Security (RLS) policies directly within your TypeScript models. Policies are enforced at the PostgreSQL database level — not in application code — meaning they cannot be bypassed by any SDK call, raw query, or misconfigured backend.

  1. You define access rules in the policies array of your static schema
  2. The Pulsabase CLI generates the corresponding CREATE POLICY statements
  3. On pulsabase db:sync, policies are applied to your PostgreSQL database
  4. Every SDK query is automatically filtered — no application code required

A policy defines which action it applies to, the required role or permission, an owner matching column, or a public flag.

import { ModelSchema, primary, text, integer } from 'pulsabase';
export default class Note {
@primary
id: string;
@text()
title: string;
@text()
content: string;
@text()
user_id: string; // Foreign key to the owner
@boolean()
is_public: boolean;
static schema: ModelSchema = {
tableName: 'notes',
timestamps: true,
policies: [
// 1. Any authenticated user can create a note
{ action: ['INSERT'], authenticated: true },
// 2. Only the owner can read, edit, or delete their private notes
{ action: ['SELECT', 'UPDATE', 'DELETE'], authenticated: true, owner: 'user_id' },
// 3. Admins can delete any note
{ action: ['DELETE'], role: 'admin' },
// 4. Public notes can be read by anyone (including unauthenticated users)
{ action: ['SELECT'], public: true, pivotWhere: { is_public: true } },
]
};
}

Logic between fields in a single policy object is AND. A user must satisfy all specified conditions. Logic within arrays (e.g., role: ['admin', 'editor']) is OR — any matching role grants access.

PropertyTypeDescriptionGenerated SQL Condition
actionstring[]SQL commands: SELECT, INSERT, UPDATE, DELETEFOR SELECT / INSERT / ...
authenticatedbooleanApplies to any signed-in usercurrent_setting('request.jwt.sub') IS NOT NULL
rolestring | string[]Required IDP rolecurrent_setting('request.jwt.role') = 'admin'
permissionstring | string[]Required IDP permissioncurrent_setting('request.jwt.permissions') @> '"rooms.create"'
ownerstringColumn that must match the JWT sub (user ID)user_id = current_setting('request.jwt.sub')
publicbooleanApplies to everyone, including anonymous usersTRUE
pivotWhereobjectAdditional row-level condition on top of the access checkAND is_public = true

Configure how Pulsabase resolves the current user in Dashboard → Settings → Auth Mode:

ModeDescription
IDPUse Pulsabase’s built-in Identity Provider (recommended). JWTs are signed and verified automatically.
JWKSUse an external provider (Auth0, Clerk, Firebase, Supabase, etc.). Pulsabase fetches the JWKS endpoint to validate external JWTs.
NONERLS is disabled entirely for the project. Development only.

Pulsabase supports two-way binding for RLS policies.

If you define your policies in TypeScript models:

Terminal window
# Preview the generated CREATE POLICY statements
pulsabase db:sync --dry-run
# Apply policies to the database
pulsabase db:sync

If you create policies visually from the Dashboard, pull them to your local code:

Terminal window
pulsabase db:pull

Pulsabase introspects your PostgreSQL database and populates the policies array in your local model files.

policies: [
{ action: ['INSERT'], authenticated: true },
{ action: ['SELECT', 'UPDATE', 'DELETE'], authenticated: true, owner: 'user_id' },
]
policies: [
{ action: ['SELECT'], public: true },
{ action: ['INSERT', 'UPDATE', 'DELETE'], authenticated: true },
]
policies: [
{ action: ['SELECT'], authenticated: true },
{ action: ['INSERT', 'UPDATE', 'DELETE'], role: 'admin' },
]
policies: [
{ action: ['SELECT'], authenticated: true },
{ action: ['INSERT'], permission: 'content.write' },
{ action: ['DELETE'], permission: 'content.delete' },
]