Non-admin users are not able to access my plugin in the CP. With admin rights, everything is fine—the templates are accessible, but all the other users are getting 403.

I've tried to build the plugin based on Craft 3 best practices and used:

EVENT_REGISTER_CP_NAV_ITEMS - to register nav items

EVENT_REGISTER_CP_URL_RULES - to register url rules like this:

$event->rules['plugin-name'] = ['template' => 'plugin-handle/index'];

What am I missing?

Should I add / register something else in the plugin's init()?

The permission checkbox for the plugin doesn't even show up in User(Group) Permissions.

I'm not using controllers because routing to templates are working fine and seems to be enough. Is that a problem?


2 Answers 2


If your plugin has a CP section (specifically, the main Plugin class declares a $hasCpSection property, and it’s set to true), you should get an “Access My Plugin Name” option near the top of the permissions list—this might be enough to grant access to your plugin's controllers. See how this is enforced, here.

If you want to use more granular permissions, you'll have to check that box, plus anything you register via the UserPermissions::EVENT_REGISTER_PERMISSIONS event. Here's an example of that being done in a stripped-down main plugin class:


use Craft;

use craft\base\Plugin as BasePlugin;
use craft\events\RegisterUserPermissionsEvent;
use craft\services\UserPermissions;

use yii\base\Event;

class MyPlugin extends BasePlugin {
    // ...

    public function init()
            function (RegisterUserPermissionsEvent $event) {
                // Each "group" is just a key in the Permissions array, and you can have 
                $event->permissions[Craft::t('my-plugin', 'My Plugin Features')] = [
                    // You might have discrete feature permissions, or different ones for `access` vs `modify`:
                    'my-plugin-access-first-feature' => [
                        'label' => Craft::t('my-plugin', 'Can Access Feature A')
                        // You can also use the `nested` key here to group permissions!
                    'my-plugin-access-second-feature' => [
                        'label' => Craft::t('my-plugin', 'Can Access Feature B')

    // ...

In a controller action, you can call $this->requirePermission('my-plugin-access-first-feature'); to lock down access.

Also worth noting: Some plugins (like Commerce) use a few abstract controller definitions (i.e. BaseController, BaseCpController, BaseAdminController) to lock down entire bundles of functionality. If you're using this paradigm, you won't be able to easily relax permissions—only tighten—enforced by the parent Controller.

  • Thanks for your answer. I've tried both actually. $hasCpSection doesn't change anything, the plugin still doesn't show up in permissions. I've checked other plugins, they work as expected without this property.. Custom granular permissions are working when I declare them the same way like in your example but that doesn't solve the issue that non-admin users are not able to access at all.
    – peet86
    Jan 8, 2020 at 7:16
  • Wild, I can't think why this would be. 🤔 I'll keep this in the back of my mind and ping you if anything else occurs to me! Jan 9, 2020 at 3:27
  • You can either set hasCpSection in the Composer file or in the main plugin class (not both). However, re-installing the plugin via Composer is required if you set hasCpSection to true in hindsight.
    – Johannes
    May 1, 2020 at 16:23

The reason was:

"hasCpSection": true property was missing from the plugin's composer.json and I had to (composer) remove and require it again in order to see the permission checkmark.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.