permission_policy
A flexible and powerful permission management system for Flutter applications with role-based access control, hierarchical permissions, and customizable UI components.
Permission policy helps you manage permissions in your Flutter application.
^7.27.4{"sdk":"flutter"}{"sdk":"flutter"}^6.0.0English project snapshot. Visit GitHub for the latest content.
A powerful and flexible permission management system for Flutter applications with device-level role-based access control (RBAC), hierarchical permissions, and customizable UI components.
Permission and Role with full serialization supportPermissionResult includes the reason, contributing roles, and permissionsPermissionStorage for any backend (default uses Nylo local storage)PermissionGuard, PermissionBuilder, and RoleSelectorAdd to your pubspec.yaml:
dependencies:
permission_policy: ^3.2.1
Or with the Dart CLI:
flutter pub add permission_policy
import 'package:permission_policy/permission_policy.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize
await PermissionPolicy.instance.initialize();
// Define permissions and roles
await PermissionPolicy.instance.configure(
permissions: [
Permission(id: 'read_posts', name: 'Read Posts'),
Permission(id: 'write_posts', name: 'Write Posts'),
Permission(id: 'delete_posts', name: 'Delete Posts'),
],
roles: [
Role(id: 'viewer', name: 'Viewer', permissions: ['read_posts']),
Role(id: 'editor', name: 'Editor', permissions: ['read_posts', 'write_posts']),
Role(id: 'admin', name: 'Admin', permissions: ['read_posts', 'write_posts', 'delete_posts']),
],
);
// Assign a role to the device
await PermissionPolicy.instance.giveRole('editor');
runApp(MyApp());
}
For quick setups you can use a map of role names to permission IDs:
await PermissionPolicy.instance.configureSimple({
'admin': ['read_posts', 'write_posts', 'delete_posts'],
'editor': ['read_posts', 'write_posts'],
'viewer': ['read_posts'],
});
A Permission represents a specific action or access right. Permissions can inherit from other permissions using inheritsFrom - granting a permission automatically includes its parents.
Permission(
id: 'edit_profile',
name: 'Edit Profile',
description: 'Can edit user profile information',
category: 'user',
priority: 2,
inheritsFrom: ['view_profile'], // Also grants view_profile
metadata: {'icon': 'edit'},
)
| Property | Type | Description |
|---|---|---|
id |
String |
Unique identifier (required) |
name |
String |
Human-readable name (required) |
description |
String? |
What this permission allows |
category |
String? |
Grouping category |
priority |
int |
Priority level (default 0) |
inheritsFrom |
List<String> |
Parent permission IDs to inherit |
metadata |
Map<String, dynamic> |
Custom metadata |
A Role groups permissions together and can inherit from other roles. Use isAssignable to control whether a role appears in selectors, and isSystem to mark roles that shouldn't be deleted.
Role(
id: 'moderator',
name: 'Moderator',
description: 'Can moderate content',
permissions: ['edit_posts', 'delete_posts'],
inheritsFrom: ['editor'], // Inherits all editor permissions
priority: 5,
isAssignable: true,
isSystem: false,
)
| Property | Type | Description |
|---|---|---|
id |
String |
Unique identifier (required) |
name |
String |
Human-readable name (required) |
description |
String? |
Role description |
priority |
int |
Priority level (default 0) |
permissions |
List<String> |
Directly assigned permission IDs |
inheritsFrom |
List<String> |
Parent role IDs to inherit from |
isAssignable |
bool |
Can be assigned (default true) |
isSystem |
bool |
System role flag (default false) |
metadata |
Map<String, dynamic> |
Custom metadata |
Permissions are resolved purely through roles. The device has a list of role IDs, and the system resolves all permissions by walking the role and permission inheritance chains.
final policy = PermissionPolicy.instance;
// Simple boolean check
bool canEdit = await policy.hasPermission('edit_posts');
// Check any of multiple permissions
bool canManage = await policy.hasAnyPermission(['edit_posts', 'delete_posts']);
// Check all permissions are granted
bool fullAccess = await policy.hasAllPermissions(['read_posts', 'write_posts', 'delete_posts']);
Use checkPermission to get a PermissionResult with the reason and contributing factors:
PermissionResult result = await policy.checkPermission('edit_posts');
print(result.granted); // true or false
print(result.reason); // e.g. 'Permission granted through roles'
print(result.contributingRoles); // e.g. ['editor']
print(result.contributingPermissions); // e.g. ['edit_posts']
// Also available for multiple checks
PermissionResult anyResult = await policy.checkAnyPermission(['edit_posts', 'delete_posts']);
PermissionResult allResult = await policy.checkAllPermissions(['read_posts', 'write_posts']);
// Give a role to the device
await policy.giveRole('editor');
// Remove a role
await policy.removeRole('editor');
// Check roles
bool isAdmin = await policy.hasRole('admin');
bool hasEditAccess = await policy.hasAnyRole(['editor', 'admin']);
bool isSuperMod = await policy.hasAllRoles(['moderator', 'admin']);
// Get the device's roles
List<String> roleIds = await policy.getDeviceRoles();
// Get all available roles
List<Role> allRoles = await policy.getRoles();
// Get only assignable roles (isAssignable == true)
List<Role> assignable = await policy.getAssignableRoles();
// Get all resolved permissions for the device
Set<String> allPermissions = await policy.getDevicePermissions();
// Clear all device roles
await policy.clearRoles();
// Clear everything (permissions, roles, and device roles)
await policy.clearAll();
Conditionally renders a widget based on permissions or roles. Shows the child when granted and the fallback when denied.
PermissionGuard(
permissions: ['edit_posts'],
child: ElevatedButton(
onPressed: () => editPost(),
child: Text('Edit Post'),
),
fallback: Text('You cannot edit this post'),
)
Guard by roles:
PermissionGuard(
roles: ['admin', 'moderator'],
checkType: PermissionCheck.any, // Device needs ANY of these roles
child: AdminPanel(),
)
| Property | Type | Description |
|---|---|---|
permissions |
List<String>? |
Permission IDs to check |
roles |
List<String>? |
Role IDs to check |
checkType |
PermissionCheck |
.all (default) or .any |
child |
Widget |
Widget shown when granted (required) |
fallback |
Widget? |
Widget shown when denied |
loading |
Widget? |
Widget shown while checking |
onPermissionChecked |
Function(PermissionResult)? |
Callback with result |
showDebugInfo |
bool |
Show debug tooltip in debug mode (default false) |
A builder pattern that gives you full control over the widget tree based on permission state.
PermissionBuilder(
permissions: ['delete_posts'],
builder: (context, hasPermission, result) {
return ElevatedButton(
onPressed: hasPermission ? () => deletePost() : null,
style: ElevatedButton.styleFrom(
backgroundColor: hasPermission ? Colors.red : Colors.grey,
),
child: Text(hasPermission ? 'Delete' : 'Cannot Delete'),
);
},
)
| Property | Type | Description |
|---|---|---|
permissions |
List<String>? |
Permission IDs to check |
roles |
List<String>? |
Role IDs to check |
checkType |
PermissionCheck |
.all (default) or .any |
builder |
Widget Function(BuildContext, bool, PermissionResult?) |
Builder function (required) |
loading |
Widget? |
Widget shown while checking |
An interactive role selection widget with three display styles: list, chips, and cards.
// List style (default)
RoleSelector(
style: const RoleSelectorStyle.list(),
onRoleChanged: (role, isSelected) {
if (isSelected) {
PermissionPolicy.instance.giveRole(role.id);
} else {
PermissionPolicy.instance.removeRole(role.id);
}
},
)
// Chip style
RoleSelector(
style: const RoleSelectorStyle.chips(),
multiSelect: true,
showDescriptions: true,
)
// Card grid style
RoleSelector(
style: const RoleSelectorStyle.cards(
crossAxisCount: 3,
selectedColor: Colors.blue,
),
)
| Property | Type | Description |
|---|---|---|
style |
RoleSelectorStyle |
.list(), .chips(), or .cards() |
onRoleChanged |
Function(Role, bool)? |
Called when a role is toggled |
onSelectionComplete |
Function(List<Role>)? |
Called with all selected roles |
multiSelect |
bool |
Allow multiple selection (default true) |
initialSelectedRoleIds |
List<String> |
Pre-selected role IDs |
itemBuilder |
Widget Function(Role, bool, VoidCallback)? |
Custom item builder |
roleFilter |
bool Function(Role)? |
Filter which roles to display |
showDescriptions |
bool |
Show role descriptions (default true) |
loading |
Widget? |
Loading widget |
emptyWidget |
Widget? |
Widget when no roles available |
RoleSelectorStyle supports customizing spacing, selectedColor, backgroundColor, titleStyle, subtitleStyle, scrollPhysics, and for cards: crossAxisCount and childAspectRatio.
Build hierarchical permission structures where higher-level permissions automatically include lower-level ones:
await PermissionPolicy.instance.configure(
permissions: [
Permission(id: 'view_content', name: 'View Content'),
Permission(
id: 'edit_content',
name: 'Edit Content',
inheritsFrom: ['view_content'], // Includes view
),
Permission(
id: 'publish_content',
name: 'Publish Content',
inheritsFrom: ['edit_content'], // Includes edit AND view
),
],
roles: [
Role(id: 'author', name: 'Author', permissions: ['edit_content']),
Role(
id: 'publisher',
name: 'Publisher',
inheritsFrom: ['author'], // Inherits author's permissions
permissions: ['publish_content'],
),
],
);
A device with the publisher role will have: publish_content, edit_content, and view_content.
Implement PermissionStorage to use any backend (database, API, shared preferences, etc.):
class ApiPermissionStorage implements PermissionStorage {
@override
Future<void> initialize() async {
// Connect to your API
}
@override
Future<void> storePermissions(List<Permission> permissions) async { ... }
@override
Future<List<Permission>> getPermissions() async { ... }
@override
Future<Permission?> getPermission(String id) async { ... }
@override
Future<void> storeRoles(List<Role> roles) async { ... }
@override
Future<List<Role>> getRoles() async { ... }
@override
Future<Role?> getRole(String id) async { ... }
@override
Future<void> storeDeviceRoles(List<String> roleIds) async { ... }
@override
Future<List<String>> getDeviceRoles() async { ... }
@override
Future<void> clearDeviceRoles() async { ... }
@override
Future<void> clear() async { ... }
}
// Use it
final policy = PermissionPolicy.createWithStorage(ApiPermissionStorage());
Use PermissionPolicy.reset() in test setup to start fresh:
import 'package:flutter_test/flutter_test.dart';
import 'package:permission_policy/permission_policy.dart';
void main() {
setUp(() async {
PermissionPolicy.reset();
await PermissionPolicy.instance.initialize();
await PermissionPolicy.instance.configureSimple({
'admin': ['read', 'write', 'delete'],
'viewer': ['read'],
});
});
test('editor can write', () async {
await PermissionPolicy.instance.giveRole('admin');
expect(await PermissionPolicy.instance.hasPermission('write'), isTrue);
});
testWidgets('guard shows content with permission', (tester) async {
await PermissionPolicy.instance.giveRole('viewer');
await tester.pumpWidget(
MaterialApp(
home: PermissionGuard(
permissions: const ['read'],
child: const Text('Content'),
fallback: const Text('Denied'),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Content'), findsOneWidget);
});
}
Please see CHANGELOG for more information on what has changed recently.
The MIT License (MIT). Please view the License File for more information.