Craft CMS User Management Overview | OpsBlu Docs

Craft CMS User Management Overview

Complete guide to managing users in Craft CMS including user accounts, groups, permissions, authentication, and access control.

Comprehensive guide to managing users in Craft CMS, covering user accounts, user groups, permissions, registration, authentication, and security best practices.

Understanding Craft CMS Users

Craft CMS provides a robust user management system with:

  • User Accounts - Individual user profiles with custom fields
  • User Groups - Organize users into groups
  • Permissions - Granular access control
  • Custom Fields - Extend user profiles with custom data
  • User Authentication - Login, registration, password reset
  • Multi-Site Support - Per-site user access control

User Types

Admin Users

  • Full Control Panel access
  • Can manage all content and settings
  • Can create/edit other users
  • Typically site administrators and developers

Content Editors

  • Limited Control Panel access
  • Can create and edit content
  • No access to system settings
  • Typically content managers and editors

Front-End Users

  • No Control Panel access
  • Can log in to front-end features
  • Member areas, user profiles, comments
  • Typically site members and customers

Guest Users

  • Not logged in
  • Public access only
  • No authentication required

User Groups

What are User Groups?

User groups organize users and define permissions:

  • Assign permissions to groups, not individual users
  • Users inherit permissions from all their groups
  • Groups can have custom fields
  • Useful for role-based access control (RBAC)

Common Group Structures

Administrators
├── Full control panel access
└── All permissions

Editors
├── Edit all entries
├── Edit assets
└── Limited settings access

Authors
├── Edit own entries
├── Upload assets
└── No settings access

Members (Front-end only)
├── No control panel access
├── Access to member area
└── Can edit own profile

User Permissions

Permission Levels

Craft CMS offers granular permissions:

Control Panel Access

  • Access the Control Panel
  • Access CP when offline
  • Perform Craft and plugin updates

General Permissions

  • Edit entries
  • Edit assets
  • Edit categories
  • Edit users
  • Access specific sections/volumes

Entry Permissions

  • Create entries
  • Edit entries (own/others)
  • Publish entries (own/others)
  • Delete entries (own/others)
  • Edit entry drafts

Asset Permissions

  • View assets
  • Upload assets
  • Edit assets
  • Delete assets
  • Replace files
  • Edit image editor

Permission Inheritance

Users can belong to multiple groups and inherit permissions:

User: John Doe
Groups: [Editors, Authors]
Permissions: Editors permissions + Authors permissions

Accessing User Management

Control Panel

Navigate to: Settings → Users

Options:

  • Users - Manage individual user accounts
  • User Groups - Create and manage groups
  • User Fields - Add custom fields to user profiles
  • User Settings - Configure registration, photos, etc.

Programmatic Access

