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
- Roles & Permissions - Configure user groups and permissions
- Adding/Removing Users - Manage user accounts