Why the WordPress singleton pattern can burn you

I was digging through a client’s site recently, the kind of project where three different developers had left their mark over five years. Total mess. The complaint was simple: “When we update a product, sometimes the inventory count doesn’t show up right on the frontend.” It was intermittent, maddening, and of course costing them sales. After hours of tracing hooks, I found the culprit: a custom reporting plugin leaned on the Singleton pattern for literally every class. A database object, a reporting object, a product object, all of it Singletons. That’s where the trouble began.

A lot of us learned to reach for Singletons when we moved to OOP in WordPress. It solves a real problem: how do you get at your main plugin object from anywhere without a nasty global variable? The Singleton looks like a clean, clever answer, and for a while I thought it was. It felt professional.

The “Singleton for everything” trap

The classic Singleton looks something like this. You have a private constructor, a static variable to hold your one and only instance, and a static method to hand that instance back. Simple.

class My_Messy_Plugin {
    private static $instance;

    private function __construct() {
        // Initialization stuff here...
    }

    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function do_something() {
        // Plugin logic...
    }
}

// And you use it like this:
// My_Messy_Plugin::get_instance()->do_something();

I did this for years myself. My early plugins were littered with ::get_instance(). It works, right? Until you have to write a unit test. Or until two of your “unique” objects need to interact in a slightly different context. The Singleton pattern basically creates global state, just wrapped in a class so it feels less dirty. It’s a hidden dependency. When you call My_Other_Class::get_instance() from inside your first class, you’ve hard-coded a dependency that nobody can see from the outside. That’s exactly what bit my client. The reporting plugin’s “product” Singleton was holding stale data, and because it was globally unique, the rest of the site got that stale data too. A total nightmare.

A better way: static registration

The problem isn’t getting access to an object; it’s initializing your plugin and its hooks properly. So instead of making the object globally accessible, don’t. Instantiate it once, tell WordPress about its methods, and let it go. This builds on an idea I picked up from carlalexander.ca: decouple the class itself from the WordPress Plugin API.

Use a static method to bootstrap the plugin. It’s a small change with a big payoff.

class My_Clean_Plugin {
    
    public function __construct() {
        // Constructor is for setting up the object's state,
        // NOT for adding hooks.
    }

    public static function init() {
        $plugin = new self();
        add_action( 'wp_enqueue_scripts', array( $plugin, 'enqueue_assets' ) );
        add_filter( 'the_content', array( $plugin, 'modify_content' ) );
    }

    public function enqueue_assets() {
        // Enqueue scripts and styles.
    }

    public function modify_content( $content ) {
        // Do something to the content.
        return $content;
    }
}

// In your main plugin file, you just call this once:
My_Clean_Plugin::init();

So, what’s the point?

Moving the hook registration into a static init() method buys you a few things:

  • Testability: The constructor stays clean, so you can pass in mock objects and test your methods in isolation without loading all of WordPress.
  • Clarity: It’s obvious how the plugin starts. There’s one entry point, with no hidden dependency from a get_instance() call buried in some other method.
  • Flexibility: The class doesn’t depend on WordPress at all. The static init() method is the bridge, and the core logic is plain PHP.

The Singleton isn’t inherently evil. It’s a tool for a specific job, managing a single shared resource like a database connection, not general plugin architecture. In WordPress, reaching for it is almost always a sign you’re creating problems for your future self. Or for the next dev who has to clean it up.

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.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.