{# Get all users #}
{% set users = craft.users().all() %}

{# Get users by group #}
{% set editors = craft.users().group('editors').all() %}

{# Get current logged-in user #}
{% set currentUser = currentUser ?? null %}

{# Check if user is logged in #}
{% if currentUser %}
  <p>Welcome, {{ currentUser.fullName }}!</p>
{% endif %}

User Fields

Default User Fields

Every user has these built-in fields:

  • Username - Unique identifier
  • Email - Email address (can be used for login)
  • First Name - User's first name
  • Last Name - User's last name
  • Photo - Profile picture
  • Password - Hashed password
  • Status - Active, Pending, Suspended, Locked

Custom User Fields

Extend users with custom fields:

User Fields Layout:
├── Profile Information
│   ├── Bio (Rich Text)
│   ├── Company (Text)
│   ├── Job Title (Text)
│   └── Location (Text)
├── Social Media
│   ├── Twitter Handle (Text)
│   ├── LinkedIn URL (URL)
│   └── Website (URL)
└── Preferences
    ├── Newsletter Opt-in (Lightswitch)
    ├── Preferred Language (Dropdown)
    └── Time Zone (Dropdown)

Access custom fields in templates:

{% set user = currentUser %}

{% if user %}
  <h2>{{ user.fullName }}</h2>
  <p>{{ user.bio }}</p>
  <p>Company: {{ user.company }}</p>

  {% if user.newsletterOptIn %}
    <p>Subscribed to newsletter</p>
  {% endif %}
{% endif %}

User Registration

Enable Public Registration

Configure in: Settings → Users → Settings

Options:

  • Allow public registration - Enable/disable
  • Default User Group - Auto-assign to group
  • Validate email addresses - Require email verification
  • Require email verification - Users must verify email

Front-End Registration Form

{# templates/account/register.twig #}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/save-user') }}
  {{ redirectInput('account/welcome') }}

  <h2>Create Account</h2>

  {# Username #}
  <label for="username">Username</label>
  <input type="text"
         name="username"
         id="username"
         required
         value="{{ user.username ?? '' }}">

  {# Email #}
  <label for="email">Email</label>
  <input type="email"
         name="email"
         id="email"
         required
         value="{{ user.email ?? '' }}">

  {# Password #}
  <label for="password">Password</label>
  <input type="password"
         name="password"
         id="password"
         required>

  {# First/Last Name #}
  <label for="firstName">First Name</label>
  <input type="text"
         name="firstName"
         id="firstName"
         value="{{ user.firstName ?? '' }}">

  <label for="lastName">Last Name</label>
  <input type="text"
         name="lastName"
         id="lastName"
         value="{{ user.lastName ?? '' }}">

  {# Custom Fields #}
  <label for="bio">Bio</label>
  <textarea name="fields[bio]" id="bio">{{ user.bio ?? '' }}</textarea>

  {# Submit #}
  <button type="submit">Create Account</button>
</form>

{# Display errors #}
{% if user is defined and user.hasErrors() %}
  <ul class="errors">
    {% for error in user.getErrors() %}
      <li>{{ error }}</li>
    {% endfor %}
  </ul>
{% endif %}

User Login

Login Form

{# templates/account/login.twig #}

{# Check if already logged in #}
{% if currentUser %}
  <p>You're already logged in as {{ currentUser.username }}.</p>
  <a href="{{ url('account/logout') }}">Logout</a>
{% else %}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/login') }}
  {{ redirectInput('account/dashboard') }}

  <h2>Login</h2>

  {# Login Name (username or email) #}
  <label for="loginName">Username or Email</label>
  <input type="text"
         name="loginName"
         id="loginName"
         required
         autocomplete="username">

  {# Password #}
  <label for="password">Password</label>
  <input type="password"
         name="password"
         id="password"
         required
         autocomplete="current-password">

  {# Remember Me #}
  <label>
    <input type="checkbox" name="rememberMe" value="1">
    Remember me
  </label>

  {# Submit #}
  <button type="submit">Login</button>

  {# Forgot Password Link #}
  <a href="{{ url('account/forgot-password') }}">Forgot your password?</a>
</form>

{# Display errors #}
{% if errorMessage is defined %}
  <p class="error">{{ errorMessage }}</p>
{% endif %}

{% endif %}

Logout

{# templates/account/logout.twig #}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/logout') }}
  {{ redirectInput('') }}

  <button type="submit">Logout</button>
</form>

{# Or as a link #}
<a href="{{ url('account/logout') }}">Logout</a>

Password Management

Forgot Password Form

{# templates/account/forgot-password.twig #}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/send-password-reset-email') }}

  <h2>Reset Password</h2>

  <label for="loginName">Email or Username</label>
  <input type="text"
         name="loginName"
         id="loginName"
         required>

  <button type="submit">Send Reset Link</button>
</form>

{% if message is defined %}
  <p class="success">{{ message }}</p>
{% endif %}

Set Password Form

{# templates/account/set-password.twig #}

{% requireLogin %}

<form method="post" accept-charset="UTF-8">
  {{ csrfInput() }}
  {{ actionInput('users/set-password') }}
  {{ redirectInput('account/profile') }}

  <input type="hidden" name="userId" value="{{ currentUser.id }}">
  <input type="hidden" name="code" value="{{ code }}">

  <h2>Set New Password</h2>

  {# New Password #}
  <label for="newPassword">New Password</label>
  <input type="password"
         name="newPassword"
         id="newPassword"
         required
         autocomplete="new-password">

  <button type="submit">Set Password</button>
</form>

User Profile Management

Edit Profile Form

{# templates/account/profile.twig #}

{% requireLogin %}

<form method="post" accept-charset="UTF-8" enctype="multipart/form-data">
  {{ csrfInput() }}
  {{ actionInput('users/save-user') }}
  {{ redirectInput('account/profile') }}

  <input type="hidden" name="userId" value="{{ currentUser.id }}">

  <h2>Edit Profile</h2>

  {# Email #}
  <label for="email">Email</label>
  <input type="email"
         name="email"
         id="email"
         value="{{ currentUser.email }}"
         required>

  {# First/Last Name #}
  <label for="firstName">First Name</label>
  <input type="text"
         name="firstName"
         id="firstName"
         value="{{ currentUser.firstName }}">

  <label for="lastName">Last Name</label>
  <input type="text"
         name="lastName"
         id="lastName"
         value="{{ currentUser.lastName }}">

  {# Photo Upload #}
  <label for="photo">Profile Photo</label>
  {% if currentUser.photo %}
    <img src="{{ currentUser.photo.url }}" width="100">
    <label>
      <input type="checkbox" name="deletePhoto" value="1">
      Delete photo
    </label>
  {% endif %}
  <input type="file" name="photo" id="photo" accept="image/*">

  {# Custom Fields #}
  <label for="bio">Bio</label>
  <textarea name="fields[bio]" id="bio">{{ currentUser.bio }}</textarea>

  {# Submit #}
  <button type="submit">Save Profile</button>
</form>

Checking User Status

In Templates

{# Check if user is logged in #}
{% if currentUser %}
  <p>Welcome, {{ currentUser.fullName }}!</p>
{% else %}
  <a href="{{ url('account/login') }}">Login</a>
{% endif %}

{# Check if user is in a group #}
{% if currentUser and currentUser.isInGroup('editors') %}
  <p>Editor tools available</p>
{% endif %}

{# Check if user has permission #}
{% if currentUser and currentUser.can('editEntries') %}
  <a href="{{ cpUrl('entries') }}">Edit Entries</a>
{% endif %}

{# Get user by ID #}
{% set user = craft.users().id(123).one() %}

{# Get user by email #}
{% set user = craft.users().email('user@example.com').one() %}

{# Get user by username #}
{% set user = craft.users().username('johndoe').one() %}

Require Login

{# Require user to be logged in #}
{% requireLogin %}

{# Or redirect to specific URL #}
{% requireLogin 'account/login' %}

{# Rest of template only visible to logged-in users #}
<h1>Members Area</h1>

Multi-Site User Access

Per-Site Permissions

Configure user access for multi-site installations:

// User can access specific sites
User: Jane Doe
Sites Allowed: [Site 1, Site 3]
Cannot access: Site 2, Site 4

In Templates

{# Check if user can access current site #}
{% if currentUser and currentUser.canAccessSite(currentSite) %}
  <p>Access granted for {{ currentSite.name }}</p>
{% endif %}

Security Best Practices

Password Requirements

// config/general.php
return [
    '*' => [
        'minPasswordLength' => 12,
        'maxInvalidLogins' => 5,
        'invalidLoginWindowDuration' => 3600, // 1 hour
        'cooldownDuration' => 300, // 5 minutes
        'requireMatchingUserAgentForSession' => true,
        'requireUserAgentAndIpForSession' => true,
    ],
];

Two-Factor Authentication

Install plugin:

composer require born05/craft-twofactorauthentication

Email Verification

Enable in: Settings → Users → Settings

  • Require email verification for new accounts
  • Validate email addresses

User Photo Upload Settings

// config/general.php
return [
    '*' => [
        'maxUploadFileSize' => '2M',
        'allowedFileExtensions' => ['jpg', 'jpeg', 'png', 'gif', 'webp'],
    ],
];

User Query Examples

{# Get all active users #}
{% set activeUsers = craft.users()
  .status('active')
  .all() %}

{# Get users by group #}
{% set editors = craft.users()
  .group('editors')
  .all() %}

{# Get users with custom field value #}
{% set newsletterUsers = craft.users()
  .newsletterOptIn(true)
  .all() %}

{# Search users #}
{% set results = craft.users()
  .search('john')
  .all() %}

{# Paginate users #}
{% paginate craft.users().limit(20) as pageInfo, users %}

{% for user in users %}
  <p>{{ user.fullName }}</p>
{% endfor %}

{{ pageInfo.prevUrl }}
{{ pageInfo.nextUrl }}

Next Steps

Resources