Comprehensive guide to adding, inviting, managing, suspending, and deleting user accounts in Craft CMS through the Control Panel and programmatically.
Adding Users via Control Panel
Method 1: Create User Directly
- Navigate to Users in the Control Panel
- Click + New user button
- Fill in user details:
- Email - Required, must be unique
- Username - Optional, auto-generated from email if blank
- First Name / Last Name - Optional
- Password - Set initial password or send activation email
- Photo - Optional profile picture
- User Groups - Assign to one or more groups
- Custom Fields - Fill in any custom user fields
- Click Create user
Method 2: Send Activation Email
For users who should set their own password:
- Create new user as above
- Enable Send an activation email
- User receives email with link to set password
- User activates account and sets password
User Status Options
When creating users:
- Active - User can log in immediately
- Pending - Awaiting activation
- Suspended - Cannot log in
- Locked - Locked due to failed login attempts
Adding Users Programmatically
Create User via PHP
<?php
use craft\elements\User;
// Create new user
$user = new User();
// Set user properties
$user->username = 'johndoe';
$user->email = 'john@example.com';
$user->firstName = 'John';
$user->lastName = 'Doe';
// Set password
$user->newPassword = 'SecurePassword123!';
// Assign to groups
$user->setGroupIds([1, 3]); // Group IDs
// Set custom field values
$user->setFieldValues([
'bio' => 'Software developer and writer',
'company' => 'Acme Inc',
'newsletterOptIn' => true,
]);
// Save user
if (Craft::$app->elements->saveElement($user)) {
echo 'User created successfully';
} else {
print_r($user->getErrors());
}
Create User via Twig (Front-End Registration)
{# 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
minlength="12">
{# 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="fields-bio">Bio</label>
<textarea name="fields[bio]" id="fields-bio">{{ user.bio ?? '' }}</textarea>
<label>
<input type="checkbox" name="fields[newsletterOptIn]" value="1">
Subscribe to newsletter
</label>
{# Submit #}
<button type="submit">Create Account</button>
</form>
{# Display validation errors #}
{% if user is defined and user.hasErrors() %}
<div class="errors">
<h3>Please fix the following errors:</h3>
<ul>
{% for attribute, errors in user.getErrors() %}
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
Inviting Users
Send Invitation Email
<?php
use craft\elements\User;
// Create user
$user = new User();
$user->username = 'janedoe';
$user->email = 'jane@example.com';
$user->firstName = 'Jane';
$user->lastName = 'Doe';
$user->pending = true; // Mark as pending
// Save user
if (Craft::$app->elements->saveElement($user)) {
// Send activation email
Craft::$app->users->sendActivationEmail($user);
echo 'Invitation sent to ' . $user->email;
}
Custom Invitation Template
Create custom activation email template:
{# templates/_emails/user-activation.twig #}
Hi {{ user.friendlyName }},
Welcome to {{ siteName }}! Please click the link below to activate your account and set your password:
{{ activationUrl }}
This link will expire in 24 hours.
If you did not request this account, please ignore this email.
Best regards,
The {{ siteName }} Team
Configure in config/app.php:
return [
'components' => [
'mailer' => [
'messageConfig' => [
'from' => ['noreply@example.com' => 'Site Name'],
],
'templates' => [
'activate-account' => [
'subject' => 'Activate your {{ siteName }} account',
'htmlBody' => '_emails/user-activation.twig',
],
],
],
],
];
Bulk User Import
Import Users from CSV
<?php
use craft\elements\User;
use craft\helpers\StringHelper;
// Read CSV file
$csvFile = '/path/to/users.csv';
$rows = array_map('str_getcsv', file($csvFile));
$header = array_shift($rows);
$successCount = 0;
$errorCount = 0;
foreach ($rows as $row) {
$data = array_combine($header, $row);
// Create user
$user = new User();
$user->email = $data['email'];
$user->username = $data['username'] ?? StringHelper::toLowerCase($data['email']);
$user->firstName = $data['first_name'] ?? '';
$user->lastName = $data['last_name'] ?? '';
$user->pending = true;
// Assign to default group
$user->setGroupIds([1]); // "Members" group
// Save user
if (Craft::$app->elements->saveElement($user)) {
// Send activation email
Craft::$app->users->sendActivationEmail($user);
$successCount++;
} else {
echo "Error importing {$data['email']}: " . implode(', ', $user->getFirstErrors()) . "\n";
$errorCount++;
}
}
echo "Import complete: {$successCount} successful, {$errorCount} failed\n";
CSV format:
email,username,first_name,last_name
john@example.com,johndoe,John,Doe
jane@example.com,janedoe,Jane,Doe
Editing Users
Update User via Control Panel
- Navigate to Users
- Click on user to edit
- Modify user details
- Update user groups
- Change custom field values
- Click Save
Update User Programmatically
<?php
use craft\elements\User;
// Get user by ID
$user = User::find()->id(123)->one();
if ($user) {
// Update properties
$user->firstName = 'Updated';
$user->lastName = 'Name';
// Update custom fields
$user->setFieldValue('bio', 'Updated bio text');
$user->setFieldValue('company', 'New Company');
// Add to additional group
$currentGroups = $user->getGroups()->ids();
$currentGroups[] = 5; // Add group ID 5
$user->setGroupIds($currentGroups);
// Save changes
if (Craft::$app->elements->saveElement($user)) {
echo 'User updated successfully';
} else {
print_r($user->getErrors());
}
}
User Self-Update (Front-End)
{# 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>
{# 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.getUrl({ width: 100 }) }}" width="100">
<label>
<input type="checkbox" name="deletePhoto" value="1">
Delete current photo
</label>
{% endif %}
<input type="file" name="photo" id="photo" accept="image/*">
{# Custom Fields #}
<label for="fields-bio">Bio</label>
<textarea name="fields[bio]" id="fields-bio">{{ currentUser.bio }}</textarea>
<label for="fields-company">Company</label>
<input type="text"
name="fields[company]"
id="fields-company"
value="{{ currentUser.company }}">
<label>
<input type="checkbox"
name="fields[newsletterOptIn]"
value="1"
{{ currentUser.newsletterOptIn ? 'checked' }}>
Receive newsletter
</label>
<button type="submit">Save Changes</button>
</form>
{# Success message #}
{% if craft.app.session.hasFlash('notice') %}
<div class="success">
{{ craft.app.session.getFlash('notice') }}
</div>
{% endif %}
{# Error messages #}
{% if currentUser.hasErrors() %}
<div class="errors">
<ul>
{% for error in currentUser.getErrors() %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
Suspending Users
Suspend User via Control Panel
- Navigate to Users
- Click on user
- Change Status to Suspended
- Click Save
Suspend User Programmatically
<?php
use craft\elements\User;
// Get user
$user = User::find()->id(123)->one();
if ($user) {
// Suspend user
$user->suspended = true;
// Save
if (Craft::$app->elements->saveElement($user)) {
echo 'User suspended';
// Optionally log out user immediately
Craft::$app->users->destroyUserSession($user);
}
}
Unsuspend User
<?php
// Unsuspend user
$user->suspended = false;
if (Craft::$app->elements->saveElement($user)) {
echo 'User unsuspended';
// Optionally send notification
$message = Craft::$app->mailer->compose()
->setTo($user->email)
->setSubject('Your account has been reactivated')
->setHtmlBody('Your account access has been restored.')
->send();
}
Deleting Users
Delete User via Control Panel
- Navigate to Users
- Select user(s) to delete
- Click Delete button
- Confirm deletion
- Choose what to do with their content:
- Delete their content - Permanently remove
- Transfer to another user - Reassign content
- Leave content as-is - Keep content, remove authorship
Soft Delete (Recommended)
Soft delete keeps user data but prevents login:
<?php
use craft\elements\User;
// Get user
$user = User::find()->id(123)->one();
if ($user) {
// Method 1: Suspend user
$user->suspended = true;
Craft::$app->elements->saveElement($user);
// Method 2: Set to inactive
$user->enabled = false;
Craft::$app->elements->saveElement($user);
// Method 3: Mark as archived (Craft 4+)
$user->archived = true;
Craft::$app->elements->saveElement($user);
}
Hard Delete
Permanently delete user and optionally transfer content:
<?php
use craft\elements\User;
// Get user
$user = User::find()->id(123)->one();
if ($user) {
// Option 1: Delete user and their content
$deleted = Craft::$app->elements->deleteElement($user);
// Option 2: Transfer content to another user first
$transferToUser = User::find()->id(456)->one();
if ($transferToUser) {
// Transfer authored entries
$entries = craft\elements\Entry::find()
->authorId($user->id)
->all();
foreach ($entries as $entry) {
$entry->authorId = $transferToUser->id;
Craft::$app->elements->saveElement($entry);
}
// Transfer uploaded assets
$assets = craft\elements\Asset::find()
->uploaderId($user->id)
->all();
foreach ($assets as $asset) {
$asset->uploaderId = $transferToUser->id;
Craft::$app->elements->saveElement($asset);
}
// Then delete user
Craft::$app->elements->deleteElement($user);
}
}
Bulk User Management
Bulk Suspend Users
<?php
use craft\elements\User;
// Get users to suspend
$users = User::find()
->lastLoginDate('< ' . date('Y-m-d', strtotime('-1 year')))
->all();
foreach ($users as $user) {
$user->suspended = true;
if (Craft::$app->elements->saveElement($user)) {
echo "Suspended: {$user->email}\n";
}
}
Bulk Delete Inactive Users
<?php
use craft\elements\User;
// Get users who never logged in and are older than 30 days
$users = User::find()
->lastLoginDate(':empty:')
->dateCreated('< ' . date('Y-m-d', strtotime('-30 days')))
->all();
foreach ($users as $user) {
// Don't delete admins
if (!$user->admin) {
if (Craft::$app->elements->deleteElement($user)) {
echo "Deleted: {$user->email}\n";
}
}
}
Bulk Group Assignment
<?php
use craft\elements\User;
// Add all users with specific custom field to a group
$users = User::find()
->newsletterOptIn(true)
->all();
$newsletterGroupId = 5;
foreach ($users as $user) {
$currentGroups = $user->getGroups()->ids();
if (!in_array($newsletterGroupId, $currentGroups)) {
$currentGroups[] = $newsletterGroupId;
$user->setGroupIds($currentGroups);
if (Craft::$app->elements->saveElement($user)) {
echo "Added {$user->email} to newsletter group\n";
}
}
}
User Cleanup Tasks
Clean Up Pending Users
Remove users who never activated their account:
<?php
use craft\elements\User;
// Delete pending users older than 7 days
$pendingUsers = User::find()
->status(User::STATUS_PENDING)
->dateCreated('< ' . date('Y-m-d', strtotime('-7 days')))
->all();
foreach ($pendingUsers as $user) {
if (Craft::$app->elements->deleteElement($user)) {
echo "Deleted pending user: {$user->email}\n";
}
}
Lock Inactive Users
Lock users who haven't logged in for a long time:
<?php
use craft\elements\User;
// Lock users inactive for 6 months
$inactiveUsers = User::find()
->lastLoginDate('< ' . date('Y-m-d', strtotime('-6 months')))
->status(User::STATUS_ACTIVE)
->all();
foreach ($inactiveUsers as $user) {
if (!$user->admin) {
$user->locked = true;
if (Craft::$app->elements->saveElement($user)) {
// Send notification
$message = Craft::$app->mailer->compose()
->setTo($user->email)
->setSubject('Account locked due to inactivity')
->setHtmlBody('Your account has been locked. Contact us to reactivate.')
->send();
echo "Locked: {$user->email}\n";
}
}
}
Best Practices
1. Always Validate User Input
// Validate before saving
if (!$user->validate()) {
foreach ($user->getErrors() as $attribute => $errors) {
echo "{$attribute}: " . implode(', ', $errors) . "\n";
}
}
2. Send Notifications
Notify users of account changes:
// User suspended
Craft::$app->mailer->compose()
->setTo($user->email)
->setSubject('Account suspended')
->setHtmlBody('Your account has been suspended...')
->send();
3. Log User Changes
use craft\helpers\App;
// Log user creation
Craft::info(
"User created: {$user->email} by " . Craft::$app->user->identity->email,
'users'
);
// Log user deletion
Craft::info(
"User deleted: {$user->email} by " . Craft::$app->user->identity->email,
'users'
);
4. Handle Content Ownership
Always consider what to do with user content:
- Transfer entries to another author
- Transfer assets to another uploader
- Archive content
- Delete content
5. Maintain Audit Trail
Keep records of user management actions:
// Store in custom table or log file
$auditLog = [
'action' => 'user_deleted',
'user_id' => $user->id,
'user_email' => $user->email,
'performed_by' => Craft::$app->user->identity->id,
'timestamp' => date('Y-m-d H:i:s'),
'reason' => 'Inactive for 1 year',
];
Console Commands
Create User via CLI
# Create user interactively
php craft users/create
# Create user with parameters
php craft users/create \
--email="user@example.com" \
--username="username" \
--password="SecurePassword123!" \
--admin
# Send activation email
php craft users/send-activation-email user@example.com
Delete User via CLI
# Delete user by email
php craft users/delete user@example.com
# Delete user by ID
php craft users/delete --user-id=123
Next Steps
- Roles & Permissions - Configure user permissions
- User Management Overview - User management fundamentals