Multi-Tenant Setup Walkthrough

This is an end-to-end worked example that combines every layer of Yurbi security into a working multi-tenant deployment. We'll set up a fictional Yurbi instance serving two customers — Acme Corp and Widget Inc — using one instance, one app, and the canned-content pattern. By the end, each customer will sign in, see their own data, and have no awareness of the other customer's existence.

Customers commonly trip up on this setup. Most of the gotchas are wrong-by-one details — folders assigned to the wrong group, RLS policies assigned to the wrong scope, users in too many groups. Follow this article as a checklist and the result is reliable.

Quick Checklist

If you've done this before and just need the reminder, here's the entire setup compressed:

  • [ ] Tenant Mode on (Settings → Server Settings)

  • [ ] Library folders default to the All Users group only (the canned-content distribution channel), plus optionally an internal management folder for non-admin internal users

  • [ ] One group per tenant — never combined with other tenants

  • [ ] Each tenant's users assigned only to their tenant group with appropriate role (View / Modify / Delete inside the group)

  • [ ] One root-level or sub-folder per tenant in the Library, assigned to that tenant's group only

  • [ ] Never mingle tenants in a group — All Users is the only legitimate shared group

  • [ ] One data tag per RLS value (e.g., tenantid), with a per-group override for each tenant — OR rely on the Company profile tag set per-user

  • [ ] One Data Security Policy per app that references the data tag (/#tenantid#/) or profile tag (/#company#/, /#tag1#/)

  • [ ] Assign the RLS policy to All Users (ideally — covers everyone automatically) or to every tenant group (when admins need unfiltered access)

  • [ ] Per-tenant branding if you want different visual experiences per customer

Ongoing user creation (once the foundation above is set up) is just three steps:

  1. Create the user

  2. Assign to their tenant group (set role — controls what they can do within their tenant folder)

  3. Assign to apps they use (set role — Agent / Builder / Architect)

If you're not sure whether this walkthrough applies to you, read Deployment Models and Best Practices first to confirm you're in the multi-tenant model.

When to Use This Walkthrough

Use this walkthrough when:

  • You're standing up a new multi-tenant Yurbi instance from scratch

  • You've inherited an instance and want to verify it follows multi-tenant best practices

  • You're migrating from per-customer report copies to a canned-content model

  • You want a reference for onboarding a new customer to an existing multi-tenant instance

Prerequisites

You'll need:

  • Admin access to Yurbi

  • An app already registered in Architect (or willingness to register one as part of this walkthrough)

  • A clear data-isolation strategy: either (a) a single shared database with a customer identifier column, or (b) separate databases per customer

  • Backup credentials in case you misconfigure something — make a backup admin first

You should be familiar with the foundation articles:

How These Pieces Fit Together

Before the steps, a diagram of the relationships at play in a multi-tenant Yurbi setup:

USER
├── Identity (login, password)
├── Profile tags (company, tag1, tag2...) ────────┐
├── Group memberships │ referenced by
│ │ │
│ │ member of ▼
│ ▼
│ TENANT GROUP RLS POLICY
│ ├── name (e.g., "Acme Corp") ├── constraint (column = ?)
│ ├── members ├── value ('/#company#/'
│ └── target of folder perms │ or '/#tenantid#/')
│ │ └── assigned groups
│ │ has View / Modify / Delete on
│ ▼
│ TENANT FOLDER
│ ├── permissions: tenant group only
│ └── stores reports
└── Per-app role + (optional) alternate data source
│ runs reports against
APP (semantic layer)
│ created via
INTEGRATION (connection to data source)
├── primary data source
└── optional alternates (per-tenant DBs)
 
 
QUERY FLOW
──────────
user clicks "run report"
Yurbi reads user's tag values (company="ACME-001")
RLS substitutes: WHERE customer_id = 'ACME-001'
Yurbi picks the integration (primary or user's assigned alternate)
Query runs against that database
User sees only their tenant's rows

The key insight: a user passes through all these layers for every query. Per-app role gates whether they can run anything; folder permissions gate whether they can open the report; RLS gates which rows come back; alternate data sources gate which database the query hits.

The Scenario

We're standing up Yurbi for a SaaS company serving two customers:

  • Acme Corp — 5 users, customer code ACME-001

  • Widget Inc — 3 users, customer code WIDGET-002

Both customers use the same product, run the same reports, and have their data in a shared database identified by a customer_id column. The internal team needs access to admin reports but not customer data.

We'll do this in eight steps.

Step 1: Confirm the Data Isolation Strategy

Before any Yurbi configuration, confirm how customer data is separated in the source database:

  • Shared database with customer_id column → use Row-Level Security

  • Separate database per customer → use Alternate User Data Sources

  • Hybrid (some apps shared, some per-customer) → use both, app by app

For this walkthrough, our data is in a shared database with a customer_id column. We'll use Row-Level Security.

Action: Verify the customer_id column exists on every table that has customer-specific data. RLS depends on it being consistent and populated.

Step 2: Create Per-Tenant Groups

Navigate to Settings → Security → Groups

Create two groups:

  • Acme Corp — Description: "Customer organization Acme Corp"

  • Widget Inc — Description: "Customer organization Widget Inc"

[SCREENSHOT: Settings → Security → Groups page showing Administrators, Acme Corp, and Widget Inc groups]

Don't add members yet — we'll do that when creating users. Don't worry about folder permissions yet either; that comes in step 6.

Important: don't mingle tenants in a group. Each tenant gets its own group, used only for that tenant. The All Users group is Yurbi's only legitimate shared-access group — never replicate that pattern with custom groups. Mingling tenants breaks the isolation Tenant Mode is designed to provide and causes scheduling, sharing, and save-as dialogs to leak member names and folder names across tenants.

You may already have an internal group for your support team. If not, create one — "Internal Team" or similar. Internal users don't strictly need their own group (the Administrators group handles full access), but it's useful when you want internal Builders who aren't full admins.

Step 3: Create the Users

Navigate to Settings → Security → Users

For each customer's users, fill the form with:

  • General Information — name, login, email, initial password

  • Company — set to the customer code. For Acme users, set Company to ACME-001. For Widget users, WIDGET-002. This is what RLS will reference as /#company#/.

  • Group Assignments — check View on the customer's group only. Nothing on Administrators. Nothing on any other tenant's group.

  • App Assignments — typically Agent on the app(s) the customer should access. Customers who'll build their own reports get Builder; the rest get Agent only.

  • Profile Tags — typically empty for customer users unless you've defined a specific use

[SCREENSHOT: Edit User panel showing Company field filled with ACME-001 and Group Assignments showing Acme Corp checked with View]

Save & Apply for each user. Repeat for all 5 Acme users and all 3 Widget users.

Don't forget:

  • A backup admin (Admin on Administrators group) if you don't already have one

  • Your internal Builders or support users (Builder on relevant apps, optionally Admin on Administrators)

Once the foundation below (steps 4–7) is in place, adding a new tenant user is just these three steps — create, assign to tenant group, assign per-app roles. No need to revisit RLS, folders, branding, or tenant mode for each user.

Step 4: Set Up Row-Level Security

Navigate to Settings → Security → Data Security

Click Create New Policy:

  • Name — "Restrict to user's company"

  • Description — "Filters every report to rows matching the running user's Company profile tag"

  • Active — On

  • App — Your app

Policy Constraints:

Add one constraint:

  • Category: Customers (or whichever category contains the customer ID)

  • Field: customer_id

  • Operator: =

  • Value: /#company#/

[SCREENSHOT: Create New Policy form showing the constraint customer_id = /#company#/ and Assigned Groups section]

Alternative — use a data tag instead of a profile tag: if you'd rather set the filter value per-group instead of per-user, create a data tag (e.g., tenantid) under Settings → Security → Data Tags. Set a default value, then add a per-group override for each tenant group (Acme Corp = ACME-001, Widget Inc = WIDGET-002). Reference it in the constraint as /#tenantid#/. Both approaches work; pick the one that fits your user-provisioning workflow. The profile-tag approach is simpler when users are created manually; the data-tag approach is cleaner when users are created via automation that sets the tag at the group level.

Assigned Groups:

  • Recommended: assign to All Users. Every user gets filtered by their own Company (or tenantid) value automatically. 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-only reports.

  • Alternative: assign to each tenant group individually. Use this only if admins need unfiltered customer data from the same reports tenants run. The downside: every new tenant group you create needs to be added to this policy's assigned groups.

Save & Apply.

Why this works: When an Acme user runs a report, Yurbi substitutes customer_id = 'ACME-001'. They see only Acme rows. When a Widget user runs the same report, it becomes customer_id = 'WIDGET-002'. Same report, different data per user.

Step 5: Build (or Verify) the Canned Report Library

Sign in as yourself (an admin) and go to the Library.

If you already have reports built, evaluate where they live:

  • Reports meant for all customers should live in folders targeting All Users with View permission. These become the canned-content library.

  • Reports for internal use only should live in folders targeting your internal group or Administrators.

  • Reports specific to one customer should live in that customer's group's folder.

If you don't have reports yet, build one or two simple ones to test the pattern. Save them to a folder that targets All Users with View.

Verify the folder structure looks roughly like this:

Library/
├── Shared Reports/ (targets All Users — canned content)
│ ├── Monthly Revenue
│ └── Customer Health
├── Acme Corp/ (targets Acme Corp group only)
│ └── (their private work)
├── Widget Inc/ (targets Widget Inc group only)
│ └── (their private work)
└── Internal Only/ (targets Administrators or internal group)
└── (admin reports, usage stats, etc.)

[SCREENSHOT: Library folder tree showing Shared Reports, per-customer folders, and Internal Only folders]

Step 6: Configure Library Folder Permissions

For each folder, set permissions explicitly:

Shared Reports (canned content):

  • All Users — View

  • (Modify and Delete reserved for admins via the Administrators group's inherent access)

Acme Corp folder:

  • Acme Corp — View, Modify (so their Builders can save here)

  • No other groups

Widget Inc folder:

  • Widget Inc — View, Modify

  • No other groups

Internal Only folder:

  • Administrators (or your internal group) — View, Modify

  • No customer groups

[SCREENSHOT: Folder Permissions dialog showing the per-folder group/role assignments]

Step 7: Enable Tenant Mode

Navigate to Settings → Server Settings → Tenant Mode

Toggle on.

This locks in the multi-tenant guarantees:

  • Customer Builders can only save into their own group's folder

  • The Shared Reports folder (targeting All Users) becomes read-only canned content for everyone except admins

  • Cross-tenant content mixing is prevented at the save/edit layer

Existing users may need to sign out and back in for the new UI restrictions to apply in their current session.

Optional extra strictness: Server Settings also has a toggle to disable the All Users group entirely. With it on, no shared-with-everyone content can exist at all — every folder, branding policy, and RLS assignment must target a specific group. Useful for high-security deployments. Leave it off if you're using the canned-content pattern, since canned content lives in All Users folders.

Step 8: Test as Each Role

The most important step. Sign in as each persona and verify what they see.

Test as an Acme Corp Agent

  1. Sign in with an Acme user's credentials

  2. Verify the Library shows: Shared Reports (View only), Acme Corp folder

  3. Verify the Library does not show: Widget Inc folder, Internal Only folder

  4. Open a report from Shared Reports

  5. Verify the data is filtered — they see only Acme rows, no Widget rows

  6. Verify they cannot edit the canned report (no Save button, or Save to their own folder only)

  7. Verify schedule/share dialogs only show Acme members and the Acme folder — not Widget members, not the Widget folder

Test as a Widget Inc Agent

  1. Sign in as a Widget user

  2. Same checks — verify they see Shared Reports + Widget Inc folder, and not Acme Corp

  3. Open the same report. Verify they see Widget rows only

  4. Verify scheduling/sharing dialogs are scoped to Widget only

Test as an Acme Corp Builder

  1. Sign in as an Acme user with Builder on the app

  2. Try to build a new report

  3. Verify they can save to their Acme Corp folder

  4. Verify they cannot save into Shared Reports, Widget Inc, or Internal Only

  5. Verify schedule recipient lists show only Acme members

Test as an Admin

  1. Sign in as an admin

  2. Verify all folders are visible

  3. Open a report from Shared Reports

  4. Verify the data is filtered to zero rows (or set your admin's Company tag to a special value if you need admin reports to show full data)

  5. Verify admin reports against admin-only folders work as expected

If any of these checks fail, walk back through the relevant configuration step. Common causes are in the Common Issues section below.

End-State Checklist

After the eight steps, your instance should have:

  • Two customer groups (Acme Corp, Widget Inc) with their members — never combined

  • A backup admin with credentials stored durably

  • Users with Company profile tag set to their customer code

  • One RLS policy filtering by customer_id = /#company#/, assigned to All Users

  • A folder structure separating canned content (All Users target) from per-customer content (per-group target)

  • Tenant Mode on

  • Verified behavior for each persona via sign-in tests

When you onboard a new customer, the workflow becomes:

  1. Create the new customer's group

  2. Create the customer's users — one operation each: create + tenant group + per-app roles

  3. Create the customer's folder targeting their group

  4. (No RLS changes needed — All Users assignment covers them automatically)

  5. Test as one of the new users

A new customer is onboarded in 10–15 minutes once the foundation is in place. That's the canned-content payoff.

Common Configuration Scenarios

The eight-step walkthrough covers the standard shared-database pattern. Variations:

Variation A: Each Customer Has Their Own Database

Replace step 4 with:

  • Configure Alternate User Data Sources in Architect (one alternate per customer database)

  • On each user's App Assignment, use the Default dropdown to pick their customer's alternate

  • Skip RLS — the data is already physically isolated at the connection level

See Alternate User Data Sources.

Variation B: Hybrid — Some Apps Shared, Some Per-Customer

  • Use the walkthrough as-is for the shared apps (one app, RLS for separation)

  • For per-customer apps, register each customer's database as a separate integration, creating separate apps, or use Alternate Data Sources within one app

  • Library folder structure stays the same

Variation C: Per-Customer Branding

Add a step between 7 and 8: create per-tenant branding policies. See Application Branding (Logged-in Users).

Variation D: Mixed Internal + Customer

  • Internal users go in your internal group (or just Administrators)

  • The walkthrough handles them naturally — they're not in any customer group

  • They see admin folders and reports; the RLS policy filters them on customer data (typically to zero rows, which is fine)

Best Practices

Document the customer-code convention. What format does the Company profile tag use? ACME-001 or acme or 12345? Pick a convention and stick to it. Inconsistent codes break RLS silently.

Onboard one test customer end-to-end before opening the doors. Create a "TestCo" group, a test user, a test folder. Run through the eight steps. Sign in as TestCo, verify the isolation. Then delete TestCo and proceed with real customers.

Don't mingle tenants in custom groups, ever. Already mentioned in step 2 but worth repeating — this is the most common multi-tenant configuration mistake. The All Users group is the only legitimate shared group.

Keep an internal "admin reports" folder. Track which customer is on which alternate, which reports are canned vs. per-customer, audit dashboards for who's accessing what. Make this folder Administrators-only.

Don't put admins in customer groups. Easiest way to keep admins unfiltered is to leave them out of the per-tenant groups. They have Admin on Administrators and that's it.

Audit periodically. Twice a year: walk through Settings → Security → Groups, verify membership; walk through your Library folder permissions; sign in as a representative customer user and verify isolation still holds.

Plan for the customer who needs something custom. Most customers fit canned content. The occasional customer needs a bespoke report. Build it in their group's folder — Tenant Mode keeps it isolated. Don't try to fork the canned library.

Common Issues

Customer sees no data. Their Company profile tag value doesn't match any data, or it's misspelled, or it's set on the wrong field. Edit the user, verify Company is set to exactly the customer code that appears in the customer_id column.

Customer sees another customer's data. Either (1) they're accidentally in multiple customer groups (check their Group Assignments), (2) the RLS policy isn't assigned to All Users (and isn't assigned to their tenant group), (3) the policy is disabled.

Customer can see the canned report folder name but the folder is empty. They have View on the parent but the reports inside have their own permissions overriding the parent. Check the report-level permissions.

Builder can't save anywhere. Tenant Mode is on but their group has no folder with Modify permission. Create a folder for their group with their group having View + Modify, or grant Modify on an existing folder.

Admin says "RLS is filtering me too." They're in a group that's been assigned to the policy, or the policy is assigned to All Users (which includes admins by default). Decide: do you want admins filtered or not? If not, switch the policy assignment from All Users to the tenant groups only (Scenario 4 in the RLS article).

Schedule/share dialogs show members from other tenants. A user is in more than one tenant group, or tenants have been mingled in one group. Fix: remove the user from the wrong group, or split the mingled group into per-tenant groups.

New customer onboarded but they see other customers' data. Their Company profile tag is unset, or they're not in any customer group. If RLS is assigned to All Users, they'd still be filtered — unless their tag value happens to match other customers' data. Check their tag value and group membership.

Onboarding a new customer takes too long. If onboarding takes more than 15 minutes per customer, your model has likely drifted from pure canned content. Look for per-customer report copies, per-customer policies, or custom folder structures. Refactor toward shared canned content + RLS scoping.


Remember: The goal is "onboard a new customer in 15 minutes." If your setup gets there, you've built a maintainable multi-tenant Yurbi. If it doesn't, look for what's not following the canned-content pattern.

Related Articles