Plugin Permissions#
Dify controls plugin access through two overlapping systems: a legacy workspace-level permission model (predating RBAC) and a newer RBAC permission key system introduced in v1.15.0 . Understanding how they interact is essential when debugging unexpected access denials for editor/normal users.
Permission Systems#
Legacy Permission Model#
When RBAC is disabled, plugin access is governed by workspace-level settings stored in the Permissions object with two fields:
install_permission— controls who can install/update/delete pluginsdebug_permission— controls who can use the plugin debugger
Each field accepts one of three values from PermissionType:
| Value | Meaning |
|---|---|
everyone | All workspace members |
admins | Admins and owners only |
noone | No one |
These settings are fetched from /workspaces/current/plugin/permission/fetch and updated via /workspaces/current/plugin/permission/change .
RBAC Permission Keys#
When RBAC is enabled, access is determined by whether workspacePermissionKeys (from useAppContext) contains specific string keys :
| Key | Controls |
|---|---|
plugin.install | Install, update plugins |
plugin.delete | Delete plugins |
plugin.debug | Access the plugin debugger |
plugin.plugin_preferences | Edit plugin preference settings |
The hasPermission utility checks whether a given key exists in the user's workspacePermissionKeys array.
How They Interact: use-reference-setting.ts#
The central hook usePluginSettingsAccess computes all plugin capability flags by combining both systems.
canDebugPlugin — the conditional is RBAC-mode-aware :
if rbacEnabled → hasPermission(workspacePermissionKeys, 'plugin.debug')
else → hasLegacyPluginPermissionAccess({ permission: debug_permission, ... })
canInstallPlugin / canUpdatePlugin / canDeletePlugin — these use AND logic , requiring both an RBAC key and the legacy check to pass:
canInstallPlugin = hasPermission(workspacePermissionKeys, 'plugin.install') && legacyCanInstallPlugin
The legacy helper hasLegacyPluginPermissionAccess short-circuits to true when rbacEnabled !== false — so when RBAC is on, the legacy side of the AND always passes and only the RBAC key matters.
Known Bug: v1.15.0 (Fixed in PR #38197)#
In Dify 1.15.0, canDebugPlugin was incorrectly computed as :
canDebugPlugin = hasPermission(workspacePermissionKeys, 'plugin.debug') && legacyCanDebugPlugin
Because hasLegacyPluginPermissionAccess returns true when RBAC is on , the effective gate became only hasPermission(workspacePermissionKeys, 'plugin.debug'). Editor/normal users lacking the plugin.debug RBAC key were blocked from the debug modal even when the owner set debug_permission = everyone.
This was fixed by PR #38197 (merged June 30, 2026) to use a conditional rather than AND: when RBAC is enabled, only the RBAC key is checked; when disabled, only the legacy setting is checked.
Who Can Change Permission Settings#
The ability to modify workspace plugin permission settings is gated separately :
canSetPermissions = !rbacEnabled && hasPermission(workspacePermissionKeys, 'plugin.plugin_preferences')
When RBAC is active, the legacy permission UI is hidden entirely — permissions are managed through RBAC role configuration instead.
Key Files#
| File | Purpose |
|---|---|
plugin-permissions.ts | hasLegacyPluginPermissionAccess helper |
use-reference-setting.ts | Computes all can*Plugin flags; main entry point |
types.ts (PermissionType) | Enum for legacy permission values |
utils/permission.ts | hasPermission RBAC key checker |
service/use-plugins.ts | API hooks for fetching/mutating permission settings |