Even though I have years of software development experience, I am new to WP & PHP development, so please bear with me.

After reading through the plugin manual, I started with a WP plugin boiler plate, made all the required field and name changes, added a custom capability, and, as a first real functionality, tried to add a simple admin interface for the plugin like so:


class Plugin_Name_Admin {

    // some code ommitted, the actual code uses my plugin's name, slugs, and fields

    public function init() {
        add_action('admin_menu', array($this, 'add_plugin_admin_menu'));

    function add_plugin_admin_menu() {
                'Plugin Name',
                'Plugin Menu',
                array( $this, 'admin_page' ),
    function admin_page() {
        if ( current_user_can( PLUGIN_USER_CAPABILITY ) ) {
            require_once plugin_dir( __FILE__ ) . 'partials/plugin-name-admin-display.php';

The menu shows up for the users that have the assigned capability and the URL looks like http://wp.localhost/wp-admin/admin.php?page=plugin-name-admin but neither an administrator nor another user with the PLUGIN_USER_CAPABILITY can access that page. The error message is "Sorry, you are not allowed to access this page." and the error occurs in wp-admin/includes/plugin.php in the function user_can_access_admin_page where WP checks the following:

        if ( ! isset( $_registered_pages[ $hookname ] ) ) {
            return false;

The hookname it uses is admin_page_plugin-name-admin-admin.

What am I missing?

Also, any pointers on how to properly debug WP code in PhpStorm would be greatly appreciated. So far, I've just been muddling around with added error messages.

EDIT: The Plugin_Name_Admin class is instantiated in the boilerplate code's main class in the define_admin_hooks function, which also adds the hook for the init function:

    private function define_admin_hooks() {

        $plugin_admin = new Plugin_Name_Admin( $this->get_plugin_name(), $this->get_version() );

        $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles' );
        $this->loader->add_action( 'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts' );
        $this->loader->add_action( 'admin_init', $plugin_admin, 'init' );


This function is called from the constructor of the main class and uses the boiler plate's loader approach to create hooks.

As I mention in my answer below, changing the call to add_plugin_admin_menu from a hook for admin_menu to a direct call solved the permission error.

EDIT2: I got PhpStorm to work with XDebug using this write-up.

  • can you enable the logs and get a full error? might give you a better idea.
    – abestrad
    Commented Feb 3, 2022 at 17:29
  • 1
    Where and when is Plugin_Name_Admin created? Your question only shows the definition of the class, not how it's used/created/instantiated, when you do it matters, so does where. Also what is the value of PLUGIN_USER_CAPABILITY and where does it come from? Have you eliminated this as a possibility by using a known capability that comes in standard WordPress installs rather than a custom capability? e.g. manage_options which administrators have would provide a test case for that theory
    – Tom J Nowell
    Commented Feb 3, 2022 at 17:39
  • I had the WP_DEBUG log enabled but it remained empty. The WP code doesn't seem to contain any debug or error generating lines either. As for where Plugin_Name_Admin is created, I had assumed some familiarity with the boilerplate code but I'll edit the question to make it clearer. PLUGIN_USER_CAPABILITY is, as stated, a custom capability used as a string constant. manage_options would not give me a menu for either the administrator nor any other user.
    – helgew
    Commented Feb 3, 2022 at 18:09
  • Just looked at the source code of WP Plugin Boilerplate and I don't like it at all (my personal opinion). It uses a lot of things I consider bad practice and would block if anyone of my colleagues tried to do. E.g. "class-X.php" filenames manually required, instead of using PSR-4 autoloading. Also the way classes are used makes it a testing nightmare as well as abstracts some important parts heavily away. I can't find a better boilerplate right now, but being a fan of Roots.io work I recommend checking Soil source code.
    – kero
    Commented Feb 4, 2022 at 9:55

1 Answer 1


Well, as it usually goes, after banging my head on this for hours yesterday and then finally deciding to ask for help, I was able to solve it within a few minutes. Here is what I needed to change:

    public function init() {
        // add_action('admin_menu', array($this, 'add_plugin_admin_menu'));
        $this-> add_plugin_admin_menu();

Clearly, I have much to learn about the call chain in WP.

  • I guess that means admin_menu is called before admin_init. You could probably also just add_action('admin_menu', in the class constructor.
    – Rup
    Commented Feb 4, 2022 at 13:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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