ProcessWire treats users as pages in its page tree, stored under /processwire/access/users/. This page-based architecture means user management follows the same patterns as content management -- you can use the admin UI, the API, or template files to create, modify, and delete users.
How ProcessWire User Management Works
ProcessWire's user system is built on three core concepts:
- Users -- Pages using the
usertemplate, stored under the users parent page - Roles -- Pages using the
roletemplate, stored under/processwire/access/roles/ - Permissions -- Pages using the
permissiontemplate, stored under/processwire/access/permissions/
Every user must have at least the guest role. The superuser role grants unrestricted access. Custom roles combine any set of permissions to create fine-grained access levels.
Adding Users via Admin UI
- Log in to the ProcessWire admin at
https://your-site.com/processwire/ - Navigate to Access > Users in the top menu
- Click Add New in the page tree or use the Add New User button
- Fill in the required fields:
- Name (username for login, lowercase, no spaces -- becomes the page name)
- Email (used for password resets)
- Password (minimum 6 characters by default, configurable)
- Under Roles, check the roles to assign:
guest(assigned automatically)superuser(full admin access)- Any custom roles you have created
- Click Save
The user can immediately log in at https://your-site.com/processwire/ with their credentials.
Adding Users via the ProcessWire API
ProcessWire's API makes programmatic user creation straightforward:
<?php
// Create a new user via ProcessWire API
$u = new User();
$u->of(false); // Turn off output formatting
$u->name = 'jdeveloper';
$u->pass = 'SecurePass123!';
$u->email = 'jane@company.com';
$u->addRole('editor'); // Add a custom role
$u->save();
echo "Created user: {$u->name} (ID: {$u->id})";
Using the $users API variable:
<?php
// Alternative: using the $users API
$u = $users->add('jdeveloper');
$u->of(false);
$u->pass = 'SecurePass123!';
$u->email = 'jane@company.com';
$u->addRole('editor');
$u->save();
With custom fields (if your user template has additional fields):
<?php
$u = new User();
$u->of(false);
$u->name = 'jdeveloper';
$u->pass = 'SecurePass123!';
$u->email = 'jane@company.com';
$u->title = 'Jane Developer'; // Display name
$u->department = 'Engineering'; // Custom field
$u->phone = '555-0100'; // Custom field
$u->addRole('editor');
$u->addRole('page-editor');
$u->save();
Bulk User Management
Bulk Import via API Script
Create a template file or module for bulk user imports:
<?php
// bulk-import-users.php -- run via ProcessWire bootstrap
// Place in site root and access via CLI: php bulk-import-users.php
include('./index.php');
$csvFile = './user-import.csv';
$handle = fopen($csvFile, 'r');
$header = fgetcsv($handle); // name,email,password,role
$created = 0;
$skipped = 0;
while (($row = fgetcsv($handle)) !== false) {
$data = array_combine($header, $row);
// Skip if user already exists
if ($users->get("name={$data['name']}")->id) {
echo "Skipped (exists): {$data['name']}\n";
$skipped++;
continue;
}
$u = new User();
$u->of(false);
$u->name = $data['name'];
$u->pass = $data['password'];
$u->email = $data['email'];
$u->addRole($data['role']);
$u->save();
echo "Created: {$data['name']} with role {$data['role']}\n";
$created++;
}
fclose($handle);
echo "\nDone. Created: $created, Skipped: $skipped\n";
Bulk Role Assignment
<?php
// Assign a new role to all users who have the 'editor' role
$editors = $users->find("roles=editor");
foreach ($editors as $editor) {
$editor->of(false);
$editor->addRole('page-publisher');
$editor->save();
echo "Added page-publisher to: {$editor->name}\n";
}
echo "Updated {$editors->count()} users.\n";
Bulk Deactivation Script
<?php
// Deactivate users who haven't logged in within 90 days
$cutoff = time() - (90 * 86400);
$staleUsers = $users->find("login_timestamp<$cutoff, roles!=superuser");
foreach ($staleUsers as $u) {
$u->of(false);
$u->status = Page::statusUnpublished; // Effectively disables login
$u->save();
echo "Deactivated: {$u->name} (last login: " . date('Y-m-d', $u->login_timestamp) . ")\n";
}
Removing and Deactivating Users
Deactivation (Recommended)
Since users are pages, you can unpublish them to block login without deleting:
Via Admin UI:
- Navigate to Access > Users
- Click on the user
- In the Settings tab, change Status to Unpublished
- Click Save
Via API:
<?php
$u = $users->get('jdeveloper');
$u->of(false);
$u->status = Page::statusUnpublished;
$u->save();
echo "Deactivated: {$u->name}";
Unpublished users cannot log in. All their content and page edit history remains intact.
Removing Roles Without Deleting
<?php
$u = $users->get('jdeveloper');
$u->of(false);
$u->removeRole('editor');
$u->removeRole('page-editor');
// User retains 'guest' role only -- can log in but has no permissions
$u->save();
Permanent Deletion
Via Admin UI:
- Navigate to Access > Users
- Click on the user
- In the Settings tab, click Delete (or move to Trash)
- Confirm deletion
Via API:
<?php
$u = $users->get('jdeveloper');
if ($u->id && !$u->isSuperuser()) {
$users->delete($u);
echo "Deleted user: jdeveloper";
}
What happens to their content:
- Pages created by the deleted user retain the
created_users_idfield pointing to the now-nonexistent user ID - Pages modified by the deleted user retain
modified_users_idreferences - The admin page edit history (in the
pages_accesslog) keeps the user ID as a number - File and image uploads remain in
/site/assets/files/regardless of user deletion - Comments or forum posts (if using community modules) retain the user reference but display as "Unknown User"
Reassign Content Before Deletion
<?php
$oldUser = $users->get('departing-user');
$newUser = $users->get('replacement-user');
// Find all pages created by the departing user
$pages = $pages->find("created_users_id={$oldUser->id}");
foreach ($pages as $p) {
$p->of(false);
$p->created_users_id = $newUser->id;
$p->save();
}
echo "Reassigned {$pages->count()} pages from {$oldUser->name} to {$newUser->name}\n";
SSO and External Authentication
ProcessWire does not include built-in LDAP or SAML support, but its module system allows integration:
LoginRegisterPro Module (Third-Party)
The LoginRegisterPro module supports external authentication:
- Social login (Google, Facebook, Twitter)
- Two-factor authentication
- Custom login forms with CSRF protection
Custom LDAP Authentication Module
<?php
// site/modules/LdapAuth/LdapAuth.module
class LdapAuth extends WireData implements Module {
public function init() {
$this->addHookBefore('Session::authenticate', $this, 'ldapAuthenticate');
}
public function ldapAuthenticate(HookEvent $event) {
$user = $event->arguments(0);
$pass = $event->arguments(1);
$ldapConn = ldap_connect('ldap://ldap.company.com');
ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
$dn = "uid={$user->name},ou=People,dc=company,dc=com";
if (@ldap_bind($ldapConn, $dn, $pass)) {
// LDAP auth succeeded -- ensure PW user exists
if (!$user->id) {
$attrs = $this->getLdapAttrs($ldapConn, $dn);
$u = new User();
$u->of(false);
$u->name = $user->name;
$u->email = $attrs['mail'];
$u->pass = $pass;
$u->addRole('editor');
$u->save();
}
$event->return = true;
$event->replace = true;
}
ldap_unbind($ldapConn);
}
}
Access Audit Checklist
- Review all users under Access > Users, checking roles and last login dates
- Use
$users->find("roles=superuser")to audit who has full admin access - Check for users with status
Unpublishedthat should be permanently deleted - Verify that the
guestuser has no roles beyondguest - Review custom user template fields for any sensitive data that should be encrypted
- Audit the
session_login_throttletable for brute-force attempts - Document all user provisioning in your change management system