Meta Box Generator

Fields in the editor, and a save handler that checks the nonce, the autosave, the revision and the capability — in that order, before it writes anything.

acme-event-details.php
Saved just now
The box
Post types
Fields
4 fields · keys prefixed _acme_
_acme_start_date
_acme_venue
_acme_format
_acme_sold_out
Save handler
Nonce check
wp_nonce_field() in the box and wp_verify_nonce() before saving.
Autosave and revision guard
Bails on autosaves and revisions, which otherwise blank your fields.
Capability check
current_user_can( edit_post, $post_id ) before writing.
Post type check
Skips the handler on saves of unrelated post types.
register_post_meta()
Registers the keys with show_in_rest so blocks and the editor can read them.
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Register the meta box.
 */
function acme_add_box() {
	add_meta_box(
		'acme_event_details',
		__( 'Event details', 'acme' ),
		'acme_render',
		array( 'post', 'event' ),
		'side',
		'default'
	);
}
add_action( 'add_meta_boxes', 'acme_add_box' );

/**
 * Print the box.
 *
 * @param WP_Post $post Post being edited.
 */
function acme_render( $post ) {
	wp_nonce_field( 'acme_save_meta', 'acme_meta_nonce' );

	$start_date = get_post_meta( $post->ID, '_acme_start_date', true );
	printf(
		'<p><label for="acme_start_date"><strong>%1$s</strong></label><br /><input type="date" id="acme_start_date" name="acme_start_date" value="%2$s" class="widefat" />%3$s</p>',
		esc_html( __( 'Start date', 'acme' ) ),
		esc_attr( $start_date ),
		'<span class="description">' . esc_html( __( 'When the event begins.', 'acme' ) ) . '</span>'
	);

	$venue = get_post_meta( $post->ID, '_acme_venue', true );
	printf(
		'<p><label for="acme_venue"><strong>%1$s</strong></label><br /><input type="text" id="acme_venue" name="acme_venue" value="%2$s" class="widefat" />%3$s</p>',
		esc_html( __( 'Venue', 'acme' ) ),
		esc_attr( $venue ),
		''
	);

	$format = get_post_meta( $post->ID, '_acme_format', true );
	echo '<p><label for="acme_format"><strong>' . esc_html( __( 'Format', 'acme' ) ) . '</strong></label><br />';
	echo '<select id="acme_format" name="acme_format">';

	foreach ( array(
		'online' => __( 'Online', 'acme' ),
		'venue'  => __( 'In person', 'acme' ),
		'hybrid' => __( 'Hybrid', 'acme' ),
	) as $value => $label ) {
		printf(
			'<option value="%1$s"%2$s>%3$s</option>',
			esc_attr( $value ),
			selected( $format, $value, false ),
			esc_html( $label )
		);
	}

	echo '</select></p>';

	$sold_out = get_post_meta( $post->ID, '_acme_sold_out', true );
	printf(
		'<p><label for="acme_sold_out"><input type="checkbox" id="acme_sold_out" name="acme_sold_out" value="1"%1$s /> %2$s</label>%3$s</p>',
		checked( (bool) $sold_out, true, false ),
		esc_html( __( 'Sold out', 'acme' ) ),
		'<span class="description">' . esc_html( __( 'Hides the booking button on the front end.', 'acme' ) ) . '</span>'
	);
}

/**
 * Save the fields.
 *
 * @param int $post_id Post being saved.
 */
function acme_save( $post_id ) {
	if ( ! isset( $_POST['acme_meta_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['acme_meta_nonce'] ), 'acme_save_meta' ) ) {
		return;
	}

	if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) || ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) ) {
		return;
	}

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

	if ( ! in_array( get_post_type( $post_id ), array( 'post', 'event' ), true ) ) {
		return;
	}

	if ( isset( $_POST['acme_start_date'] ) ) {
		$value = sanitize_text_field( wp_unslash( $_POST['acme_start_date'] ) );

		if ( '' === $value ) {
			delete_post_meta( $post_id, '_acme_start_date' );
		} else {
			update_post_meta( $post_id, '_acme_start_date', $value );
		}
	}

	if ( isset( $_POST['acme_venue'] ) ) {
		$value = sanitize_text_field( wp_unslash( $_POST['acme_venue'] ) );

		if ( '' === $value ) {
			delete_post_meta( $post_id, '_acme_venue' );
		} else {
			update_post_meta( $post_id, '_acme_venue', $value );
		}
	}

	if ( isset( $_POST['acme_format'] ) && in_array( sanitize_key( $_POST['acme_format'] ), array( 'online', 'venue', 'hybrid' ), true ) ) {
		update_post_meta( $post_id, '_acme_format', sanitize_key( $_POST['acme_format'] ) );
	}

	if ( ! empty( $_POST['acme_sold_out'] ) ) {
		update_post_meta( $post_id, '_acme_sold_out', 1 );
	} else {
		delete_post_meta( $post_id, '_acme_sold_out' );
	}
}
add_action( 'save_post', 'acme_save' );

/**
 * Expose the meta to the REST API so blocks and the editor can read it.
 */
function acme_register_meta() {
	register_post_meta(
		'post',
		'_acme_start_date',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'sanitize_callback' => 'sanitize_text_field',
			'auth_callback'     => function () {
				return current_user_can( 'edit_posts' );
			},
		)
	);

	register_post_meta(
		'post',
		'_acme_venue',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'sanitize_callback' => 'sanitize_text_field',
			'auth_callback'     => function () {
				return current_user_can( 'edit_posts' );
			},
		)
	);

	register_post_meta(
		'post',
		'_acme_format',
		array(
			'type'              => 'string',
			'single'            => true,
			'show_in_rest'      => true,
			'sanitize_callback' => 'sanitize_key',
			'auth_callback'     => function () {
				return current_user_can( 'edit_posts' );
			},
		)
	);

	register_post_meta(
		'post',
		'_acme_sold_out',
		array(
			'type'              => 'boolean',
			'single'            => true,
			'show_in_rest'      => true,
			'sanitize_callback' => 'rest_sanitize_boolean',
			'auth_callback'     => function () {
				return current_user_can( 'edit_posts' );
			},
		)
	);
}
add_action( 'init', 'acme_register_meta' );