Fork CMS is a PHP-based open-source CMS that stores users in a MySQL database. Backend users (editors and admins) are managed through the Fork CMS backend at /private/en/ (locale-prefixed). Fork CMS uses a group-based permission system where each user belongs to one or more groups with module-level access controls.
Adding Users via the Backend
Creating a New User
- Log in to the Fork CMS backend (e.g.,
https://your-site.com/private/en/) - Navigate to Settings > Users (or Users in the main navigation, depending on your version)
- Click Add user
- Fill in the Account tab:
- Email (used for login and notifications)
- Password (and confirmation)
- Display Name (shown in the backend and on content)
- Active: checked
- Under the Groups tab, select one or more groups:
- Admin -- Full backend access to all modules and settings
- Pages -- Access to the Pages module only
- Custom groups as configured
- Fill in optional Profile fields (avatar, biography, etc.)
- Click Add
Managing Groups and Permissions
Fork CMS defines permissions at the group level. Each group specifies which backend modules and actions are accessible:
- Go to Settings > Groups
- Click Add group or edit an existing group
- Configure module-level permissions:
Group Permission Structure:
├── Dashboard (view)
├── Pages (add, edit, delete)
├── Blog (add, edit, edit_own, delete, categories)
├── Search (manage, statistics)
├── Content Blocks (add, edit, delete)
├── FAQ (add, edit, delete, categories)
├── Location (add, edit, delete)
├── Tags (manage)
├── Profiles (manage, groups)
└── Settings (general, modules, themes, translations)
Each module action has a checkbox. Users inherit permissions from all groups they belong to (union of permissions).
Adding Users via Direct Database
For scripted or automated user creation:
-- Create a new backend user in Fork CMS
-- Fork CMS uses MD5 hashed passwords (legacy) or bcrypt (newer versions)
-- Step 1: Insert the user record
INSERT INTO users (email, password, active, is_god, date_added)
VALUES (
'jsmith@example.com',
-- For bcrypt: php -r "echo password_hash('Pass123!', PASSWORD_BCRYPT);"
'$2y$10$hashed_password_here',
'Y',
'N',
NOW()
);
SET @uid = LAST_INSERT_ID();
-- Step 2: Add user settings (display name, etc.)
INSERT INTO users_settings (user_id, name, value)
VALUES
(@uid, 'nickname', 's:10:"John Smith";'),
(@uid, 'date_format', 's:5:"d/m/Y";'),
(@uid, 'time_format', 's:5:"H:i:s";'),
(@uid, 'number_format', 's:12:"dot_nothing";'),
(@uid, 'avatar', 's:11:"no-avatar.gif";');
-- Step 3: Add user to a group
-- Get the admin group ID first
INSERT INTO users_groups (user_id, group_id)
SELECT @uid, id FROM groups WHERE name = 'Admin';
CLI User Creation Script
#!/bin/bash
# create-fork-user.sh -- Create a Fork CMS user from the command line
DB_NAME="fork_cms"
DB_USER="fork_user"
DB_PASS="db_password"
read -p "Email: " EMAIL
read -p "Display Name: " NICKNAME
read -s -p "Password: " PASSWORD
echo
read -p "Group (Admin/Pages): " GROUP
HASH=$(php -r "echo password_hash('$PASSWORD', PASSWORD_BCRYPT);")
mysql -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" <<SQL
INSERT INTO users (email, password, active, is_god, date_added)
VALUES ('$EMAIL', '$HASH', 'Y', 'N', NOW());
SET @uid = LAST_INSERT_ID();
INSERT INTO users_settings (user_id, name, value)
VALUES (@uid, 'nickname', 's:${#NICKNAME}:"$NICKNAME";');
INSERT INTO users_groups (user_id, group_id)
SELECT @uid, id FROM groups WHERE name = '$GROUP';
SQL
echo "User created: $EMAIL ($GROUP)"
Removing and Deactivating Users
Deactivating a User
- Go to Settings > Users
- Click Edit next to the user
- Uncheck the Active checkbox
- Click Save
Deactivated users cannot log in to the backend. Their content and attribution remain intact.
Deleting a User
- Go to Settings > Users
- Click the Delete icon next to the user
- Confirm the deletion
What Happens to Their Content
When you delete a Fork CMS user:
- Blog posts authored by the user remain published but the author reference may show as empty
- Pages are not tied to individual users in the same way -- page revisions store the user ID, but pages persist
- Content blocks created by the user remain active
- The
users_settingsandusers_groupsrecords are deleted along with the user record - Revision history retains references to the deleted user's ID, which resolves to "Unknown"
- Media uploads remain in the filesystem under
/src/Frontend/Files/
SQL-Based Deactivation
-- Deactivate a user
UPDATE users SET active = 'N' WHERE email = 'jsmith@example.com';
-- Delete a user and all associated records
SET @uid = (SELECT id FROM users WHERE email = 'jsmith@example.com');
DELETE FROM users_groups WHERE user_id = @uid;
DELETE FROM users_settings WHERE user_id = @uid;
DELETE FROM users WHERE id = @uid;
Bulk User Management
Export All Users
-- Export all backend users with their groups
SELECT u.id, u.email, u.active,
(SELECT value FROM users_settings WHERE user_id = u.id AND name = 'nickname') AS nickname,
GROUP_CONCAT(g.name) AS groups
FROM users u
LEFT JOIN users_groups ug ON u.id = ug.user_id
LEFT JOIN groups g ON ug.group_id = g.id
GROUP BY u.id
ORDER BY u.email;
Bulk Import Script
<?php
// bulk-import-fork-users.php
// Usage: php bulk-import-fork-users.php < users.csv
$pdo = new PDO(
'mysql:host=localhost;dbname=fork_cms',
'fork_user',
getenv('DB_PASSWORD')
);
$csv = array_map('str_getcsv', file('php://stdin'));
$header = array_shift($csv);
foreach ($csv as $row) {
$data = array_combine($header, $row);
// Check for existing user
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$data['email']]);
if ($stmt->fetch()) {
echo "SKIP: {$data['email']} (exists)\n";
continue;
}
$hash = password_hash($data['password'], PASSWORD_BCRYPT);
$pdo->prepare("INSERT INTO users (email, password, active, is_god, date_added)
VALUES (?, ?, 'Y', 'N', NOW())")
->execute([$data['email'], $hash]);
$uid = $pdo->lastInsertId();
$nickname = $data['name'];
$serialized = 's:' . strlen($nickname) . ':"' . $nickname . '";';
$pdo->prepare("INSERT INTO users_settings (user_id, name, value) VALUES (?, 'nickname', ?)")
->execute([$uid, $serialized]);
$pdo->prepare("INSERT INTO users_groups (user_id, group_id)
SELECT ?, id FROM groups WHERE name = ?")
->execute([$uid, $data['group']]);
echo "ADDED: {$data['email']} ({$data['group']})\n";
}
echo "Import complete.\n";
Authentication and Security
Fork CMS does not natively support LDAP or SAML SSO. Authentication is local, using the users table.
Security Configuration
Fork CMS security settings are in app/config/parameters.yml and the database:
# app/config/parameters.yml
parameters:
site.default_language: en
fork.debug_mode: false
fork.debug_email: admin@example.com
Brute-Force Protection
Fork CMS does not include built-in login rate limiting. Protect the backend with server-level configuration:
# .htaccess -- Rate limit the backend login
<Location /private>
<Limit POST>
# Use mod_evasive or mod_security for rate limiting
SetEnvIf Request_URI "/authentication" rate_limit
</Limit>
</Location>
# nginx -- Rate limit backend login
limit_req_zone $binary_remote_addr zone=fork_login:10m rate=5r/m;
location /private {
location ~ /authentication {
limit_req zone=fork_login burst=3;
# ... pass to PHP-FPM
}
}
Two-Factor Authentication
Fork CMS does not include built-in 2FA. To add it, implement a custom module or use a reverse proxy with 2FA (e.g., Cloudflare Access, Authelia):
# Example: Authelia protection for Fork CMS backend
# docker-compose.yml addition
services:
authelia:
image: authelia/authelia
volumes:
- ./authelia/config:/config
environment:
- TZ=America/Chicago
Offboarding Checklist
- Deactivate the user -- Set
active = 'N'to preserve content attribution - Remove from all groups -- Clear group memberships before deactivation
- Review blog posts -- Reassign authored blog posts if author attribution matters
- Check content blocks -- Verify no content blocks reference the departing user
- Clear sessions -- Delete the user's session data from the
sessionstable - Audit recent revisions -- Review page revision history for recent changes by the user
- Change shared credentials -- If the user knew the database password or had SSH access, rotate those credentials
- Update server access -- Remove their SSH keys and any IP allowlist entries