“Error: Options Page Not Found” on Settings Page Submission for an OOP Plugin

“Error: Options Page Not Found” Bug

This is a known issue in the WP Settings API. There was a ticket opened years ago, and it was marked as solved — but the bug persists in the latest versions of WordPress. This is what the (now removed) Codex page said about this:

The “Error: options page not found.” problem (including a solution and
explanation):

The problem then is, that the ‘whitelist_options’ filter
hasn’t got the right index for your data. It gets applied on
options.php#98 (WP 3.4).

register_settings() adds your data to the
global $new_whitelist_options. This then gets merged with the global
$whitelist_options inside the option_update_filter() (resp.
add_option_whitelist()) callback(s). Those callbacks add your data to the global $new_whitelist_options with the $option_group as index.
When you encounter “Error: options page not found.” it means your
index hasn’t been recognized. The misleading thing is that the first
argument is used as index and named $options_group, when the actual check in options.php#112 happens against $options_page, which is the $hook_suffix, which you get as @return value from add_submenu_page().

In short, an easy solution is to make $option_group match $option_name. Another cause for this error is having an invalid value for $page parameter when calling either add_settings_section( $id, $title, $callback, $page ) or add_settings_field( $id, $title, $callback, $page, $section, $args ).

Hint: $page should match $menu_slug from Function Reference/add theme page.

Simple Fix

Using the custom page name (in your case: $this->plugin_slug) as your section id would get around the issue. However, all your options would have to be contained in a single section.

Solution

For a more robust solution, make these changes to your Plugin_Name_Admin class:

Add to constructor:

// Tracks new sections for whitelist_custom_options_page()
$this->page_sections = array();
// Must run after wp's `option_update_filter()`, so priority > 10
add_action( 'whitelist_options', array( $this, 'whitelist_custom_options_page' ),11 );

Add these methods:

// White-lists options on custom pages.
// Workaround for second issue: http://j.mp/Pk3UCF
public function whitelist_custom_options_page( $whitelist_options ){
    // Custom options are mapped by section id; Re-map by page slug.
    foreach($this->page_sections as $page => $sections ){
        $whitelist_options[$page] = array();
        foreach( $sections as $section )
            if( !empty( $whitelist_options[$section] ) )
                foreach( $whitelist_options[$section] as $option )
                    $whitelist_options[$page][] = $option;
            }
    return $whitelist_options;
}

// Wrapper for wp's `add_settings_section()` that tracks custom sections
private function add_settings_section( $id, $title, $cb, $page ){
    add_settings_section( $id, $title, $cb, $page );
    if( $id != $page ){
        if( !isset($this->page_sections[$page]))
            $this->page_sections[$page] = array();
        $this->page_sections[$page][$id] = $id;
    }
}

And change add_settings_section() calls to: $this->add_settings_section().


Other notes on your code

  • Your form code is correct. Your form has to submit to options.php, as pointed out to me by @Chris_O, and as indicated in the WP Settings API documentation.
  • Namespacing has it’s advantages, but it can make it more complex to debug, and lowers the compatibility of your code (requires PHP>=5.3, other plugins/themes that use autoloaders, etc). So if there is no good reason to namespace your file, don’t. You are already avoiding naming conflicts by wrapping your code in a class. Make your class names more specific, and bring your validate() callbacks into the class as public methods.
  • Comparing your cited plugin boilerplate with your code, it looks like your code is actually based off a fork or an old version of the boilerplate. Even the filenames and paths are different. You could migrate your plugin to the latest version, but note that this plugin boilerplate may not be right for your needs. It makes use of singletons, which are generally discouraged. There are cases where the singleton pattern is sensible, but this should be conscious decision, not the goto solution.

Leave a Comment