Grav User Roles and Permissions | OpsBlu Docs

Grav User Roles and Permissions

Complete guide to configuring user roles, permissions, and access control in Grav flat-file CMS including groups, ACL, and custom permissions.

Configure granular access control for Grav users using roles, groups, and custom permissions.

Permission System Overview

Grav uses a flexible ACL (Access Control List) system with:

  • User-level permissions - Set directly on user accounts
  • Group-based permissions - Inherit from user groups
  • Page-level permissions - Control access to specific pages
  • Admin permissions - Control admin panel access

Permission Structure

Access Levels

# user/accounts/username.yaml

access:
  admin:          # Admin panel permissions
    login: true   # Can access admin
    super: true   # Super admin (all permissions)
    pages: true   # Manage pages
    plugins: true # Manage plugins
    themes: true  # Manage themes
    users: true   # Manage users
    config: true  # Edit configuration
  site:           # Frontend permissions
    login: true   # Can login to site

Predefined Permission Sets

Super Administrator

# user/accounts/superadmin.yaml

state: enabled
email: superadmin@example.com
fullname: Super Administrator
access:
  admin:
    login: true
    super: true    # Grants ALL permissions
  site:
    login: true

Administrator (Limited)

# user/accounts/admin.yaml

state: enabled
email: admin@example.com
fullname: Administrator
access:
  admin:
    login: true
    super: false
    pages: true
    plugins: true
    themes: true
    users: true
    config: true
  site:
    login: true

Content Editor

# user/accounts/editor.yaml

state: enabled
email: editor@example.com
fullname: Content Editor
access:
  admin:
    login: true
    super: false
    pages: true      # Can edit pages
    plugins: false   # Cannot manage plugins
    themes: false    # Cannot change themes
    users: false     # Cannot manage users
    config: false    # Cannot edit config
  site:
    login: true

Author

# user/accounts/author.yaml

state: enabled
email: author@example.com
fullname: Author
access:
  admin:
    login: true
    super: false
    pages: true      # Limited page access (own pages only)
  site:
    login: true

Subscriber (Frontend Only)

# user/accounts/subscriber.yaml

state: enabled
email: subscriber@example.com
fullname: Subscriber
access:
  admin:
    login: false    # No admin access
  site:
    login: true     # Frontend login only

User Groups

Creating Groups

# user/accounts/groups.yaml

editors:
  readableName: Content Editors
  description: Users who create and edit content
  icon: fa-pencil
  access:
    admin:
      login: true
      pages: true
    site:
      login: true

authors:
  readableName: Authors
  description: Users who write blog posts
  icon: fa-user-edit
  access:
    admin:
      login: true
      pages: true
    site:
      login: true

subscribers:
  readableName: Subscribers
  description: Frontend users with no admin access
  icon: fa-users
  access:
    admin:
      login: false
    site:
      login: true

Assigning Users to Groups

# user/accounts/johndoe.yaml

state: enabled
email: johndoe@example.com
fullname: John Doe
groups:
  - editors       # Member of editors group

# Inherits editors group permissions
# Plus individual permissions
access:
  admin:
    login: true
    pages: true
  site:
    login: true

Multiple Group Assignment

# user/accounts/janedoe.yaml

state: enabled
email: janedoe@example.com
fullname: Jane Doe
groups:
  - editors
  - authors

# Inherits combined permissions from both groups
access:
  admin:
    login: true
    pages: true
  site:
    login: true

Admin Panel Permissions

Specific Admin Permissions

access:
  admin:
    login: true              # Access admin panel
    super: false             # Not super admin

    # Content management
    pages: true              # Edit pages
    pages.delete: false      # Cannot delete pages

    # Configuration
    config: true             # Edit site config
    config.system: false     # Cannot edit system config
    config.site: true        # Can edit site config

    # Plugins
    plugins: true            # Manage plugins
    plugins.install: false   # Cannot install new plugins

    # Themes
    themes: true             # Manage themes
    themes.install: false    # Cannot install new themes

    # Users
    users: true              # Manage users
    users.create: false      # Cannot create users
    users.delete: false      # Cannot delete users

Read-Only Admin Access

# user/accounts/viewer.yaml

access:
  admin:
    login: true
    super: false
    pages: false      # No page editing
    config: false     # No config editing
    plugins: false    # No plugin management
    themes: false     # No theme management
    users: false      # No user management
    # Can view admin panel but cannot modify

Page-Level Permissions

Restrict Page Access

# pages/01.home/default.md

---
title: Home
access:
  site.login: true    # Requires login
  admin.pages: true   # Requires pages permission
---

Page content here...

Role-Based Page Access

# pages/members/default.md

---
title: Members Area
access:
  site.login: true
  groups:
    - subscribers
    - editors
---

Members only content...

Check Permissions in Templates

