A client came to us with a plugin that was, to put it mildly, a total mess. It started simple enough—one little settings page. But over the years, five different developers had bolted on five more admin pages. Each one was a unique beast, with its own set of functions, its own way of hooking into admin_menu, and a ton of duplicated code. Adding a new feature was like playing Jenga on a rollercoaster. The whole thing was fragile, and the client was terrified to touch it. This is a classic problem when you need to manage multiple WordPress admin pages.
The Wrong Fix: Treating the Symptom
My first thought was to just grab the ugliest page and refactor it into a nice, tidy class. Contain the chaos, right? And sure, that would have worked for that single page. It would have been isolated and clean. But then I stopped. That’s a band-aid. The real disease wasn’t one messy page; it was the complete lack of a system for managing *all* the pages. Creating another one-off solution, even a clean one, wasn’t going to fix the architectural rot. It was just another silo.
Building a Real System for WordPress Admin Pages
To do it right, you have to stop thinking about individual pages and start thinking about the system as a whole. The key is to create a contract that every admin page must follow. In object-oriented programming, that contract is an interface. It doesn’t care *how* a page renders or what it does; it only cares that the page can provide a specific set of details. Trust me on this, it makes everything predictable.
interface AdminPageInterface
{
/**
* Get the slug used by the admin page.
* @return string
*/
public function get_slug();
/**
* Get the title of the admin page.
* @return string
*/
public function get_page_title();
/**
* Get the capability required to view the page.
* @return string
*/
public function get_capability();
/**
* Renders the admin page.
*/
public function render_page();
}
With that interface in place, every admin page class we create will implement it. That guarantees they all have the same core methods. Then, the real magic happens: we create one central “manager” class. Its only job is to take an array of objects that follow our contract and register them with WordPress. This idea builds on a solid foundation I first saw over on Carl Alexander’s blog. The manager doesn’t need to know anything about what the pages do. It just loops and registers.
class AdminPagesSubscriber
{
/** @var AdminPageInterface[] */
private $admin_pages;
public function __construct(array $admin_pages)
{
// You'd add validation here to ensure every object
// in the array implements AdminPageInterface.
$this->admin_pages = $admin_pages;
}
public function register_hooks()
{
add_action('admin_menu', [$this, 'add_admin_pages']);
}
public function add_admin_pages()
{
foreach ($this->admin_pages as $page) {
add_menu_page(
$page->get_page_title(),
$page->get_menu_title(),
$page->get_capability(),
$page->get_slug(),
[$page, 'render_page']
);
}
}
}
// Then, in your main plugin file:
// $pages_to_register = [ new SettingsPage(), new DashboardWidgetPage() ];
// $subscriber = new AdminPagesSubscriber( $pages_to_register );
// $subscriber->register_hooks();
So, What’s the Point?
This isn’t just about writing cleaner code. It’s about building something that can grow without collapsing. And that was it. Suddenly, the client’s messy plugin had a central, logical system.
- It’s Scalable: Adding a new admin page is trivial. Just create a new class that follows the contract and add it to the array. No more hunting for the right hook.
- It’s Decoupled: The pages themselves are completely separate from the registration logic. They don’t know or care how they get added to WordPress.
- It’s Maintainable: When you need to change how admin pages are registered, you only have to do it in one place: the subscriber. You’re not editing five different files. Total nightmare avoided.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Leave a Reply