Users
mcg/filament-cms includes a Users screen for managing the application's user accounts and their access to the CMS panel. The screen is off unless a site turns it on. This document describes how to turn the screen on, what to configure before doing so, what the screen shows, the safeguards it enforces, how to add the CMS access toggle to an application's own user resource instead, and how CMS access relates to canAccessPanel().
When the Users screen appears
The screen is Mcg\FilamentCms\Resources\UserResource. It manages the model named by auth.providers.users.model in config/auth.php. When it is turned on, it appears in the panel sidebar as Users in the Content Manager navigation group at sort position 96, between Redirects (95) and API Tokens (97), and is served at {panel path}/cms-users.
The screen is registered on a panel only when that panel's FilamentCmsPlugin is configured with ->users(true). This applies both to the CMS's own panel (FilamentCmsPanelProvider) and to a panel of the application's that carries the plugin.
| Plugin configuration | Users screen |
|---|---|
| FilamentCmsPlugin::make(), the default, which FilamentCmsPanelProvider uses | Not registered |
| FilamentCmsPlugin::make()->users(true) | Registered, except when the user provider names no Eloquent model |
| FilamentCmsPlugin::make()->users(false) | Not registered |
When auth.providers.users.model does not name an Eloquent model, for example with Laravel's database user provider, the screen is never registered. FilamentCmsPlugin::shouldRegisterUsers() checks this, so ->users(true) does not register the screen in that case.
->users(true) registers the screen even when the panel already has a resource for the user model. The panel then has two screens that edit the same accounts: the application's at its own slug and the package's at cms-users.
When the application has more than one Filament panel, the Users list page shows the subheading "These are the site's user accounts. The same accounts may also be edited in the site's other admin panels." ListUsers::getSubheading() counts the panels returned by Filament::getPanels() and shows the subheading when there are two or more, on whichever panel the screen is registered.
Who can manage accounts once the screen is on
With cms.permissions.enabled set to false, which is the default, Abilities::allows() returns true for every ability, so every user who can open the panel can use every part of the Users screen. Such a user can change any account's email address and password and switch on CMS access for any account, including accounts that sign in to the application's other Filament panels or to its front end. Changing another account's password lets the person who changed it sign in as that account wherever the account is used, so on a site with other panels, any CMS editor can take over an administrator account of another panel. Before turning the screen on, set cms.permissions.enabled to true in config/cms.php and grant cms.users.view, cms.users.create, cms.users.update and cms.users.delete only to the accounts that should manage users. The steps are in permissions.md, and the four abilities are listed under Permissions.
Turning the screen on for a panel of the application's
- In the panel provider that registers the plugin (for example
app/Providers/Filament/AdminPanelProvider.php), change the plugin line to pass->users(true):
``php ->plugin(FilamentCmsPlugin::make()->users(true)) ``
- In a terminal at the site's root, run
php artisan route:clearif the application caches routes. If the filebootstrap/cache/filament/panels/<panel id>.phpexists (for examplebootstrap/cache/filament/panels/admin.php), runphp artisan filament:cache-componentsto rebuild it, orphp artisan filament:optimize-clearto delete it.
- In the browser, open the panel, for example
/admin. Users appears in the Content Manager navigation group, and/admin/cms-userslists the accounts.
Turning the screen on for the CMS's own panel
FilamentCmsPanelProvider is a class in the package and cannot be edited, so the setting goes in a subclass of it.
- In
app/Providers/Filament/CmsPanelProvider.php, create the subclass. An application that already registers its own subclass ofFilamentCmsPanelProvideradds theFilamentCmsPluginimport and the three lines that callusers(true)insidepanel()to that class instead, and skips step 2. The@varannotation tells static analysis thatgetPlugin(), which is declared to return Filament'sPlugininterface, returns aFilamentCmsPlugin, which has theusers()method.
```php <?php
namespace App\Providers\Filament;
use Filament\Panel; use Mcg\FilamentCms\FilamentCmsPanelProvider; use Mcg\FilamentCms\FilamentCmsPlugin;
class CmsPanelProvider extends FilamentCmsPanelProvider { public function panel(Panel $panel): Panel { $panel = parent::panel($panel);
/** @var FilamentCmsPlugin $plugin */ $plugin = $panel->getPlugin('filament-cms'); $plugin->users(true);
return $panel; } } ```
- In
bootstrap/providers.php, replace\Mcg\FilamentCms\FilamentCmsPanelProvider::classwithApp\Providers\Filament\CmsPanelProvider::class. Registering both would give two providers for the same panel id.
- In a terminal at the site's root, run the commands in step 2 of the previous section, with the CMS panel's id (
cmsby default).
- In the browser, open the CMS panel,
/cmsby default. Users appears in the Content Manager navigation group.
Turning the screen off again
Remove ->users(true), or pass ->users(false), and run the commands in step 2 of Turning the screen on for a panel of the application's.
The Filament component cache
When the Filament component cache is out of date, /admin/cms-users answers 404 in the browser while php artisan route:list --path=cms-users lists its three routes. Filament reads the cache only for web requests (HasComponents::hasCachedComponents() returns false when the application runs in the console), and while the cache exists, Panel::resources() registers nothing new, so a resource added after the cache was written has no routes in the browser. php artisan optimize rebuilds the cache, so a deploy that runs it picks up the change.
When the decision is made
FilamentCmsPlugin::register() runs when ->plugin() is called, which can be before ->users() is called on the same plugin object later in the panel's configuration. It therefore sets up an afterResolving(PanelRegistry::class) callback that calls FilamentCmsPlugin::registerUserResource(), which calls shouldRegisterUsers(). Filament builds each panel inside a resolving(PanelRegistry::class) callback, and Laravel runs every afterResolving callback after every resolving callback, so the decision is made after the whole configuration of every panel has run and before Filament's route file registers resource routes.
Which accounts the screen manages
CmsUsers::model() reads only auth.providers.users.model. It does not read the panel's authGuard() or the provider that guard uses. On a panel whose guard uses a different provider, for example a guard named admin whose provider is admins with the model App\Models\Admin, the Users screen lists and edits the users provider's model, App\Models\User, and not the accounts that sign in to that panel.
On such a panel, leave the Users screen off and manage the panel's accounts with the application's own resource.
The cms-users slug
The slug is cms-users rather than users so that ->users(true) can place the screen beside an application resource that already uses users. Two resources with the same slug on one panel share route names, and the second replaces the first.
The Users screen
The list
The list has four columns: Name, Email, CMS access (an icon) and Created. Name and email are searchable. Name, email and created date are sortable, and the list is sorted by name by default. The CMS access filter shows users with access, users without it, or all users. The CMS access column and filter appear only when the users table has the is_cms_admin column. The "New user" button opens the create page.
Creating and editing a user
| Field | Rules | Stored as |
|---|---|---|
| Name | Required, at most 255 characters | name |
| Required, a valid address, at most 255 characters, unique in the users table (an edit ignores the user's own row) | ||
| Password | Required on create; validated with Password::defaults(); must match Confirm password. Leaving both fields blank on edit keeps the current password | password, hashed with Hash::make() |
| Confirm password | Required when Password is filled | Not stored |
| CMS access | Shown only when the users table has iscmsadmin | iscmsadmin |
Both password fields can be revealed with the eye icon. On the edit page, the Password helper text reads "Leave both password fields blank to keep the current password." The stored hash is never loaded into the form. Password::defaults() is the application's own password policy, so an application that tightened it gets that policy and any other application gets Laravel's default minimum of eight characters.
The screen saves with forceFill() in CreateUser::handleRecordCreation() and EditUser::handleRecordUpdate(). A model that declares only name, email and password fillable, as Laravel 13's skeleton does, still stores is_cms_admin.
The CMS access helper text reads "Grants access to this panel through the HasCmsAccess trait. If the User model defines its own canAccessPanel(), that rule decides access instead." When the user model does not use HasCmsAccess, the toggle is still shown, and its helper text reads "The User model does not use the HasCmsAccess trait, so this setting does not grant access to this panel unless the model's own canAccessPanel() reads is_cms_admin."
Safeguards
The Users screen enforces these rules in its delete actions and in the edit page's save, not only in what the screen displays:
- A user cannot delete their own account. Their own row, and their own edit page, have no Delete action, and a delete of their own account is refused with "You cannot delete your own account."
- A user cannot change their own CMS access. On their own edit screen the CMS access toggle is disabled, and a value sent for it anyway is removed by
EditUser::mutateFormDataBeforeSave()before the save. - The last user with CMS access keeps it. Switching off CMS access for, or deleting, the only user whose
is_cms_adminis true is refused with "At least one user must keep CMS access." A refused save stores nothing. - Deleting asks for confirmation. The confirmation reads "Pages, versions and media this user created record only the user's id, so the user's name will no longer appear on them. This cannot be undone."
- Bulk delete checks each selected row. A row that rule 1 or rule 3 refuses is skipped and reported, with its reason, in Filament's partial-failure notification, and the other rows are deleted. The rows are checked in turn after each earlier deletion.
CmsUsers::deletionRefusal()applies rule 1 before rule 3, so selecting every user with CMS access has one of two outcomes:
- When your own account has CMS access, every selected row is deleted except your own, which rule 1 refuses. Your account keeps CMS access, so rule 3 never refuses any other row.
- When your own account has no CMS access, for example under a custom
canAccessPanel(), every selected row is deleted except the last one checked, which rule 3 refuses.
Rule 3 applies only when the users table has is_cms_admin. Without the column, panel access is granted by the application's own canAccessPanel(), which the package cannot read.
The Users screen does not edit roles or abilities, send invitations, or send password-reset links.
Adding CMS access to your own user resource
An application that manages its accounts with its own Filament resource for the user model can leave the Users screen off and add the CMS access toggle and its table column to that resource. Mcg\FilamentCms\Support\CmsUserFields provides both.
One-time setup
- In
app/Filament/Resources/Users/Schemas/UserForm.php(or your user resource'sform()method), add the toggle to the form's components:
```php use Mcg\FilamentCms\Support\CmsUserFields;
CmsUserFields::accessToggle(), ```
- In
app/Filament/Resources/Users/Tables/UsersTable.php(or your user resource'stable()method), add the column to the table's columns:
```php use Mcg\FilamentCms\Support\CmsUserFields;
CmsUserFields::accessColumn(), ```
- In
app/Models/User.php, addis_cms_adminto the model's fillable attributes. On a Laravel 13 model that uses the attribute:
``php #[Fillable(['name', 'email', 'password', 'is_cms_admin'])] ``
On a model that uses the property, add 'is_cms_admin' to $fillable. Filament's create and edit pages fill the model through mass assignment, which drops an attribute the model does not declare fillable without reporting an error.
- In the admin panel, on your user resource's edit screen, for example
/admin/users/{id}/editfor a resource with the default sluguserson a panel at/admin, switch on CMS access for an account and click "Save changes". The path follows the host panel's path and the resource's slug. Reopen the account. The toggle should still be on.
Both components render only when the users table has is_cms_admin.
The safeguards in Safeguards belong to the package's Users screen and are not added to your resource. CmsUsers::deletionRefusal($user) and CmsUsers::accessRemovalRefusal($user, $grantsAccess) in Mcg\FilamentCms\Support\CmsUsers return the refusal message, or null when the change may proceed, and can be called from your resource's own actions.
CMS access and canAccessPanel()
Filament lets a user into a panel when the user model's canAccessPanel() returns true. The HasCmsAccess trait implements it as return (bool) ($this->is_cms_admin ?? false);, and the CMS access toggle writes is_cms_admin. When the model defines its own canAccessPanel(), or does not use the trait, that method decides access, and the toggle changes access only if the method reads is_cms_admin. The installer offers the migration that adds the column; see installation.md.
Permissions
When cms.permissions.enabled is true, four abilities gate the screen:
| Ability | Constant | Covers |
|---|---|---|
| cms.users.view | USERSVIEW | Seeing the Users screen |
| cms.users.create | USERSCREATE | Creating a user |
| cms.users.update | USERSUPDATE | Editing a user, including the CMS access toggle |
| cms.users.delete | USERSDELETE | Deleting users, one at a time or in bulk |
When cms.permissions.enabled is false, which is the default, none of the four is checked, and every user who can open the panel can create, edit and delete every account and grant CMS access, as described in Who can manage accounts once the screen is on. The screen is off unless ->users(true) is set, so this applies only to a site that has turned it on.
See permissions.md for granting abilities.