{# Check if user can access admin #}

{% if grav.user.authorize('admin.login') %}
    <a href="/admin">Admin Panel</a>
{% endif %}

{# Check if user can edit pages #}

{% if grav.user.authorize('admin.pages') %}
    <a href="/admin/pages">Edit Pages</a>
{% endif %}

{# Check if user is in group #}

{% if 'editors' in grav.user.groups %}
    <a href="/editor-dashboard">Editor Dashboard</a>
{% endif %}

Custom Permissions

Define Custom Permissions

# user/accounts/johndoe.yaml

access:
  admin:
    login: true
  site:
    login: true

  # Custom permissions
  custom:
    view_analytics: true
    export_data: true
    manage_comments: true
    moderate_forum: false

Check Custom Permissions

{# Check custom permission #}

{% if grav.user.authorize('site.custom.view_analytics') %}
    <a href="/analytics">View Analytics</a>
{% endif %}

{% if grav.user.authorize('site.custom.export_data') %}
    <button>Export Data</button>
{% endif %}

Use in Plugins

<?php

// Check permission in plugin
if ($this->grav['user']->authorize('site.custom.view_analytics')) {
    // Show analytics
} else {
    // Deny access
    return $this->grav->redirect('/');
}

Permission Inheritance

How Permissions Combine

  1. Group Permissions - Base permissions from groups
  2. User Permissions - Override/extend group permissions
  3. Page Permissions - Further restrict based on page
# Group: editors
access:
  admin:
    login: true
    pages: true

# User: johndoe (in editors group)
groups:
  - editors

access:
  admin:
    login: true
    pages: true
    config: true    # Additional permission

# Effective permissions:
# - admin.login: true (from group)
# - admin.pages: true (from group)
# - admin.config: true (from user)

Protecting Content

Require Login for Entire Site

# user/config/plugins/login.yaml

route: /login
redirect_to_login: true

# Protect all pages by default
protect_protected_page_media: true

Public vs Protected Pages

# Public page (pages/about/default.md)
---
title: About
# No access restrictions
---

# Protected page (pages/dashboard/default.md)
---
title: Dashboard
access:
  site.login: true
---

# Admin only page (pages/admin-area/default.md)
---
title: Admin Area
access:
  admin.login: true
---

Conditional Content Display

{# Show content based on permissions #}

{% if grav.user.authenticated %}
    <p>Welcome, {{ grav.user.fullname }}!</p>

    {% if grav.user.authorize('admin.login') %}
        <p>You have admin access.</p>
    {% endif %}

    {% if 'editors' in grav.user.groups %}
        <p>You are an editor.</p>
    {% endif %}
{% else %}
    <p><a href="/login">Please login</a></p>
{% endif %}

Role-Based Redirects

Redirect After Login Based on Role

# user/config/plugins/login.yaml

redirect_after_login:
  default: '/'

# Custom redirect per group
redirect_after_login_by_role:
  admin.super: '/admin'
  admin.pages: '/admin/pages'
  site.login: '/dashboard'

Twig-Based Redirects

{# Redirect based on user role #}

{% if grav.user.authenticated %}
    {% if grav.user.authorize('admin.super') %}
        {% do grav.redirect('/admin') %}
    {% elseif grav.user.authorize('admin.pages') %}
        {% do grav.redirect('/admin/pages') %}
    {% else %}
        {% do grav.redirect('/dashboard') %}
    {% endif %}
{% endif %}

Best Practices

1. Principle of Least Privilege

Give users minimum permissions needed:

# Bad - too many permissions
access:
  admin:
    super: true  # Everything

# Good - specific permissions
access:
  admin:
    login: true
    pages: true  # Only what's needed

2. Use Groups for Common Roles

# Define groups for common roles
# user/accounts/groups.yaml

editors:
  access:
    admin:
      login: true
      pages: true

# Assign users to groups
# user/accounts/editor1.yaml
groups:
  - editors

3. Document Custom Permissions

# user/accounts/groups.yaml

# Document what each permission does
moderators:
  description: Users who can moderate content
  access:
    custom:
      moderate_comments: true   # Can approve/delete comments
      view_reports: true        # Can view user reports
      ban_users: false          # Cannot ban users

4. Regular Permission Audits

# List all users and their permissions
for file in user/accounts/*.yaml; do
    echo "=== $file ==="
    grep -A 10 "access:" $file
done

5. Separate Admin and Content Users

# Content editors - limited admin access
access:
  admin:
    login: true
    pages: true

# System admins - full access
access:
  admin:
    super: true

Troubleshooting

User Has Access But Cannot Edit

Check:

  1. User has admin.pages permission
  2. Page is not protected with stricter permissions
  3. User state is enabled
  4. Cache cleared after permission change

Permission Changes Not Taking Effect

# Clear cache
bin/grav clear-cache

# Logout and login again
# Permissions cached in session

Cannot Access Admin Panel

Verify:

access:
  admin:
    login: true  # Must be true

Next Steps

Resources