Context for module-level permissions in PhoenixKit.
Controls which roles can access which admin sections and feature modules. Uses an allowlist model: row present = granted, absent = denied. Owner role always has full access (enforced in code, no DB rows needed).
Permission Keys
Core sections: dashboard, users, media, settings, modules Feature modules: billing, shop, emails, entities, tickets, posts, comments, ai, sync, publishing, referrals, sitemap, seo, maintenance, storage, languages, connections, legal, db, jobs
Constants & Metadata
Permissions.all_module_keys() # 25 built-in + any custom keys
Permissions.core_section_keys() # 5 core keys
Permissions.feature_module_keys() # 20 feature keys
Permissions.enabled_module_keys() # Core + enabled features + custom keys
Permissions.valid_module_key?("ai") # true
Permissions.feature_enabled?("ai") # true/false based on module status
Permissions.module_label("shop") # "E-Commerce"
Permissions.module_icon("shop") # "hero-shopping-cart"
Permissions.module_description("shop") # "Product catalog, orders, ..."Query API
Permissions.get_permissions_for_user(user) # User's keys via roles
Permissions.get_permissions_for_role(role_uuid) # Keys for a role
Permissions.role_has_permission?(role_uuid, "billing") # Single check
Permissions.get_permissions_matrix() # All roles → MapSet
Permissions.roles_with_permission("billing") # Role UUIDs with key
Permissions.users_with_permission("billing") # User UUIDs with key
Permissions.count_permissions_for_role(role_uuid) # Efficient count
Permissions.diff_permissions(role_a, role_b) # Compare two rolesMutation API
Permissions.grant_permission(role_uuid, "billing", granted_by_uuid)
Permissions.revoke_permission(role_uuid, "billing")
Permissions.set_permissions(role_uuid, ["dashboard", "users"], granted_by_uuid)
Permissions.grant_all_permissions(role_uuid, granted_by_uuid)
Permissions.revoke_all_permissions(role_uuid)
Permissions.copy_permissions(source_role_uuid, target_role_uuid, granted_by_uuid)Custom Keys API
Parent apps can register custom permission keys for custom admin tabs:
Permissions.register_custom_key("analytics", label: "Analytics", icon: "hero-chart-bar")
Permissions.unregister_custom_key("analytics")
Permissions.custom_keys() # List of registered custom key strings
Permissions.custom_view_permissions() # %{ViewModule => "key"} mappingCustom keys are always treated as "enabled" (no module toggle) and appear in the permission matrix UI under a "Custom" group.
Edit Protection
Permissions.can_edit_role_permissions?(scope, role) :: :ok | {:error, String.t()}Enforces: users cannot edit their own role, only Owner can edit Admin,
system roles cannot have is_system_role changed.
Summary
Functions
Returns all built-in and custom permission keys as a list. See enabled_module_keys/0 for filtered MapSet variant.
Auto-grants a permission key to the Admin system role. Stores a flag in phoenix_kit_settings so that if Owner later revokes the key, it won't be re-granted on next application restart.
Caches a LiveView module → permission key mapping for custom admin tabs. Used by the auth system to enforce permissions on custom admin LiveViews without reading Application config on every mount.
Checks if the given scope can edit the target role's permissions.
Clears all custom permission keys. For test isolation.
Copies all permissions from one role to another.
Returns the 5 core section keys.
Returns the number of permission keys granted to a role.
More efficient than length(get_permissions_for_role(role_uuid)).
Returns the list of custom permission key strings.
Returns the map of registered custom permission keys and their metadata.
Returns the cached custom view → permission mapping.
Compares permissions between two roles and returns a diff map.
Returns module keys that are currently enabled (core sections + enabled feature modules + custom keys)
as a MapSet for efficient membership checks. Core sections and custom keys are always included.
Feature modules are included only if their module reports enabled status.
Checks whether a feature module is currently enabled.
Returns the feature module keys from the registry.
Returns the list of module_keys granted to a specific role.
Returns the list of module_keys the given user has access to. Joins through role_assignments → role_permissions.
Returns a matrix of role_uuid → MapSet of granted keys for all roles.
Grants all permission keys (built-in + custom) to a role.
Grants a single permission to a role. Uses upsert to be idempotent.
Returns a short description for a module key.
Returns a Heroicon name for a module key.
Returns a human-readable label for a module key.
Registers a custom permission key with metadata.
Revokes all permissions from a role.
Revokes a single permission from a role.
Checks if a specific role has a specific permission.
Returns a list of role_ids that have been granted the given module_key.
Syncs permissions for a role: grants missing keys, revokes extras. Runs in a transaction.
Unregisters a custom permission key. Stale DB rows are harmless.
Returns a list of user_ids that have access to the given module_key (through any of their assigned roles).
Checks whether key is a known permission key (built-in or custom).
Functions
@spec all_module_keys() :: [String.t()]
Returns all built-in and custom permission keys as a list. See enabled_module_keys/0 for filtered MapSet variant.
@spec auto_grant_to_admin_roles(String.t()) :: :ok
Auto-grants a permission key to the Admin system role. Stores a flag in phoenix_kit_settings so that if Owner later revokes the key, it won't be re-granted on next application restart.
Caches a LiveView module → permission key mapping for custom admin tabs. Used by the auth system to enforce permissions on custom admin LiveViews without reading Application config on every mount.
@spec can_edit_role_permissions?( PhoenixKit.Users.Auth.Scope.t() | nil, PhoenixKit.Users.Role.t() ) :: :ok | {:error, atom()}
Checks if the given scope can edit the target role's permissions.
Returns :ok if allowed, or {:error, reason} if not.
Rules:
- Owner role cannot be edited (always has full access)
- Users cannot edit their own role (prevents self-lockout)
- Only Owner can edit Admin role (prevents privilege escalation)
@spec clear_custom_keys() :: :ok
Clears all custom permission keys. For test isolation.
@spec copy_permissions( integer() | String.t(), integer() | String.t(), integer() | String.t() | nil ) :: :ok | {:error, term()}
Copies all permissions from one role to another.
The target role will end up with the exact same set of permissions as the source role. Existing permissions on the target that don't exist on the source will be revoked.
@spec core_section_keys() :: [String.t()]
Returns the 5 core section keys.
@spec count_permissions_for_role(integer() | String.t()) :: non_neg_integer()
Returns the number of permission keys granted to a role.
More efficient than length(get_permissions_for_role(role_uuid)).
@spec custom_keys() :: [String.t()]
Returns the list of custom permission key strings.
Returns the map of registered custom permission keys and their metadata.
Returns the cached custom view → permission mapping.
@spec diff_permissions(integer() | String.t(), integer() | String.t()) :: %{ only_a: MapSet.t(), only_b: MapSet.t(), common: MapSet.t() }
Compares permissions between two roles and returns a diff map.
Returns %{only_a: MapSet.t(), only_b: MapSet.t(), common: MapSet.t()}
where only_a are keys role_a has but role_b doesn't, only_b is the
inverse, and common are keys both roles share.
@spec enabled_module_keys() :: MapSet.t()
Returns module keys that are currently enabled (core sections + enabled feature modules + custom keys)
as a MapSet for efficient membership checks. Core sections and custom keys are always included.
Feature modules are included only if their module reports enabled status.
Returns MapSet.t() unlike all_module_keys/0 which returns a list — callers use this
primarily for MapSet.member?/2 and MapSet.intersection/2 checks.
Checks whether a feature module is currently enabled.
Core section keys always return true. Feature module keys return the
result of calling the module's enabled?/0 (or equivalent) function.
Custom permission keys are always enabled (no module toggle).
Returns false for unknown keys.
@spec feature_module_keys() :: [String.t()]
Returns the feature module keys from the registry.
Returns the list of module_keys granted to a specific role.
@spec get_permissions_for_user(PhoenixKit.Users.Auth.User.t() | nil) :: [String.t()]
Returns the list of module_keys the given user has access to. Joins through role_assignments → role_permissions.
Returns a matrix of role_uuid → MapSet of granted keys for all roles.
@spec grant_all_permissions(integer() | String.t(), integer() | String.t() | nil) :: :ok | {:error, term()}
Grants all permission keys (built-in + custom) to a role.
@spec grant_permission( integer() | String.t(), String.t(), integer() | String.t() | nil ) :: {:ok, PhoenixKit.Users.RolePermission.t()} | {:error, Ecto.Changeset.t()}
Grants a single permission to a role. Uses upsert to be idempotent.
Returns a short description for a module key.
Returns a Heroicon name for a module key.
Returns a human-readable label for a module key.
Registers a custom permission key with metadata.
Custom keys extend the built-in 25 permission keys, allowing parent apps to define new permission scopes for custom admin tabs. Custom keys are always treated as "enabled" (no module toggle) and appear in the permission matrix UI under "Custom".
Raises ArgumentError if the key collides with a built-in key or has
an invalid format. Logs a warning on duplicate override.
Options
:label- Human-readable label (default: capitalized key):icon- Heroicon name (default:"hero-squares-2x2"):description- Short description (default:"")
Examples
Permissions.register_custom_key("analytics", label: "Analytics", icon: "hero-chart-bar")
Revokes all permissions from a role.
Revokes a single permission from a role.
Checks if a specific role has a specific permission.
Returns a list of role_ids that have been granted the given module_key.
@spec set_permissions( integer() | String.t(), [String.t()], integer() | String.t() | nil ) :: :ok | {:error, term()}
Syncs permissions for a role: grants missing keys, revokes extras. Runs in a transaction.
@spec unregister_custom_key(String.t()) :: :ok
Unregisters a custom permission key. Stale DB rows are harmless.
Returns a list of user_ids that have access to the given module_key (through any of their assigned roles).
Checks whether key is a known permission key (built-in or custom).