I'm writing a plugin which defines a class that is meant to be called multiple times. The class uses add_action to register it's own methods as callbacks.

class MyClass {

    __construct($foo) {

        add_action('hook1', array($this, 'method1') );
        add_action('hook2', array($this, 'method2') );


    public function method1() {...}

    public function method2() {...}


This works just fine. But when I do it twice, only the second works.

new MyClass('foo');
new MyClass('bar');

In other words, the results of the code above are exactly what I'd expect from the following:

// new MyClass('foo');
new MyClass('bar');

It seems like the second instance overrides the first. What am I doing wrong?

I've checked to make sure that spl_object_hash() is different for both instances and it is.

I added var_dump($wp_filter); after my code and I see BOTH instances listed under the relevant action hook key. So it looks like WP knows they are both there...

Full code from my complete plugin:

Paste this into a file in /wp-content/mu-plugins.php and then add/edit a post. You should see two new custom meta boxes "Foo" and "Bar" but you will only see "Bar". Comment in/out the final lines to test with different combinations of instances of the class.

Plugin Name: SDP Custom Meta Boxes
Description: Defines a custom meta box framework class.

class SDP_Custom_Field {

  public $name = NULL;
  public $slug = NULL;
  public $prefix = NULL;
  public $post_type = 'post'; //The type of Write screen on which to show the meta: 'post', 'page', 'dashboard', 'link', 'attachment' or 'custom_post_type'
  public $meta_box_location = 'normal'; //'normal', 'advanced', or 'side'
  public $meta_box_priority = 'default'; //'high', 'core', 'default' or 'low'
  public $meta_box_field_type = NULL;

  public function __construct( $custom_field_name, $args=array() ) {
    #set the name
    $this->name = $custom_field_name;

    #set the slug
    if (isset($args['slug'])) {
        $this->slug = $args['slug'];
    } else {
        $slug = strtolower($custom_field_name);
        $slug = str_replace(' ', '_', $slug);
        $this->slug = $slug;      

    #set the meta_box_field_type
    if (isset($args['field_type'])) {
        switch ($args['field_type']) {
            case 'text':
                $this->meta_box_field_type = 'text';
            case 'textarea':
                $this->meta_box_field_type = 'textarea';
                $this->meta_box_field_type = 'text';

    } else {
        $this->meta_box_field_type = 'text';

    // add_action( 'add_meta_boxes', array($this, '_add_custom_metaboxes'));
    // add_action( 'save_post', array($this, '_save_postdata'));

    add_action( 'add_meta_boxes', array(&$this, '_add_custom_metaboxes'));
    add_action( 'save_post', array(&$this, '_save_postdata'));

    //var_dump( is_object( array($this, '_add_custom_metaboxes') ));


  // public function go() {
  //   add_action( 'add_meta_boxes', array($this, '_add_custom_metaboxes'));
  //   add_action( 'save_post', array($this, '_save_postdata'));
  // }

  #Echo the HTML for this meta box... 
  public function _print_field_HTML( $post ) {

      $value = get_post_meta( $post->ID, $this->slug, true );

      // Add an nonce field so we can check for it later.
      wp_nonce_field( $this->slug.'_custom_box', $this->slug.'_custom_box_nonce' );

      echo '<label for="'.$this->slug.'">'.$this->name.'</label> ';
      echo '<input type="text" id="'.$this->slug.'" name="'.$this->slug.'" value="' . esc_attr( $value ) . '" size="25" />';

      //TODO: Add update button
      //TODO: Add delet button

  public function _add_custom_metaboxes() {
      add_meta_box( NULL, $this->name, array(&$this, '_print_field_HTML'), $this->post_type, $this->meta_box_location, $this->meta_box_priority, NULL );

   * When the post is saved, saves our custom data.
   * @param int $post_id The ID of the post being saved.
  public function _save_postdata( $post_id ) {

     * We need to verify this came from the our screen and with proper authorization,
     * because save_post can be triggered at other times.

    // Check if our nonce is set.
    if ( ! isset( $_POST[$this->slug.'_custom_box_nonce'] ) )
      return $post_id;

    $nonce = $_POST[$this->slug.'_custom_box_nonce'];

    // Verify that the nonce is valid.
    if ( ! wp_verify_nonce( $nonce, $this->slug.'_custom_box' ) )
        return $post_id;

    // If this is an autosave, our form has not been submitted, so we don't want to do anything.
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 
        return $post_id;

      if ( ! current_user_can( 'edit_post', $post_id ) )
          return $post_id;

    /* OK, its safe for us to save the data now. */

    // Sanitize user input.
    $mydata = sanitize_text_field( $_POST[$this->slug] );

    // Update the meta field in the database.
    update_post_meta( $post_id, $this->slug, $mydata );


new SDP_Custom_Field( 'Foo' );
new SDP_Custom_Field( 'Bar' );

1 Answer 1


When you call add_meta_box() you must provide a unique ID as the first parameter.

Consider this case:

add_meta_box( 'foobox', 'Foo Title', 'foo_callback' );
add_meta_box( 'foobox', 'Bar Title', 'bar_callback' );

You will just get the Bar Title metabox now, because it will overwrite the first one.

What you need is a new ID for each box:

add_meta_box( 'foobox', 'Foo Title', 'foo_callback' );
add_meta_box( 'barbox', 'Bar Title', 'bar_callback' );

Try to create an ID from $custom_field_name or use a static counter.

Besides that, you should change your code in two other areas:

  1. Get rid of that god class, separate it into multiple classes:

    • one for the output, the metabox markup
    • one for the nonce. Example
    • one to get and to save the data
    • a controller to stick these classes together
  2. Fix your nonce, or your plugin will break badly on multi-site installations.

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.