Roles & Permissions

How access is decided across the whole console: permissions are seeded in code, roles bundle them, and a user's effective access is simply the union of every permission on every role they currently hold.

The role-only model

There is no way to grant a permission to a person directly — access always flows permission → role → user. This is deliberate: it keeps access auditable (you can always answer "why can they do that?" by looking at their roles) and keeps individual exceptions out of the system.

Two access-gate styles exist side by side in the console today. A handful of setup-style resources — including this page's own two resources, plus Warehouses, Geography and most of Customers — check isSuperAdmin() only, with no granular permission string honoured at all, even where one is seeded (e.g. admin.manage_roles, admin.manage_users). Everything else checks isSuperAdmin() || hasPermission('module.action'). Practically: today, only super_admin role holders can reach the pages on this page, regardless of any other permission granted to them.


Roles Built

Nav location: Access Control → Roles & Permissions lists roles first. Access: super-administrator only.

Screenshot placeholderThe Roles list — Name, Display Name, System (icon), Permissions count, Users count columns; an Edit button and a hidden/absent Delete button on a system role's row.

List columns

ColumnShows
NameThe slug identifier, e.g. sales_manager. Searchable, sortable.
Display NameThe human-readable label shown everywhere else (attach pickers, badges). Searchable, sortable.
SystemCheck icon when is_system is true — a role the application itself depends on.
PermissionsLive count of attached permissions. Sortable.
UsersLive count of users currently holding this role. Sortable.
CreatedDate + time, hidden by default. Sortable.

There are no filters on this list.

Creating or editing a role

  1. Select New role (or a row to edit)
  2. Set the Name (slug)

    A short, code-friendly identifier such as sales_manager — required, max 100, unique. On a system role this field is disabled; you cannot rename it.

  3. Set the Display Name

    What people actually see in the console — required, max 150.

  4. Optionally add a Description

    Free text explaining the role's purpose.

  5. Save

    Permissions are not assigned on this form — see the Permissions tab below, which appears once the role exists.

FieldTypeRequiredNotes
nameTextYesMax 100, unique, slug-style. Disabled for system roles.
display_nameTextYesMax 150.
descriptionTextareaNoFull width.
System roles cannot be deleted

If is_system is true — for example the super_admin role itself — the Delete button is simply hidden, both on the list row and on the role's own edit page header. This protects roles the application logic depends on from being removed by accident. Its name field is also locked on the edit form for the same reason, though its display name, description and permissions remain editable.

Assigning permissions to a role Built

Open a role for editing and use its Permissions tab. Permissions are never created or edited here — only attached to, or detached from, the role. The table is grouped by module by default, so you can see at a glance how much of one module (e.g. all of billing) a role already covers.

  1. Open the role, then its Permissions tab
  2. Select Attach

    Opens a picker preloaded with every permission in the system, searchable by name.

  3. Choose one permission and confirm

    Repeat for each permission the role needs — there is no multi-select in the attach picker, and no "assign whole module at once" bulk action. Building a role that should cover, say, all of billing means attaching each billing.* permission individually.

  4. Remove a permission with row Detach

    Or select several rows and use the bulk Detach action. Detaching never deletes the permission — only the role's grant of it.

Screenshot placeholderA role's Permissions relation-manager tab, grouped by module (e.g. "billing", "sales" headers), with the Attach button and per-row Detach buttons.
No bulk "assign whole module"

Some other systems let you tick a module and grant every permission in it at once. This console does not — each permission is attached one at a time. When building a broad role, work module-by-module using the grouped table as your checklist.


Permissions Built (view-only)

Nav location: Access Control → Roles & Permissions, the Permissions list. Access: super-administrator only. Every permission is seeded in code and always carries is_system = true — there is no way to create, edit or delete a permission from this screen. Its canCreate() check is explicitly hardcoded to always refuse, so you will not even see a New button here.

Screenshot placeholderThe Permissions list, grouped by module, with the Module filter open showing the dynamic list of module names.

List columns

ColumnShows
NameThe permission string, e.g. payment.approve. Searchable, sortable.
Display NameHuman-readable label, e.g. "Approve Payment". Searchable.
ModuleBadge — the module this permission belongs to. Sortable. Table is grouped by this column by default.
SystemCheck icon — always on for every permission in this console.

Filters

A single Module filter, built dynamically from whatever module names actually exist in the database — it is never a hardcoded list, so it always matches reality even as new modules are seeded.

There are no row actions at all on this list — no Edit, no Delete. Select a row to open its view page instead.

The permission's view page

Two read-only sections:

SectionShows
Permission Detailsname, display_name, description, module (badge), and is_system formatted as "System Permission: Yes/No".
Assigned to RolesEvery role currently granted this permission, as a comma-separated list of badges — the quickest way to answer "which roles can do this?" for any single permission.
Screenshot placeholderA permission's view page showing its Permission Details section and, below it, the Assigned to Roles list of role badges.

Permission modules at a glance

Permissions are grouped by a module field. The table below summarises the breadth by module with a few representative examples — it is not the full list. For every seeded permission name, see the Glossary reference page.

ModuleCoversExamples
adminConsole administration itself.admin.manage_users, admin.manage_roles, admin.view_system_config
userA user acting on their own account.user.view_self, user.change_password
zone / region / pincodeThe Geography hierarchy.region.create, pincode.import Planned
customerCustomer records and their categorisation.customer.verify, customer.view_financial, customer.merge Planned
warehouseWarehouses, stock locations, stock levels, carriers.warehouse.create, stock_level.edit, delivery_carrier.manage
inventoryProducts, pricing, stock moves/transfers, closing stock.product.view, closing_stock.audit, stock_transfer.approve Planned
salesSale orders and their credit/discount approval side-flows.sale_order.approve, sale_order.cancel, credit.approve
despatchPicking waves, packages, returns, VPP payment collection.wave.manage, package.track, vpp_payment.reconcile
billingPayment vouchers, bank reconciliation, journals, GST e-invoices, PADs, refunds, charges.payment.approve, bank_statement.reconcile, gst_einvoice.manage
reportsDashboard KPIs and reporting.dashboard.view, report.view Planned
Some permissions exist ahead of their console button

A number of seeded permissions — pincode.import, customer.merge, most of stock_move.*/stock_transfer.*, product.create/edit, and others — describe capabilities that have no wired action anywhere in this console yet. Granting one of these on a role does no harm, but it will not make a corresponding button appear; those actions happen via the API, the field app, or are simply not built yet. This page can still assign such a permission to a role in preparation for when the feature ships.

Related pages

See Users for how roles (with an optional expiry) are attached to a person, and Getting started for the general "why did a button disappear" mental model that permissions and states share.