Featurevisor

Use cases

Role-Based Access Control (RBAC)

RBAC is a way to manage access to resources in an application based on the roles of the identified users. In this guide we will go through the process of setting up RBAC using Featurevisor complimenting the backend's role management.

Why use RBAC?

Most teams reach for Role-Based Access Control (RBAC) as a backend concern, and rightly so. The backend owns authentication, session management, and information about what roles a user holds. That's non-negotiable.

But there's a gap that often goes unaddressed: how does the frontend know which features to show, hide, or constrain based on the current user's roles?

The naive answer is to scatter if (user.role === 'admin') conditionals throughout our UI code. The slightly better answer is to centralize those checks into a permissions utility. But neither approach gives us the flexibility, auditability, and separation of concerns that a mature product team needs.

This guide presents a third path: using Featurevisor's segment system to declare role-specific feature visibility in a Git-managed, reviewable, auditable way while keeping the backend firmly in control of what roles a user actually has.

Who owns what

Before declaring any segments or features, it's worth being precise about responsibilities.

ConcernOwner
Authenticating the userBackend
Determining which roles the user hasBackend
Communicating roles to the clientBackend (via API response / JWT)
Declaring which features require which rolesFeaturevisor project (Git)
Evaluating feature visibility at runtimeFeaturevisor SDK (client)
Enforcing access on sensitive operationsBackend (always)

The key insight:

  • Featurevisor never decides who a user is.
  • It only decides what a user with a given set of attributes, including their roles, should see.
  • The roles themselves are always sourced from the backend.

This means:

  • Frontend engineers stop writing role-checking logic, and they only ask "is this feature enabled?"
  • Backend engineers retain full control over role assignment and enforcement
  • Product managers can see exactly which roles unlock which features, all in one reviewable place

Note: while this guide treats frontend as the layer where the role-checking logic is implemented, it is entirely possible to implement it on yet another backend service as well.

How it works

Featurevisor supports attributes with an array type, which can expect an array of strings in the context in your application runtime. This is a good fit for roles, as roles are inherently a set of strings.

The pattern has three parts:

  1. A roles attribute: an array attribute representing the current user's roles
  2. Role-specific segments: one segment per role, with a condition checking if the array contains that role
  3. Feature rules that target those segments: features target the appropriate role segments

When our application initializes the Featurevisor SDK, it passes the roles (fetched from the backend) into the evaluation context. The SDK does the rest.

Define roles attribute

attributes/roles.yml
description: |
The roles assigned to the currently authenticated user,
as provided by the backend.
type: array
# we optionally make use of the `enum` property
# to provide a list of allowed roles values
items:
type: string
enum:
- viewer
- editor
- manager
- admin
- super-admin

The items and enum properties are here for linting purposes. It won't break evaluation if additional roles appear, but it makes our Featurevisor project self-documenting and help catch issues early.

Role specific segments

Each segment encodes a single, reusable question: does this user have this role?

segments/role-super-admin.yml
description: Users who have the super-admin role
conditions:
- attribute: roles
operator: includes
value: super-admin
segments/role-admin.yml
description: Users who have the admin role
conditions:
- attribute: roles
operator: includes
value: admin

And the same for other roles like:

  • manager => segments/role-manager.yml
  • editor => segments/role-editor.yml
  • viewer => segments/role-viewer.yml

These segments are reusable across any number of features. Create them once, use them everywhere.

Define features

Now we can start defining features that target those segments.

Because of how composable things are with Featurevisor, we can combine role segments with and, or, and not operators to express any access policy.

Some examples below:

Admin-only

features/admin-settings-panel.yml
description: |
Advanced settings panel
visible only to admins and super admins
tags:
- web
bucketBy: userId
rules:
production:
- key: admins
segments:
or:
- role-admin
- role-super-admin
percentage: 100
- key: everyone-else
segments: "*"
percentage: 0

Managers and above

features/billing-dashboard.yml
description: |
Billing dashboard
visible to managers, admins, and super admins
tags:
- web
bucketBy: userId
rules:
production:
- key: billing_roles
segments:
or:
- role-manager
- role-admin
- role-super-admin
percentage: 100
- key: everyone-else
segments: "*"
percentage: 0

Everyone except viewers

features/bulk-export.yml
description: |
Bulk data export
available to all roles except read-only viewers
tags:
- web
bucketBy: userId
rules:
production:
- key: non-viewers
segments:
not:
- role-viewer
percentage: 100
- key: everyone-else
segments: "*"
percentage: 0

Roles with other segments

We can also combine role requirements with other segments. Here, a new analytics featture is being rolled out to managers in the Netherlands first:

features/advanced-analytics.yml
description: |
Advanced analytics
gradual rollout starting with managers in the Netherlands
tags:
- web
bucketBy: userId
rules:
production:
- key: nl-managers-early-access
segments:
and:
- role-manager
- netherlands # referencing segments/netherlands.yml
percentage: 100
- key: everyone-else
segments: "*"
percentage: 0

This is where Featurevisor truly shines, as RBAC and progressive delivery are not separate systems. They are composable rules in the same declarative file.

Setting context in SDK

The backend provides the roles for the current user, and application code passes them into the SDK context in runtime:

your-app/index.js
import { createInstance } from '@featurevisor/sdk'
const session = await fetch('/api/me').then((res) => res.json())
const datafileContent = await fetch(DATAFILE_URL).then((res) => res.json())
const f = createInstance({
context: {
userId: session.userId,
roles: session.roles, // e.g. ['admin', 'super-admin']
country: 'nl',
},
datafile: datafileContent,
})

Learn more about building datafiles here.

Evaluating features

Now we can use the SDK instance to evaluate features wherever we please:

your-app/index.js
const canSeeAdminSettings = f.isEnabled('admin-settings-panel')
const canSeeBillingDashboard = f.isEnabled('billing-dashboard')
const canSeeBulkExport = f.isEnabled('bulk-export')
const canSeeAdvancedAnalytics = f.isEnabled('advanced-analytics')

Featurevisor offers additional SDK support covering React, Go, Python, Java, Swift, and more.

Benefits by audience

Product Managers

Every feature's access policy is a readable file in Git:

  • You can see which roles unlock which capabilities
  • Changes go through Pull Requests with reviews and approvals
  • No more digging through application code scattered across multiple repositories/teams why a certain feature is accessible for some users and not others

Backend Engineers

Your API remains the authoritative source of roles for the current logged in user, and you are not losing any control.

The pattern suggested in this guide means the frontend application(s) stops hard-coding role names in its code everywhere, which was always a maintenance liability and a potential source of inconsistency with what the backend actually enforces.

The SDK reads the roles you provide, and does not invent them.

Frontend Engineers

You write f.isEnabled('my_feature_key'), and that's it.

You become free from maintaining all the conditions and role-checking logic in your codebase, and you only ask "is this feature enabled?"

When a role requirement changes, it's a feature definition change outside of your application codebase, not resulting in any code changes or refactors scattered across multiple components.

The context object is populated once, against which all evaluations are run, and you don't have to keep passing it around for each and every evaluation which may happen from 20 different places.

The same is true for any application (including backend services) that needs to evaluate features based on roles.

Previous
User entitlements