2

I have a custom meta box for saving values from two input fields into a single post meta field.

The data is saved serialized in the DB:

get_post_meta( $post->ID, '_custom_key', true )

will return:

a:2:{s:5:"email";s:10:"hello@me.me";s:6:"number";i:15;}

Now, with Gutenberg, I want to move that whole custom meta box into a separate sidebar plugin. So in order to do this, first, I had to add the __back_compat_meta_box argument when adding the meta box, so it won't appear in Sidebar -> Settings -> Document anymore:

    add_meta_box(
        'options',
        'Options',
        [ $this, 'renderOptionsBox' ],
        [ 'post', 'page', 'attachement' ], // the meta field is registered for multiple post types 
        'side',
        'high',
        [ '__back_compat_meta_box' => true ]
    );

For the meta field (_custom_key) to work with the block editor, it has to be registered using the register_meta function:

register_meta( string $object_type, string $meta_key, array $args, string|array $deprecated = null )

My questions are:

  1. [updated] How can I register the custom meta field for all of the post types in this list [ 'post', 'page', 'attachement' ]?
  2. How can I still follow the same serialized way of saving the data in a single field if the types ($args->type) supported when registering a meta are only 'string', 'boolean', 'integer', and 'number'?

Right now if I just register the _custom_key meta using:

    register_meta( 'post', '_custom_key', [
        'show_in_rest' => true,
        'single' => true,
        'type' => 'string',
        'auth_callback' => function () {
            return current_user_can( 'edit_posts' );
        }
    ]);

I will get a Notice: Array to string conversion in .../wp-includes/rest-api/fields/class-wp-rest-meta-fields.php because already saved data from the DB is serialized.

and typing in the console: wp.data.select( 'core/editor' ).getCurrentPost().meta; will return a string: {_custom_key: "Array"}.

  • 1
    I don't know the answer to 2 (as far as I can tell, register_meta() does not support array values), but in regards to 1, object_type is not the same as post_type. The object type refers to whether it's for a post type, user or term. If you want to register it for specific post types, set object_type to post and object_subtype (in the args array) to the post type. – Jacob Peattie Mar 6 at 13:39
  • object_subtype still accepts only a string. Does that mean that I have to register the meta for each post I want? – Vlad Mar 6 at 14:50
  • Seems that way. – Jacob Peattie Mar 6 at 14:59
0

Here's an answer to question 1 that uses register_post_meta. It's also the start of an answer to question 2 by use of the prepare_callback option in show_in_rest .

add_action( 'init', 'wpse_89033_register_custom_meta' );
function wpse_89033_register_custom_meta() {

    $post_types = [ 
        'post',
        'page',
        'attachment',
    ];

    foreach ( $post_types as $post_type ) {

        register_post_meta( 
            $post_type,
            '_custom_key',
            array(
                'type'              => 'string',
                'single'            => true,
                'auth_callback'     => function() { 
                    return current_user_can( 'edit_posts' );
                },
                'show_in_rest'      => [
                    'prepare_callback' => function( $value ) {
                        return wp_json_encode( $value );
                    }
                ],
            ) 
        );

    }

}

The documentation for show_in_rest (see register_meta) says it's a boolean parameter. But it will accept an options array. To see the options, look at the body of get_registered_fields, in particular the value of $default_args. The prepare_callback overrides prepare_value, the method in WP_REST_Meta_Fields that converted your custom meta value from an array to a string. With our callback, that array will instead be encoded as JSON.

On the JavaScript side, here are some key declarations.

// Get the custom meta
let customMeta = wp.data.select('core/editor').getEditedPostAttribute('meta');

// Parse the meta so you can do some JS with it
let parsed = JSON.parse(customMeta._custom_key);

// Do some JS with the parsed meta...

// Stringify the parsed meta before dispatching it
let stringified = JSON.stringify(parsed);

/*
 * Dispatch (update) the meta (and don't forget to
 * save/publish/update the post to ensure the meta goes in the database)
 */ 
wp.data.dispatch('core/editor').editPost({meta: {_custom_key: stringified}}); 

Back in PHP land, getting and updating your custom meta will now work differently. When you call get_post_meta, you're used to getting an array, and you'll probably still want one sometimes. But if you last updated your custom meta via the WP REST API, you're getting a JSON string. In that case, you'll have to use json_decode. One approach would be to wrap calls to get_post_meta like so:

function wpse_89033_get_custom_meta( $post_id, $meta_key ) {

    $post_meta = get_post_meta( $post_id, $meta_key, true );

    // If $post_meta is a string that's not empty
    if ( $post_meta && is_string( $post_meta ) ) {

        /*
         * Use the decoded JSON string or else the original string 
         * if decoding fails, e.g., if the string isn't JSON
         */ 
        $post_meta = json_decode( $post_meta, true ) ?: $post_meta;

    }

    return $post_meta;

}

If you make changes to your custom meta as an array, don't worry about re-encoding before calling update_post_meta. The prepare_callback will handle re-encoding.

About updating in PHP, calling update_post_meta during save_post, as shown in the official plugin handbook, appears to override updates made via the WP REST API. So one more thing to consider is conditioning any save_post update to your custom meta on whether the post is being edited in Gutenberg. If it is, leave the update to the REST API.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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