Row-Level Security: Profile Tags, Data Tags, and Policies

Row-Level Security (RLS) is Yurbi's third security layer. Once a user can sign in (Layer 1) and open a folder (Layer 2), RLS decides which rows of data they see inside the reports they can run. Two users running the same report see different results, filtered automatically by who they are.

This article covers the two tag types, the /#tag#/ syntax that references them, how to build policies, and the trickier patterns — in-list constraints and AND/OR composition — that come up once you have more than a handful of policies.

When This Applies

Use Row-Level Security when:

  • Multiple customers share one database and are scoped by a column (customer_id, tenant_id, region)

  • Users on the same team need to see different rows (a sales rep should see their own deals, not the whole team's)

  • Compliance requires that data access be filtered at query time, not by report design

  • You want to build one report that serves many users with different scopes

If each customer has their own database, Alternate User Data Sources may be the right tool instead — or you may use both for different apps.

Prerequisites

Configuring RLS requires Admin access to Yurbi.

You should have:

  • An app registered in Architect

  • Users created and assigned to groups

  • A clear idea of which columns in your data should be used for filtering (e.g., customer_id, region_code)

The Two Tag Types

RLS works by substituting tag values into filter constraints at query time. Yurbi has two kinds of tags:

Profile Tags (Per-User)

Profile tags are values that live on the user record. Every user has their own values. Yurbi has ten built-in profile tags:

  • firstname, lastname — auto-populated from the user's General Information

  • Login — the user's login (note the capital L when referenced as /#Login#/)

  • email — the user's email

  • company — from the Company field in General Information

  • description — from the Description field in General Information

  • tag1, tag2, tag3, tag4 — four general-purpose slots configured under Additional Options → Profile Tags

You set these values when creating users. RLS references them with /#name#/ syntax — for example, /#company#/ or /#tag1#/.

Data Tags (Per-Group with Default)

Data tags are values that live on groups, with a default value used when no group-specific value is set. You define data tags yourself — they're not built-in. Each data tag has:

  • A Label (the name you'll reference in policies, e.g., region_filter)

  • A Data Type (text, number, date)

  • A Default Value used when a user isn't in a group with an override

  • Group Assignments that override the default for specific groups

Use data tags when the filter value depends on a user's group membership rather than something on the user's individual record.

[SCREENSHOT: Settings → Security → Data Tags page showing existing tags list and Create New Data Tag button]

When to Use Which

Profile tags are right when each user has their own filter value — sales reps with their own territory codes, customers with their own company identifier, internal users mapped to external system IDs.

Data tags are right when filter values cluster by group — every member of "Acme Corp" sees the same tenant_id, every user in "EU Region" sees the same region_code. Setting the value once on the group is easier than setting it on every user.

You can combine both in the same policy. Different RLS constraints in one policy can reference different tag types.

Tag Syntax

Tags are referenced in policy constraints using this exact syntax:

/#tagname#/

Forward slash, hash, tag name, hash, forward slash. No spaces. Case matters — /#Login#/ is capitalized in the built-in list, the others are lowercase.

Examples in constraints:

  • customer_id = '/#company#/' — filter rows where customer_id equals the user's company

  • region IN ('/#tag1#/') — filter rows where region is in the user's tag1 value

  • created_by = '/#Login#/' — filter rows where the row's creator matches the user's login

At query time, Yurbi substitutes the value before sending SQL to the database. If a user's company is "ACME-001", the first example becomes customer_id = 'ACME-001'.

Creating a Data Tag

Navigate to Settings → Security → Data Tags

Click Create New Data Tag.

[SCREENSHOT: Create New Data Tag form showing Label, Group, Data Type, Default Value, and Group/User Assignments fields]

Label Required. The name used to reference this tag in policies — for example, region_filter or tenant_code. Pick something descriptive; it appears in the dropdown when you build policies.

Group Optional, cosmetic. A free-form text field for mental organization — useful when you have many data tags and want to group them in your head ("Regional", "Tenant", "Department"). It doesn't affect behavior.

Data Type Text, number, or date. Match the column you'll be filtering against.

Default Value The value used when a user isn't in any group that overrides it. If your default behavior should be "see nothing", set this to a value that won't match any rows (e.g., empty string or a sentinel like __NONE__).

Group / User Assignments Each group can override the default with its own value. Users can also be assigned individual overrides that beat group values. Order of precedence: user override > group override > default.

Click Save & Apply.

Creating a Row-Level Security Policy

Policies are how you tell Yurbi what to filter and where.

Navigate to Settings → Security → Data Security

Click Create New Policy.

[SCREENSHOT: Create New Policy form showing Policy Constraints section with Select Category and Fields, and Assigned Groups]

Policy Settings

Name Descriptive — "Restrict CRM to user's company", "EU region only", "Sales rep sees own deals".

Description Optional but helpful. Future-you will thank you.

Active Toggle on to enforce. Toggle off to temporarily disable without deleting.

App Which app this policy applies to. A policy targets exactly one app.

Policy Constraints

This is where you define what gets filtered. Each constraint has:

  • Category — a Yurbi category from the app's semantic layer

  • Field — a column within that category

  • Operator=, IN, LIKE, !=, etc.

  • Value — the value to compare against, typically a tag reference like /#company#/

You can add multiple constraints to one policy. By default, multiple constraints on one policy are combined with AND — all of them must be true for a row to show.

Example single-constraint policy:

  • Category: Customers

  • Field: customer_id

  • Operator: =

  • Value: /#company#/

This filters every report on this app's Customers category to rows where customer_id matches the running user's company profile-tag value.

Assigned Groups

Scroll to Assigned Groups. Pick which groups this policy applies to. The policy only filters reports run by users in these groups.

Recommended default: assign to All Users. Every user gets filtered by their own tag values automatically — tenant users see only their tenant's rows, admins see only rows that match their tag values (typically none, since admins usually don't have a Company value matching customer data). This is the simplest and safest pattern for most multi-tenant deployments. Admins use admin-only reports against admin-only folders, where customer-data RLS doesn't apply.

Alternative: assign to specific tenant groups. Use this when admins genuinely need to see unfiltered customer data from the same reports tenants use. Add every tenant group to the policy's assigned groups. Admins, being only in the Administrators group, aren't in any tenant group and therefore aren't filtered.

Important: A user not in any assigned group is not filtered by this policy. If your admins need unfiltered access and you assigned the policy to All Users, they will still be filtered — Administrators don't escape All Users assignment. Use the alternative pattern in that case.

Click Save & Apply.

In-List Syntax for Multi-Value Constraints

When you want a single tag to expand to multiple values (a user sees rows from multiple regions, for example), you use Yurbi's in-list syntax in the tag's value field.

The exact format:

value1','value2','value3','value4','value5

Critical rules:

  • No leading single quote at the start

  • No trailing single quote at the end

  • No spaces anywhere in the string

  • Separator is exactly ',' — close-quote, comma, open-quote (three characters)

  • Yurbi wraps the whole thing in single quotes automatically at query time

How this works in a constraint: The policy constraint uses the IN operator. The Value field contains the tag reference like /#tag1#/. The user's tag1 value is set to the multi-value string above. At query time, Yurbi substitutes and wraps in parentheses, producing something like:

region IN ('US-EAST','US-WEST','EU-WEST')

Example user setup:

  • User's tag1 value: US-EAST','US-WEST','EU-WEST

  • Policy constraint: region IN '/#tag1#/'

  • Result: user sees rows where region is any of those three

This works for both profile tags and data tags. For data tags, set the multi-value string as the default value or as a group override.

AND vs OR Composition

By default, multiple constraints within one policy combine with AND — every constraint must be true for a row to show.

To express OR, use multiple separate policies — each with one constraint — and assign all of them to the same group. RLS combines policies on the same scope with OR: a row is shown if any applicable policy says yes.

Example — AND (one policy):

A user should see rows where customer_id = '/#company#/' AND region = '/#tag1#/'.

  • One policy with two constraints

Example — OR (two policies):

A user should see rows where customer_id = '/#company#/' OR they created the row (created_by = '/#Login#/').

  • Policy A: one constraint (customer_id = '/#company#/')

  • Policy B: one constraint (created_by = '/#Login#/')

  • Both assigned to the same group

This is one of the trickier RLS concepts. The mental model: constraints within a policy AND together; policies on the same scope OR together.

Common Configuration Scenarios

Scenario 1: Each Customer Sees Only Their Own Data

Goal: Multi-tenant database; users see rows where the customer matches them

  1. Ensure every tenant user has their Company profile-tag value set during creation

  2. Create a policy on the app:

    • Constraint: Customers category, customer_id field, =, value /#company#/

    • Assigned Groups: All Users

  3. Admins typically don't have a matching Company value, so they're effectively filtered to zero customer rows on this report — which is fine because admins use admin-specific reports. If admins genuinely need unfiltered customer data, see Scenario 4 below.

Scenario 2: Region-Based Filtering with Multi-Region Users

Goal: Each user sees data from one or more assigned regions

  1. Create a data tag called region_filter, default value __NONE__

  2. Per group (or per user), set the override to the region(s) using in-list syntax: US-EAST for one region, US-EAST','US-WEST for two

  3. Create a policy:

    • Constraint: Orders category, region field, IN, value /#region_filter#/

    • Assigned Groups: All Users

Scenario 3: Sales Reps See Their Own Deals Plus Their Team's

Goal: A sales rep sees deals they own OR deals owned by their team

  1. Ensure each rep's Login profile-tag is their identifier in your CRM data

  2. Create a data tag team_id, with per-group overrides for each team

  3. Create two policies on the CRM app:

    • Policy A: Deals category, owner_id field, =, value /#Login#/

    • Policy B: Deals category, team_id field, =, value /#team_id#/

    • Both assigned to the Sales group

  4. RLS ORs these together: reps see their own deals plus their team's

Scenario 4: Admins See Everything, Customers See Only Their Own (Alternate Pattern)

Goal: Admins need unfiltered access to customer data from the same reports tenants use

Use this pattern when the default (Scenario 1, assigning to All Users) doesn't work because admins regularly need to investigate customer data without switching users or impersonating.

  1. Create the customer-filter policy as in Scenario 1

  2. Assign only the tenant groups (not All Users, not the Administrators group) to the policy

  3. Admins are in the Administrators group only, which the policy doesn't filter — they see all rows

  4. Every tenant user is in their tenant group, which is assigned, so they're filtered correctly

The tradeoff: every new tenant group you create must be added to this policy's assigned groups. With Scenario 1's All Users assignment, new groups are covered automatically.

Best Practices

Set profile tags during user creation, not later. Backfilling Company on 50 users because RLS needs it is a tedious afternoon. Setting it once at creation costs four seconds per user.

Default to "see nothing" on data tags. Set the default value to something that won't match any rows. Users who aren't assigned to any group with an override get filtered to nothing — safer than seeing everything.

Name your policies for what they do. "Restrict Orders to user's region" beats "Policy 3". When you have ten policies, descriptive names save you.

Test as a real user in each group. Sign in as a member of each scope and verify they see what you intended. Especially important when you have OR-composed policies — the interactions can surprise you.

Document the AND/OR composition. When you have multiple policies on the same scope, write down what the intended OR-combination is. It's not visible in the UI — only in the policy list.

Use the in-list syntax carefully. A single typo (extra space, leading quote, wrong separator) breaks the constraint silently. The user just sees no data, with no obvious error.

Don't put admins in policy assignments unless you mean to filter them. Easiest way to make admins see everything is to leave them out of the policy's Assigned Groups.

When Changes Take Effect

Profile-tag value changes: take effect the next time the user runs a report that references them.

Data-tag default or group override changes: take effect on the next query for users affected.

New or edited policies: take effect on the next query against the app for users in the assigned groups.

Disabling a policy: users see unfiltered data on the next query (subject to other policies).

Common Issues

User sees no data. The most common RLS issue. Causes: (1) their tag value is unset or doesn't match anything in the data, (2) the in-list syntax has a typo, (3) the policy is filtering on a field whose value doesn't match the tag's format (e.g., customer_id is a number but /#company#/ is text). Check the user's tag values and the data they should be seeing.

User sees too much data. Either (1) they're not in any group assigned to the policy, (2) an OR-composed policy is granting access you didn't intend, or (3) the operator is wider than you meant (LIKE '%X%' matches more than you'd think).

Admin says "RLS is filtering me." They're in a group that's assigned to the policy. Remove them from that group, or remove that group from the policy's assigned groups.

Tag value with quotes or special characters breaks the query. Tags substitute as text. A value containing a single quote will break SQL. Stick to alphanumeric tag values and use the in-list syntax for multi-value cases.

The IN operator works on one value but breaks on multi-value tag. Check the in-list syntax exactly: no leading or trailing quote, no spaces, separator is ','. Use a known-good example user and compare.

Two policies seem to conflict. They combine with OR if assigned to the same group. If policy A says "see Region 1" and policy B says "see Region 2", the user sees both. To get AND, combine the constraints into one policy.


Remember: Constraints within a policy AND together; policies on the same scope OR together. When in doubt, test as the user.

Related Articles