Groups and Users
Security in Fullfinity is managed through groups that control access to features and data.
Security Groups
Section titled “Security Groups”Groups define sets of permissions that can be assigned to users.
Defining Groups
Section titled “Defining Groups”Create groups in your module’s security file:
- data_type: Group identifier: sales_user_group name: User category: Sales description: "Create and edit own quotations and sales orders; read-only pricelists and templates."The
data_typeisGroup(the model’s verbose name is “User Group”). A group hasname,category(a plain text label, not a relation), an optionaldescription, an optionalexclusiveflag, andimplied_groups.Set
descriptionto a short, plain-language summary of what the group actually grants — the rights and actions it allows. It is surfaced on an info tooltip beside the group in the access picker so an admin can tell the access tiers apart (e.g. User vs Manager). Keep it grounded in the group’s realModelAccess/record-rule grants and, for a tiered group, describe what it adds over the group it implies (“Everything a User can do, plus …”).
Group Hierarchy
Section titled “Group Hierarchy”Groups can inherit from other groups:
- data_type: Group identifier: sales_manager_group name: Manager category: Sales implied_groups: - - link - sales_user_groupUsers in sales_manager_group automatically get all permissions from sales_user_group.
implied_groupsis a Many-to-Many relation, so it uses the relation-command form[["link", "<identifier>"]](link= link by identifier) — not a bare list of identifier strings.
How Permission Inheritance Works
Section titled “How Permission Inheritance Works”When a user belongs to a group with implied_groups, they automatically receive:
- All ModelAccess permissions from implied groups (union of all permissions)
- All RecordRule access from implied groups
- All menu/view visibility controlled by implied groups
Example chain:
invoicing_admin → invoicing_bookkeeper → invoicing_user → core_internalA user in invoicing_admin gets permissions from all four groups. If core_internal has read-only access to Currency and invoicing_admin has full CRUD, the user gets full CRUD (permissions are merged with OR logic).
Reaching the administrator
Section titled “Reaching the administrator”A database links its administrator to exactly one group when it is created, so a module installed later grants it nothing — its app is invisible to whoever installed it unless the module says so. Declare the link beside the groups it defines:
- data_type: Group identifier: core_admin implied_groups: - - link - crm_manager_groupThere is no admin bypass at the model level, so this is what actually grants an administrator access to a module’s data — and, because menu and settings visibility are derived from that access, what makes the module’s menus and its settings tab appear at all. See Menu and Settings Visibility. It stays a per-module declaration because keeping a group away from administrators is sometimes the point.
Important: When defining security for a module hierarchy:
- Base group (e.g.,
mymodule_user) should have minimal permissions - Higher groups add permissions via their own ModelAccess rules
- Users get the union of all permissions from their groups and all implied groups
Common Group Patterns
Section titled “Common Group Patterns”- data_type: Group identifier: crm_user_group name: User category: CRM- data_type: Group identifier: crm_manager_group name: Manager category: CRM implied_groups: - - link - crm_user_groupUser Model
Section titled “User Model”The built-in User model stores user information:
| Field | Type | Description |
|---|---|---|
name | Char | Display name |
email | Char | Email (login) |
hashed_password | Char | Hashed password |
active | Boolean | Account active |
groups | ManyToMany | Assigned groups |
Assigning Groups to Users
Section titled “Assigning Groups to Users”# In code: assign groups via the relation-command form on update()User = get_model("User")user = await User.filter(id=user_id).get()await user.update(groups=[["link", sales_manager_group_id]])update() on the User model automatically resolves implied groups (via
sync_implied_groups) so the user also receives every group reachable through
implied_groups.
Default Groups
Section titled “Default Groups”Create default user groups in data files:
- data_type: User identifier: demo_user name: Demo User email: demo@example.com groups: - - link - core_internal - - link - sales_user_groupGroup identifiers are bare (e.g.
core_internal,sales_user_group) — there is nomodule.namedotted form. As a Many-to-Many relation,groupstakes the[["link", "<identifier>"]]link-command form.
Built-in User Type Groups
Section titled “Built-in User Type Groups”These core groups define the user type and control route-level access:
| Identifier | Name | Category | Description |
|---|---|---|---|
core_internal | Internal | User Type | Backend users with full system access |
core_portal | Portal | User Type | External users with limited access (customers, vendors) |
core_public | Public | User Type | Guest/anonymous users |
Every user should belong to exactly one of these user type groups.
core_internal
Section titled “core_internal”Internal users have access to the full backend application. Use for employees and administrators.
core_portal
Section titled “core_portal”Portal users are external users (customers, vendors, contacts) with restricted access. They can only see their own records and portal-specific features.
core_public
Section titled “core_public”Public/guest users have minimal access. Used for anonymous website visitors.
Other Built-in Groups
Section titled “Other Built-in Groups”| Identifier | Name | Description |
|---|---|---|
core_admin | Settings | Access to system administration and settings |
Group Categories
Section titled “Group Categories”category is a plain text label on the group itself — there is no separate category
model or record to define. Groups that share a category string are grouped together in
the UI, and (when exclusive is true, the default) are treated as mutually exclusive
within that category. Built-in categories include User Type, Administration, and
Others; module groups typically use the app name (e.g. Sales, Invoicing).
- data_type: Group identifier: sales_user_group name: User category: SalesExclusive vs. Independent Selection
Section titled “Exclusive vs. Independent Selection”The boolean exclusive flag on a group controls how it is presented and selected
within its category. It defaults to true.
exclusive: true(default) → radio. Exclusive groups in the same category are mutually exclusive — the user-access UI renders them as a radio set, and picking one unlinks the others. This is the access-tier pattern: a category likeSalesoffering User / Manager where you hold exactly one tier. (Tiers still stack permissions viaimplied_groups— Manager implies User — but you select only the highest.)exclusive: false→ switch. A non-exclusive group renders as an independent toggle, so it can be granted on its own without affecting the other groups in the category. Use it for optional add-on capabilities (e.g. a “Show Full Accounting Features” flag) that aren’t part of a one-of-N tier.
- data_type: Group identifier: account_show_full_features name: Show Full Accounting Features category: Invoicing exclusive: falseChecking Permissions
Section titled “Checking Permissions”In Python Code
Section titled “In Python Code”The current user’s resolved group identifiers (including all implied groups) are
available on the environment as group_names. Check membership with a plain in:
from fullfinity.engine.context import env_ctx
env = env_ctx.get()group_names = env.user_data.get("group_names", [])
# Check if user has a specific groupif "sales_manager_group" in group_names: # Manager-only logic pass
# Check multiple groupsif any(g in group_names for g in ["sales_manager_group", "sales_admin_group"]): passIn Views
Section titled “In Views”Use a groups list (bare identifiers) under an element’s properties to show/hide it:
- type: field name: company properties: widget: DataCombo label: Company groups: - core_multi_companyIn Menu Items
Section titled “In Menu Items”- data_type: UiMenu name: Setup identifier: sales_config_menu parent: core_crm_menu action: configuration_sales_setup_action sequence: 100 groups: - - R - - sales_manager_groupMulti-Company Groups
Section titled “Multi-Company Groups”A built-in non-exclusive core_multi_company group gates multi-company features. Add it
to a group’s implied_groups to grant a user multi-company access. Company fields
don’t need hand-gating — a widget bound to a relation to Company is auto-gated to this
group during view composition (see Multi-Company).
- data_type: Group name: Multi Company identifier: core_multi_company category: Others exclusive: falseNext Steps
Section titled “Next Steps”- Route Authentication - Protecting API routes
- Model Access - CRUD permissions
- Record Rules - Row-level security