Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

Role-Based Access Control in OutSystems: Complete Implementation

OutSystems RBAC uses Roles defined in the Logic tab. Assign Roles to screens via the Roles property, guard server-side logic with Check<RoleName>Role(), and control widget visibility with If widgets bound to the same check. Built-in roles Anonymous and Registered cover public and authenticated access; add custom roles for fine-grained control.

What you'll learn

  • Create and configure custom Roles in Service Studio's Logic tab
  • Restrict screens to specific roles using the Roles property
  • Use Check<RoleName>Role() to guard Server Action logic
  • Show or hide UI widgets dynamically based on the current user's role
  • Grant and revoke roles programmatically with Grant<RoleName>Role() and Revoke<RoleName>Role()
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read30-45 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
TL;DR

OutSystems RBAC uses Roles defined in the Logic tab. Assign Roles to screens via the Roles property, guard server-side logic with Check<RoleName>Role(), and control widget visibility with If widgets bound to the same check. Built-in roles Anonymous and Registered cover public and authenticated access; add custom roles for fine-grained control.

How OutSystems Role-Based Access Control Works

OutSystems ships with two built-in roles: Anonymous (unauthenticated public access) and Registered (any authenticated user). You extend this with custom Roles to build fine-grained authorization. A Role in OutSystems is a server-side construct — it is checked on every request, so it cannot be spoofed client-side. This tutorial walks through creating roles, enforcing them at the screen level, checking them inside action flows, and reflecting them in the UI so buttons and menus only appear to users who have the right permissions.

Prerequisites

  • An OutSystems 11 (O11) application with at least one module open in Service Studio
  • A working user authentication setup (Users module or custom login)
  • Familiarity with Service Studio's Logic tab and screen navigation

Step-by-step guide

1

Create a Custom Role in the Logic Tab

Open Service Studio. In the right-side panel, click the **Logic tab** (gear icon). Expand the **Roles** folder. Right-click Roles → select **Add Role**. Name the role (e.g., `Manager`). Repeat for each role you need (e.g., `Admin`, `ReadOnly`). After creating roles, click the **1-Click Publish** button (top center) to compile — TrueChange will show no errors for a valid role name.

Expected result: The new role (e.g., `Manager`) appears under Logic tab → Roles. Service Studio auto-creates `CheckManagerRole()`, `GrantManagerRole()`, and `RevokeManagerRole()` system actions accessible in the Logic tab.

2

Assign Roles to a Screen

Navigate to **Interface tab → UI Flows → MainFlow** and double-click the screen you want to protect (e.g., `ManagerDashboard`). Click anywhere on the canvas to deselect all widgets. In the **Properties panel** (right side), locate the **Roles** property. Click the dropdown → uncheck `Anonymous` and `Registered` → check `Manager` (and any other roles that should have access). Save with Ctrl+S. When an unauthorized user navigates to this screen, OutSystems automatically redirects them to the `InvalidPermissions` screen (or the login screen if not logged in).

Expected result: The screen's Roles property shows only `Manager`. Unauthenticated or unauthorized users navigating to this screen are redirected automatically — no extra Server Action logic needed.

3

Guard Server Action Logic with Check<Role>Role()

Even when a screen is protected, always validate roles inside sensitive Server Actions — a user could call an API endpoint directly. Go to **Logic tab → Server Actions** → open the Server Action that performs the sensitive operation (e.g., `DeleteRecord`). At the very start of the action flow, drag an **If** element from the Toolbox. Set the condition to `CheckManagerRole()`. Connect: - **[True]** branch → your existing logic - **[False]** branch → drag a **Raise Exception** element → choose `Security Exception` → set Message to `"Insufficient permissions"`. Action flow: Start → If CheckManagerRole() → [True] existing logic → End / [False] Raise Security Exception

typescript
1/* Expression for the If condition */
2CheckManagerRole()
3
4/* In [False] branch: Raise Exception */
5Exception Type: Security Exception
6Message: "Insufficient permissions to perform this action"

Expected result: Calling `DeleteRecord` as a non-Manager user raises a Security Exception, which can be caught by an Exception Handler to show a user-friendly message instead of a raw error.

4

Control Widget Visibility by Role

