Examples
Examples of RBAC functionality in Nocto
Availability:
RBAC framework is available from the following versions:
- CLI (npx @rsc-labs/create-nocto-app) - >= 0.0.19
- @rsc-labs/nocto - >= 0.1.6
- @rsc-labs/nocto-plugin-system - >= 0.1.5
Basic example
Availability:
Even with basic example we encourage to get familiar firstly with RBAC Concept
Background
You have 2 users in database - one of them has the role Admin
and second one is the Product manager
.
Admin
can do everything, while Product manager
can work only with products and inventory.
You are storing the users with assigned roles. You expose usual Medusa API - /admin/users/{id}
, which can be used to retrieve single user. You are storing the role in metadata
field, so it is available when fetching the user.
Implementation
You need to get list of permissions for the logged-in user. You do not store any permissions related to Nocto, so you will need to have mapping between your backend model and Nocto.
This is the example of fetchPermission
function:
fetchPermissions: async (userId: string) => {
const response = await fetch(`${import.meta.env.VITE_MEDUSA_ADMIN_BACKEND_URL}/admin/users/${userId}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
const json = await response.json()
/*
Example of json:
{
"id": "user_01J8M9MBE14VGDPPJ5YZ3VG94S",
"first_name": "Admin",
"last_name": "Test1",
"email": "admin@medusa-test.com",
"avatar_url": null,
"metadata": {
"role": "product-manager"
},
"created_at": "2024-09-25T09:54:05.889Z",
"updated_at": "2025-07-18T10:27:34.615Z",
"deleted_at": null
}
*/
if (json.user.metadata.role === 'product-manager') {
return new Map<string, string[]>([
["@customers", ["forbid"]],
["@orders", ["forbid"]],
["@price-lists", ["forbid"]],
["@promotions", ["forbid"]],
]);
}
return new Map<string, string[]>();
},
Environment variable:
In the above code we are using env variable called VITE_MEDUSA_ADMIN_BACKEND_URL which is not set by default in Nocto.
Let's describe above code:
- You are getting user from the backend using Medusa User ID
- Based on the information stored in database you are doing mapping of a role to Nocto permissions. For the role
product-manager
you are describing what Nocto plugins (it can be a route, UI element etc) are forbidden. For the rest - you are returning empty map.
Worth to note:
For simplicity, we decided to use negative permission (forbid
), but you can also return positive permission (like allow
or view
) and evaluate it later.
Evaluate access
Above information about permissions can now be used to evaluate access to particular piece of UI. It is done via a function evaluateAccess
. This function is executed for every plugin in Nocto to decide if it shall be rendered for the logged-in user.
The function gets list of permissions and returns decision (boolean
) if the plugin shall be rendered.
For instance, when Nocto renders @products
plugin, it executed evaluateAccess
function with permissions related to @products
plugin.
Keeping above example of permissions from fetchPermissions
, evaluateAccess
can be implemented in the following way:
evaluateAccess: (permissions?: string[]) => {
if (permissions?.includes("forbid") ) {
return false;
}
return true;
},
It means that if permissions
contain forbid
permission, then we do not want to render such plugin for the user.
Freedom:
You may notice that you have full freedom how to handle RBAC. Function fetchPermissions
can return whatever values for Nocto plugins and you will decide in evaluateAccess
how use these values.
The full code of your Nocto application (main.tsx
) will look like this:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { loadMyNoctoPlugins } from './utils';
import { noctoConfig } from './../nocto-config'
import App from "@rsc-labs/nocto"
import "./index.css"
import "./themes/theme-oceanic.css"
async function bootstrap() {
await loadMyNoctoPlugins(noctoConfig);
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App noctoConfig={noctoConfig} rbac={{
fetchPermissions: async (userId: string) => {
const response = await fetch(`${import.meta.env.VITE_MEDUSA_ADMIN_BACKEND_URL}/admin/users/${userId}`, {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
})
const json = await response.json()
/*
Example of json:
{
"id": "user_01J8M9MBE14VGDPPJ5YZ3VG94S",
"first_name": "Admin",
"last_name": "Test1",
"email": "admin@medusa-test.com",
"avatar_url": null,
"metadata": {
"role": "product-manager"
},
"created_at": "2024-09-25T09:54:05.889Z",
"updated_at": "2025-07-18T10:27:34.615Z",
"deleted_at": null
}
*/
if (json.user.metadata.role === 'product-manager') {
return new Map<string, string[]>([
["@customers", ["forbid"]],
["@orders", ["forbid"]],
["@price-lists", ["forbid"]],
["@promotions", ["forbid"]],
]);
}
return new Map<string, string[]>();
},
evaluateAccess: (permissions?: string[]) => {
if (permissions?.includes("forbid") ) {
return false;
}
return true;
},
}}/>
</React.StrictMode>
);
}
bootstrap();