Skip to content
MCG Filament CMS Demo

Permissions

This document explains how the package decides which panel users may perform which actions inside the admin panel. It covers the cms.permissions config group, how to grant abilities through Laravel's Gate, the complete list of abilities the package checks, and what the permission system does not cover. It is written for the developer who configures access for a site's editors.

The package defines a set of named abilities and asks Laravel's Gate whether the signed-in user has each one. It does not ship roles, a permissions table, or a dependency on a permissions package. An application that already uses spatie/laravel-permission, a custom is_editor column, or no permission system at all can answer the package's questions with what it already has, and does not need to adopt a second system.

Panel access itself (whether a user can open the panel at all) is a separate check, made by canAccessPanel() on the User model. It is described in Installation.

Enabling permissions

The permission system is off by default:

config/cms.php PHP

'permissions' => [
    'enabled' => false,
    'super_admin' => null,
],

With enabled set to false, the panel behaves as it did before the permission system existed. That is the default so that upgrading the package never removes an existing user's access to their own site.

Enabling permissions without losing access

Grant a super-admin ability first, then define narrower abilities. The two code blocks below show steps 1 and 2 in order.

  1. In config/cms.php, enable permissions and name a super-admin ability.
  2. In app/Providers/AppServiceProvider.php, define that ability in the boot() method.
  3. In the same boot() method, define the specific abilities for other users, as shown under Granting abilities.

config/cms.php PHP

'permissions' => ['enabled' => true, 'super_admin' => 'cms.admin'],

app/Providers/AppServiceProvider.php PHP

// AppServiceProvider::boot()
Gate::define('cms.admin', fn (User $user) => $user->is_cms_admin);

Abilities::allows() (src/Support/Abilities.php) checks the super_admin ability before the specific ability. A user who has the super-admin ability is granted every ability, whatever the specific ability's definition returns.

The user is resolved from the panel's guard (Filament::auth()), falling back to the default guard. When no user is signed in, every check is refused.

Granting abilities

Abilities are ordinary Gate abilities. Define them anywhere Laravel's Gate reads definitions: a service provider, a policy, or the wildcard bindings of spatie/laravel-permission.

PHP

use Illuminate\Support\Facades\Gate;
use Mcg\FilamentCms\Support\Abilities;

Gate::define(Abilities::CONTENT_VIEW,   fn (User $user) => $user->isStaff());
Gate::define(Abilities::CONTENT_UPDATE, fn (User $user) => $user->isStaff());
Gate::define(Abilities::CONTENT_PUBLISH, fn (User $user) => $user->isEditor());
Gate::define(Abilities::SUBMISSIONS_VIEW, fn (User $user) => $user->isAdmin());

With spatie/laravel-permission, each ability name becomes a permission of the same name:

PHP

foreach (Abilities::all() as $ability) {
    Permission::findOrCreate($ability);
}

Abilities::all() returns every ability the package checks, read from the constants of the Abilities class. The same list is the input for a role editor.

The abilities

Each ability is a constant on Mcg\FilamentCms\Support\Abilities.

Content

Ability Constant Covers
cms.content.view CONTENT_VIEW Seeing the content section and opening a page
cms.content.create CONTENT_CREATE Creating pages, snippets and custom content types
cms.content.update CONTENT_UPDATE Saving a draft
cms.content.delete CONTENT_DELETE Deleting
cms.content.publish CONTENT_PUBLISH Publishing and unpublishing
cms.content.restore-version CONTENT_RESTORE_VERSION Rolling a page back to an earlier version

Publishing is a separate ability from editing. Writing a draft and deciding that the public site shows it are often different responsibilities, held by different people. A user with CONTENT_UPDATE but not CONTENT_PUBLISH gets a working editor with no Publish button.

Scoping content abilities by content type

A content ability receives the Content record when one exists, and the content type key (a string such as page) where there is no record. One ability definition can therefore treat each content type differently, without a separate ability per type:

PHP

Gate::define(Abilities::CONTENT_UPDATE, function (User $user, Content|string $subject) {
    $type = $subject instanceof Content ? $subject->type : $subject;

    return $type === 'page' || $user->isAdmin();
});

Media, menus, tags, redirects and forms

Each of these five sections has four abilities: view, create, update and delete.

Section Abilities
Media cms.media.view, cms.media.create, cms.media.update, cms.media.delete
Menus cms.menus.view, cms.menus.create, cms.menus.update, cms.menus.delete
Tags cms.tags.view, cms.tags.create, cms.tags.update, cms.tags.delete
Redirects cms.redirects.view, cms.redirects.create, cms.redirects.update, cms.redirects.delete
Forms cms.forms.view, cms.forms.create, cms.forms.update, cms.forms.delete

The constants follow the pattern MEDIA_VIEW, MENUS_CREATE, TAGS_UPDATE, REDIRECTS_DELETE, FORMS_VIEW. Denying a section's view ability removes that section from the panel navigation.

Form submissions

Ability Constant Covers
cms.submissions.view SUBMISSIONS_VIEW Seeing the submissions inbox at all
cms.submissions.update SUBMISSIONS_UPDATE Changing a submission's status and internal notes
cms.submissions.delete SUBMISSIONS_DELETE Deleting submissions, one at a time or in bulk
cms.submissions.export SUBMISSIONS_EXPORT Downloading submissions as a CSV file

The submission abilities are separate from the form abilities. Building a contact form is a content task. Reading what people sent through it means reading their names, addresses and attachments, and the two tasks are often assigned to different people. Denying SUBMISSIONS_VIEW hides the whole inbox rather than disabling its buttons.

Export has its own ability because reading one submission on screen and downloading every submission as a file give a user different amounts of personal data.

Site settings

Ability Constant Covers
cms.settings.view SETTINGS_VIEW Opening the Site settings page
cms.settings.update SETTINGS_UPDATE Saving the page, and the Send a test message action in its Notifications section

SETTINGS_UPDATE is checked inside SiteSettingsPage's save method and test method, not only on the buttons that call them. A Livewire method can be called by anyone who can load the component, whether or not the interface shows a button for it, so a check on the button alone would not prevent the save.

API tokens

Ability Constant Covers
cms.tokens.view-all TOKENS_VIEW_ALL Seeing and revoking API tokens that belong to other users on the API Tokens screen

A user always sees their own tokens; this ability adds every other user's tokens to the list, with the owner's name. The list shows each token's label, owner and last-used time. Sanctum stores only a hash of each token, so no token value is shown. The panel offers no way to create a token for another user, because a token carries its holder's authority and creating one for someone else would grant that authority to the creator.

The raw HTML block

cms.blocks.raw_html_ability is a separate setting, older than the permission system. It names a Gate ability that a user must have to place the html block, whose output is rendered without escaping. It applies whether permissions.enabled is true or false. See Blocks.

What permissions do not cover

Two further areas are outside the permission system:

  1. The public site. These abilities apply to the panel only. Published content is public. There is no per-page audience control, and enabling permissions does not add one.
  2. Ownership. The package has no ownership model. A rule such as "editors may change only their own pages" requires a Gate definition that inspects the record. The created_by column on each version supports this:

PHP

Gate::define(Abilities::CONTENT_UPDATE, fn (User $user, Content|string $subject) =>
    $user->isAdmin() || (
        $subject instanceof Content
        && $subject->draftVersion?->created_by === $user->id
    ));