DEV Community

Cover image for Postgres Views: The Hidden Security Gotcha in Supabase
Lucca Sanwald
Lucca Sanwald

Posted on

2 1 1 1 1

Postgres Views: The Hidden Security Gotcha in Supabase

When building with Supabase, Postgres Views can be a powerful tool for simplifying complex queries. But they come with a critical security consideration that isn't immediately obvious: Views bypass Row Level Security (RLS) by default, potentially exposing sensitive data even when your tables are properly secured.

As a reminder: RLS is what allows you to safely query your Supabase database directly from the frontend, without routing through your backend server. But if RLS isn't working on a table, that table is like your phone screen on public transport—anyone who wants to take a glance, can.

The Security Challenge

Even if you've carefully configured RLS policies on your tables, views can create an unintended backdoor because:

  • By default, views don’t use RLS
  • Supabase's RLS policies page doesn't show or warn about exposed views (last checked 09.03.2025)
  • Views don't support RLS in the same way tables do

Testing Your View's Security

Before deploying any view to production, it's crucial to verify that it properly respects your RLS policies. Here's a quick way to test if your view is secure:

// First, sign in as a specific user
// Then try to fetch ALL rows from your view
const { data } = await supabase.from('my_view').select('*')
// If your view respects RLS, you should only see rows this user has permission to access.
// If you see ALL rows, your view is bypassing RLS! 🚨
console.log("view response" ,data)
Enter fullscreen mode Exit fullscreen mode

Securing Your Views

To protect your data, you have several options:

  1. For Postgres 15+:
CREATE VIEW public.my_view
WITH (security_invoker = true) AS
SELECT * FROM my_table;
Enter fullscreen mode Exit fullscreen mode

This applies the RLS of my_table to the view you’re creating.

  1. For older Postgres versions:
  • Create an internal schema: CREATE SCHEMA internal;
  • Re-create the sensitive view in the internal schema
  • Delete the public version of the view

When to Use Views

Views are particularly valuable when you need to:

  • Simplify complex queries that you use frequently
  • Add computed columns that can't be generated columns
  • Create virtual tables that recalculate with each request

Example: Active Subscription Status

I recently built a subscription system and wanted to avoid having to write active_until > NOW() in every query where I'd need to check for active subscriptions. Planning ahead, I first considered adding an is_active generated column to the table. But I hit a wall: Postgres doesn't allow volatile functions like now() in generated columns. That's when I turned to views as a solution:

CREATE VIEW public.active_subscriptions
WITH (security_invoker = true) AS
SELECT 
    *,
    (active_until > now()) AS is_active
FROM 
    public.subscriptions;
Enter fullscreen mode Exit fullscreen mode

This view has been working perfectly, giving me clean queries while maintaining security through security_invoker.

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay