Writing

What is PostgreSQL Row-Level Security (RLS)?

August 2026

What is PostgreSQL Row-Level Security (RLS)?

Imagine you have a database with 1,000 users, but each user should only be able to see their own data. User 1 can see User 1's posts, User 2 can see User 2's posts, and so on.

You could handle this in your backend code, but there is always a chance that a developer forgets to add the correct filter.

This is where PostgreSQL Row-Level Security (RLS) comes in. RLS is a PostgreSQL feature that lets you control which rows a user can see, insert, update, or delete. Think of it as a security filter inside the database.

Row-Level Filtering

Why is RLS Important?

Normally, your application might do something like: SELECT * FROM posts WHERE user_id = 10. The problem is that every query needs to remember this rule.

If a developer accidentally writes SELECT * FROM posts, they might expose everyone's posts.

With RLS, PostgreSQL can enforce the rule automatically. So even if the application asks for SELECT * FROM posts, PostgreSQL can return only the rows the user is allowed to access.

Without RLS
SELECT * FROM posts
returns all rows (data leak)
With RLS
SELECT * FROM posts
returns only allowed rows

A Small Example

Let's create a simple table: CREATE TABLE posts (id SERIAL PRIMARY KEY, user_id INT, title TEXT). Then add some data for different users.

Without RLS, a user could potentially query SELECT * FROM posts and see everything. But first, we enable Row-Level Security: ALTER TABLE posts ENABLE ROW LEVEL SECURITY.

That's it. RLS is now enabled for this table. But we still need to tell PostgreSQL what users are allowed to do. That's where policies come in.

What is an RLS Policy?

A policy is basically a rule. For example: "Users can only see posts that belong to them."

We can write: CREATE POLICY "Users can see their own posts" ON posts FOR SELECT USING (user_id = current_user_id()).

The important part is USING (user_id = current_user_id()). It means: only allow rows where the user_id matches the current user. So if the current user is 1, they'll only see their own posts.

User 1 makes query
PostgreSQL checks policy
USING (user_id = 1)
Filtered results

SELECT: Controlling What Users Can See

SELECT controls reading data. A policy like USING (user_id = current_user_id()) ensures each user only sees their own rows.

This is useful for things like user profiles, private messages, personal tasks, orders, and documents.

INSERT: Controlling What Users Can Create

Now imagine a user tries to create a post. We don't want User 1 to create a post pretending that it belongs to User 2.

We can use WITH CHECK (user_id = current_user_id()) to validate that the new row's user_id matches the current user.

If the current user is 1 but tries to insert a row with user_id = 2, the database rejects it — because 1 ≠ 2.

USING vs WITH CHECK

This is one of the most important concepts in RLS.

USING checks existing rows. It asks: "Can this user access this row?" It's commonly used for SELECT, UPDATE, and DELETE.

WITH CHECK checks new or modified rows. It asks: "Is this new row allowed?" It's commonly used for INSERT and UPDATE.

A simple way to remember: USING → Can I access this row? WITH CHECK → Can I create or change this row?

USING
Can I access this row?
✓
✗
Filters existing rows (SELECT, UPDATE, DELETE)
WITH CHECK
Can I create this row?
✓
Validates new data (INSERT, UPDATE)

UPDATE and DELETE Examples

For UPDATE, we use both USING and WITH CHECK. USING ensures users can only update their own posts, and WITH CHECK ensures they can't change the user_id to another user's ID.

For DELETE, we only use USING (user_id = current_user_id()). User 1 can delete User 1's post, but cannot delete User 2's post.

A Real-World Example: SaaS

RLS becomes especially useful when building SaaS applications. Imagine you have a project management app used by multiple companies.

Company 100 should not see Company 200's projects. A policy like USING (organization_id = current_organization_id()) ensures both companies can use the same database and same table, while RLS keeps their data separated.

Shared Database
Company 100
Project: Website
Project: Mobile App
Company 200
Project: Marketing

RLS and Application Security

Without RLS, your backend has to remember to protect every query. With RLS, the database itself enforces the rules.

Even if someone accidentally writes SELECT * FROM projects, the RLS policy can still prevent unauthorized rows from being returned. This gives you an additional layer of protection.

Frontend / Client
Backend / API
Database Engine
RLS Policy
Allowed Rows

RLS Doesn't Replace Authentication

RLS is mainly about authorization. Authentication asks: "Who are you?" Authorization asks: "What are you allowed to access?"

Your application still needs a way to authenticate users and provide PostgreSQL with the correct user or organization context.

RLS Can Get Complicated

RLS is powerful, but large applications can have many policies. Access to a task might depend on the organization, the team, the project ownership, or admin status.

At this point, your RLS policies can become complex. That's why it's important to keep your authorization model simple and well documented.

Quick Cheat Sheet

ENABLE ROW LEVEL SECURITY turns RLS on. CREATE POLICY creates a security rule. SELECT controls reading. INSERT controls creating. UPDATE controls modifying. DELETE controls deleting.

USING controls which existing rows are accessible. WITH CHECK controls which new or changed rows are allowed. ALL applies to all supported operations.

SELECT
USING only
INSERT
WITH CHECK only
UPDATE
USING + WITH CHECK
DELETE
USING only

Final Thoughts

PostgreSQL Row-Level Security is basically a security layer inside your database. Instead of relying completely on application code, you can tell PostgreSQL: "This user can only access these rows."

The most important things to remember are: USING → Can this user access this row? WITH CHECK → Can this user create or modify this row?

If you're building a multi-user or multi-tenant application with PostgreSQL, RLS is definitely worth learning. It's one of those PostgreSQL features that looks small at first, but becomes extremely useful when you're building real production systems.

1
Enable RLS
2
Create Policy
3
Define Access
4
Enforced
Naman Sharma

Design Engineer

GitHubGitHubGitHubXXXLinkedInEmail