To show a button, menu item, or section only to users with a specific role, use the **Visible** property on a widget combined with `CheckManagerRole()`. In the Screen Editor, select the widget (e.g., a `Delete` button). In the Properties panel, find the **Visible** property. Click the expression editor (fx icon) → enter `CheckManagerRole()`. Save and publish. For multiple widgets, wrap them in a **Container** widget, set `Visible = CheckManagerRole()` on the container, and all children inherit the visibility.

typescript
1/* Visible property expression for a Button widget */
2CheckManagerRole()
3
4/* For AdminOnly container — visible to Admin OR Manager */
5CheckAdminRole() Or CheckManagerRole()

Expected result: The Delete button only renders in the HTML for Manager users. Non-Manager users see no trace of the button in the DOM.

5

Grant and Revoke Roles Programmatically

Use the auto-generated system actions to assign roles at runtime — for example, after admin approval or during user registration. Go to **Logic tab → Server Actions** → open `CreateUser` or `ApproveUser`. In the action flow, after saving the user record, drag an **Assign** node. Use `GrantManagerRole(UserId)` to grant the role, passing the target user's Id. To remove a role: use `RevokeManagerRole(UserId)`. Action flow example: Start → CreateUser → Assign UserId = CreateUser.CreatedId → GrantManagerRole(UserId) → End

typescript
1/* Grant role to a user */
2GrantManagerRole(UserId: NewUser.Id)
3
4/* Revoke role from a user */
5RevokeManagerRole(UserId: TargetUser.Id)
6
7/* Check if a specific user has a role (not just current user) */
8CheckManagerRole(UserId: TargetUser.Id)

Expected result: After calling GrantManagerRole(UserId), the target user has the Manager role on their next request. The change takes effect immediately on the server — no logout/login required.

6

Verify the Setup with the TrueChange Pane and Testing

Before publishing, check the TrueChange tab at the bottom of Service Studio. Any missing role references or invalid Check<Role> calls show as red errors. Warnings (orange) about roles on screens that have no user navigation to them are acceptable. To test: publish the module (1-Click Publish button, top center). Open the app in a browser. Log in as a user without the Manager role — navigate to the protected screen and verify the redirect. Log in as a Manager user — verify access, visible buttons, and action execution. For more thorough testing, go to **Service Center** (your environment URL `/ServiceCenter`) → **Users** → assign and remove roles from test users without changing code.

Expected result: TrueChange shows zero role-related errors. Protected screens redirect unauthorized users. Sensitive Server Actions raise Security Exceptions for non-authorized calls. Widgets are invisible to unauthorized users.

Complete working example

RBACPatterns.os-expression
1/* ============================================================
2 OutSystems RBAC Reference Expressions and Action Patterns
3 ============================================================ */
4
5/* 1. Screen Roles property set in Properties panel, not code
6 Allowed values (comma-separated role names):
7 Manager, Admin
8*/
9
10/* 2. Server Action guard — place at action start */
11If CheckManagerRole() Then
12 /* ... protected logic ... */
13Else
14 Raise Exception(Security Exception, "Insufficient permissions")
15End If
16
17/* 3. Widget Visible property expressions */
18
19/* Single role */
20CheckManagerRole()
21
22/* Multiple roles (OR logic — any of these roles grants access) */
23CheckAdminRole() Or CheckManagerRole()
24
25/* Negate — hide from admins, show to everyone else */
26Not CheckAdminRole()
27
28/* 4. Grant/Revoke in Server Action flow */
29GrantManagerRole(UserId: Input_UserId)
30RevokeManagerRole(UserId: Input_UserId)
31
32/* 5. Check a specific user's role (admin use case) */
33CheckManagerRole(UserId: TargetUserId)
34
35/* 6. Get current logged-in user's Id */
36GetUserId()
37
38/* 7. Conditional navigation based on role */
39If CheckAdminRole() Then
40 Navigate to AdminDashboard
41Else
42 Navigate to UserDashboard
43End If
44
45/* 8. Client-side role check via JavaScript node
46 (use sparingly server-side is authoritative) */
47/* $public.Security.checkIfCurrentUserHasRole(roleKey) */
48/* roleKey is available in the generated role constant */
49
50/* 9. Common pattern: row-level visibility in a List */
51/* On each list item's Delete button Visible property: */
52CheckManagerRole() And CurrentRecord.CreatedBy = GetUserId()
53
54/* 10. Exception handler pattern for Security Exception */
55/* In Exception Handler node: */
56/* Type: Security Exception */
57/* Log Error: True */
58/* Feedback Message: "You do not have permission to perform this action." */

