Skip to Content
TutorialFeature ModulesRole-Based Content Gating

Role-Based Content Gating

Demo Project

https://editor.momen.app/tool/jnPx6858ZxD/WEB?code=1xPyKE00apemt&ref=0562398

Introduction

  • Goal: Show different content to different users based on their role or membership tier, using the two mechanisms Momen provides for it.
  • Use Cases: Tiered membership sites where premium posts stay hidden from free members, internal document libraries where access depends on seniority, and customer portals where each account only sees its own records.
  • Core Logic: two mechanisms, one per table, so you can compare them side by side.
    • Client-side filtering (the notice table): the list component builds its query from the logged-in user’s attributes. Best for tiered display of non-sensitive content, because it needs no permission setup and reacts instantly to the user’s own data.
    • Server-side permissions (the file table): Momen’s Role-Based Access Control (RBAC) plus a row-level security (RLS) rule filters rows during query execution, before any data leaves the database. Required for anything sensitive.
⚠️

Pick the mechanism by how sensitive the data is, not by which is easier to build. A client-side filter lives in the GraphQL request, so a user who edits that request and drops the filter receives the full table. For financial records, personal documents, or anything else that must never leak, use server-side permissions.

Steps

This tutorial uses pre-styled layout blocks from the “Common UI Presets” template page to speed up the visual setup. Those presets only carry basic styling and typography — no conditional logic, data bindings, or Actionflows. When building your own app, copy the elements from that template page to skip manual styling and focus on the logic.

Data Model

You need three tables: account (system-default), file (secured server-side), and notice (filtered client-side).

Every Momen table already includes id (Bigint, auto-generated) plus the system-managed created_at and updated_at timestamps, so the tables below list only the fields you need to create.

1. Table: account

The system-default table storing user profiles. Add one custom field to it:

Field NameTypeNote
role_levelBigintCustom field — add this yourself. A higher value grants broader access (1 = Standard Member, 2 = Premium Member). Set a default of 1 for new accounts: a user whose role_level is null falls through to the Default query branch configured in Method 1 and sees no notices at all.

2. Table: file

Stores file assets, isolated by a server-side RLS rule.

Field NameTypeNote
file_nameTextThe display name of the file

3. Table: notice

Stores announcements, restricted by client-side list filtering.

Field NameTypeNote
titleTextThe announcement title
min_view_levelBigintThe lowest role_level allowed to see this announcement

The two tables model the threshold differently on purpose. notice stores it per row in min_view_level, so editors can change who sees what from the CMS. file hardcodes it in the RLS rule to keep the permission example minimal — an RLS condition can reference a table field too.

Sample Records

Populate the tables so there is something to test against.

1. account Records

Create the test users with Login simulation, in the preview bar at the bottom of the editor — it is also the tool you will use to switch users during verification.

  • User 1: id = 100000000000001, role_level = 1 (Standard Member)
  • User 2: id = 100000000000002, role_level = 2 (Premium Member)

2. file Records

  • Record 1: id = 1, file_name = file1

3. notice Records

  • Record 1: id = 1, title = notice1, min_view_level = 1
  • Record 2: id = 2, title = notice2, min_view_level = 2

Page Setup

Create a page and copy the basic layout components — elevated cards and list containers — from your UI presets.

Method 1 — Client-Side Filtering (notice table)

The List notice component builds its own query from the logged-in user’s role_level.

Query criteria works as a set of branches: each Condition decides whether that branch applies, and the Filter underneath it is what actually gets sent with the query. The Default branch runs when no condition matches.

  1. Select the List notice component in the Component Tree.

  2. In the right sidebar, open the Data tab and set Data source to notice.

  3. Click + next to Query criteria to add a branch:

    • Condition: Logged in user/role_levelIs not null — this branch applies to signed-in users who have a tier.
    • Filter: min_view_levelLess than or equal toLogged in user/role_level.

  4. Configure the Default branch, which catches anonymous visitors and any user whose role_level is null:

    • Filter: idEqual to0.

    Momen has no “skip the request” option, so the way to return nothing is a condition that can never match. id values start at 1, so 0 guarantees an empty result set — pick any value you are certain no record uses.

Method 2 — Server-Side Permissions (file table)

This filters rows during query execution, so unauthorized data never reaches the client.

Step 1 — Close the file and notice tables to anonymous users

  1. Click Settings in the top navigation bar and select Permissions.
  2. On the Roles tab, select Anonymous user (visitors who are not signed in).
  3. Select the file table in the data model list and toggle Read off. Make sure Create, Update, and Delete are off as well, so the table is fully inaccessible to guests.
  4. Repeat step 3 for the notice table — toggle its Read permission off too.

This gives you a defense-in-depth setup: the server rejects the query for guests entirely. The Default branch (id = 0) you set in Method 1 stays in place as a client-side safety net, ready to take effect if you ever open the notice table’s Read permission to guests later.

Step 2 — Add a row-level security rule for signed-in users

  1. Select the Logged-in user role in the left panel.
  2. In the file table row, keep Read enabled.
  3. Find the Row-level security (RLS) row at the bottom and click Set in the file column.
  4. In the Condition setting modal, click + Condition and set: Logged in user/role_levelGreater than or equal to2. Only users at level 2 or above can now read rows in the file table.

Step 3 — Configure the List file component

  1. Select List file in the Component Tree.
  2. In the right sidebar, open the Data tab.
  3. Set Data source to file.
  4. Set Request type to Query.
  5. Leave Query criteria as Unset filter — the component requests all rows, with no frontend filter of its own.

With the RLS rule in place, Momen’s data layer applies it while executing the query and drops unauthorized rows from the result set — even though this component has no filter of its own. That is the whole point: the rule holds no matter what the frontend asks for.


Verification

Use Login simulation in the preview bar to switch between the three states.

Step 1: Anonymous Visitor (signed out)

  1. Click Preview to open the app.
  2. In the bottom bar, click Login simulation and select Restore user to logged out state.
  3. Expected Result: both lists render no rows. The file list is empty because anonymous Read is off (Method 2, Step 1); the notice list is empty because its anonymous Read is off as well — the server-side permission blocks the query before the client-side Default branch runs. The Default branch (id = 0) remains useful as a fallback if you later decide to grant anonymous Read access to the notice table.

Step 2: Standard Member (role_level = 1)

  1. In the Login simulation panel, select the user with id 100000000000001.
  2. Expected Result:
    • The file list stays empty — the user is signed in, but a role_level of 1 fails the RLS rule requiring role_level >= 2.
    • The notice list shows only notice1, whose min_view_level of 1 satisfies the client-side filter.

Step 3: Premium Member (role_level = 2)

  1. In the Login simulation panel, switch to the user with id 100000000000002.
  2. Expected Result:
    • The file list renders file1 — a role_level of 2 satisfies the RLS condition.
    • The notice list shows both notice1 and notice2, since both have min_view_level ≤ 2.

Step 4 (optional): Confirm the rule is a threshold, not an exact match

Set a test user’s role_level to 3 and reload. Both lists should still be fully populated, confirming that Greater than or equal to and Less than or equal to behave as thresholds — so adding higher tiers later needs no permission changes.

Last updated on