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:
- [updated] How can I register the custom meta field for all of the post types in this list
[ 'post', 'page', 'attachement' ]
?
- 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"}
.
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.