Common mistakes

Why it's a problem: Protecting only the screen via the Roles property but not the underlying Server Action, allowing direct API calls to bypass the restriction.

How to avoid: Add a CheckManagerRole() If guard at the start of every sensitive Server Action, raising a Security Exception on the False branch.

Why it's a problem: Using a Client Action to check roles and control navigation — Client Actions run in the browser and can be bypassed by a user with developer tools.

How to avoid: Always perform role checks in Server Actions or use the screen-level Roles property, which is enforced server-side on every request.

Why it's a problem: Creating separate 'User' and 'Registered' roles that have identical permissions, causing maintenance duplication.

How to avoid: Use the built-in Registered role for base authenticated-user access. Only create custom roles when permissions diverge from basic authentication.

Why it's a problem: Calling GrantManagerRole() without passing a UserId in admin workflows, inadvertently granting the role to the admin performing the action.

How to avoid: Always pass the explicit UserId parameter: GrantManagerRole(UserId: TargetUser.Id) when operating on another user's account.

Best practices

  • Always check roles on the Server Action, not just the screen — screen-level protection only prevents navigation, not direct API calls.
  • Use the built-in `Registered` role for any screen all logged-in users can see — avoid creating a redundant `User` role that mimics it.
  • Never store sensitive role logic client-side; CheckManagerRole() executes server-side and cannot be bypassed by manipulating the browser.
  • Wrap related protected widgets in a Container and set Visible once on the container rather than on each individual widget — reduces maintenance.
  • Use AI Mentor Studio's Security pattern analysis to detect screens that are publicly accessible but contain sensitive data (missing Roles configuration).
  • Document role assignment rules in Site Properties or a dedicated Admin screen rather than hardcoding role grants in application logic.
  • In ODC, use the ODC Portal → Users section to manage role assignments in production without redeploying the application.
  • For cross-module role checks, expose a Service Action that performs the Check<Role> call — do not expose the role check directly from a different module.

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I'm building an OutSystems 11 application and need to implement role-based access control. I have three roles: Admin, Manager, and ReadOnly. Explain how to: (1) create these roles in the Logic tab, (2) restrict specific screens to each role using the Roles property, (3) guard Server Actions with CheckAdminRole() and CheckManagerRole(), (4) show or hide buttons using the Visible property with role expressions, and (5) grant roles programmatically after user approval. Use OutSystems expression syntax, not JavaScript.

OutSystems Prompt

In my OutSystems Service Studio module, help me add role-based access to the OrderManagement screen. The screen should only be accessible to users with the Manager or Admin role. The Delete button inside a List should only be visible to Admin users. The DeleteOrder Server Action should raise a Security Exception if called by a non-Admin. Show me the exact Roles property values, Visible property expressions, and the If/Raise Exception pattern for the Server Action guard.

Frequently asked questions

Can I assign a role to a user without Service Center access?

Yes. Call GrantManagerRole(UserId: targetId) inside any Server Action — for example, an admin screen in your application. This changes the role assignment in the OutSystems Users database immediately without requiring a Service Center login.

What happens when a user without the required role navigates to a protected screen?

OutSystems automatically redirects them. If they are not logged in, they go to the login screen. If they are logged in but lack the role, they are redirected to the InvalidPermissions screen (configurable in the module properties).

Does CheckManagerRole() work inside a Mobile app or only web apps?

CheckManagerRole() is a Server Action and works in both Mobile and Reactive Web apps. In Mobile apps it runs server-side when called from a Server Action or Data Action — it cannot be called directly from a Client Action without a server round-trip.

How do OutSystems roles differ in ODC compared to O11?

The Role system is identical in both O11 and ODC — you create roles in the Logic tab, use Check/Grant/Revoke actions, and set the Roles property on screens the same way. The main ODC difference is that role assignments for production users are managed in ODC Portal rather than Service Center.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.