WordPress at Your Fingertips

Creating Custom Fields Block in WordPress Admin Panel

To avoid creating custom fields entirely by hand, you can use my PHP class for convenient creation of post meta fields.

To display a similar block for taxonomy elements, refer to the description of the event: (taxonomy)_edit_form_fields.

Introduction

Before delving into creating a meta-box for custom fields, it should be noted that WordPress is a platform that, perhaps more than any other, lends itself to extension with minimal effort. This is why so many plugins are written for WordPress - it's all very simple, you just need to know the basics of PHP and understand how the system works.

Anyone who is familiar with WordPress has encountered the concept of "custom fields" and has used them to solve some non-trivial tasks.

Custom fields in WordPress are a very convenient tool when you need to "attach" any additional data to a specific post. This data can be anything, from logical true/false (1/0) to extensive texts, arrays, and more.

For example, we can create a new custom field "Title" and write text in its value (an alternative post title), then in the template code use the following code to display this text:

<h1><?php echo get_post_meta( $post_id, 'title', true ); ?></h1>

The get_post_meta() function can be used anywhere in the template or plugin, i.e., outside the WordPress Loop.

Another example: using custom fields, we can perform or not perform actions, depending on the value of the meta-field. For example, by placing logical numbers "1" or "0" in a custom field, we can display or not display some information for the current post.

Custom fields are used in WordPress extensively, by various post rating plugins (WP-PostRatings), SEO plugins (Platinum SEO Pack), allowing to specify the post's Title, Description, Keywords, my thumbnail creation plugin (Kama Thumbnail), and many other plugins. It can be said that every second non-standard task related to posts is solved through meta-fields (custom fields)!

Therefore, if you don't know how to use them yet, it's time to learn! Below, we will talk about how to create a separate block with the necessary custom fields and how to do it without plugins.

Before You Start

WordPress has a default custom fields block. However, it is often not suitable because to add a field, you need to enter a key and value, and there is no explanatory description for what a specific field is for.

It is much more convenient to have a block where you can only enter values, and where each field will have a meaningful name and description. The block we are about to create will look like this:

Note: If you create a custom field whose key starts with an underscore "_" (underscore), for example "_my_special_key", then such a field will be considered "internal" and will not be displayed in the dropdown list of custom fields in the admin panel. Such a field can only be created by querying the database, for example, using the add_post_meta() or update_post_meta() functions.

For the block editor (Gutenberg), you can also enable the default custom fields block:

Custom Fields Block for Gutenberg

Creating a Custom Fields Meta Block

To create a meta-block, we will need:

Add the following code to the theme's functions.php file or to a plugin:

#1. Create a new meta block for posts

Let's call it "Extra Fields":

// activate the meta block function (my_extra_fields)
add_action( 'add_meta_boxes', 'my_extra_fields_meta_box', 1 );

function my_extra_fields_meta_box() {
	$post_type = 'post';
	add_meta_box( 'extra_fields', 'Extra Fields', 'extra_fields_box_func', $post_type, 'normal', 'high' );
}

#2. Fill this block with HTML form fields

To do this, create a callback function extra_fields_box_func, the name of which we specified in the add_meta_box() function parameter. It is responsible for the contents of the meta block:

<?php
// block code
function extra_fields_box_func( $post ) {
	?>
	<p>
		Page Title (title):
		<label>
			<input type="text" name="extra[title]"
				   value="<?= get_post_meta( $post->ID, 'title', 1 ) ?>"
				   style="width:50%"/>
		</label>
	</p>

	<p>
		Article Description (description):
		<textarea type="text" name="extra[description]"
				  style="width:100%;height:50px;"><?= get_post_meta( $post->ID, 'description', 1 ) ?></textarea>
	</p>

	<p>
		Post Visibility: <?php $mark_v = get_post_meta( $post->ID, 'robotmeta', true ) ?>
		<label><input type="radio" name="extra[robotmeta]" value="" <?php checked( $mark_v, '' ) ?> />
			index,follow</label>
		<label><input type="radio" name="extra[robotmeta]" value="nofollow" <?php checked( $mark_v, 'nofollow' ) ?> />
			nofollow</label>
		<label><input type="radio" name="extra[robotmeta]" value="noindex" <?php checked( $mark_v, 'noindex' ) ?> />
			noindex</label>
		<label><input type="radio" name="extra[robotmeta]"
					  value="noindex,nofollow" <?php checked( $mark_v, 'noindex,nofollow' ) ?> />
			noindex,nofollow</label>
	</p>

	<p>
		Select a value:
		<select name="extra[select]">
			<?php $sel_v = get_post_meta( $post->ID, 'select', true ) ?>
			<option value="0">---</option>
			<option value="1" <?php selected( $sel_v, '1' ) ?> >Choose me</option>
			<option value="2" <?php selected( $sel_v, '2' ) ?> >Not me</option>
			<option value="3" <?php selected( $sel_v, '3' ) ?> >Better me</option>
		</select>
	</p>

	<input type="hidden" name="extra_fields_nonce" value="<?= wp_create_nonce( 'extra_fields_nonce_id' ) ?>"/>
	<?php
}

I formatted all field names into the extra[] array to make it easier to process this data later.

The hidden field name="extra_fields_nonce" is necessary for data protection when saving data.

#3. Save the data

We have already created the custom fields block, now we need to process the data when saving the post. To do this, we use the save_post hook, which is triggered when the post is saved. At this moment, we will get the data from the extra[] array and process it:

// update fields on save
add_action( 'save_post', 'my_extra_fields_save_on_update', 0 );

function my_extra_fields_save_on_update( $post_id ) {
	// basic check
	if(
		empty( $_POST['extra'] )
		|| ! wp_verify_nonce( $_POST['extra_fields_nonce'], 'extra_fields_nonce_id' )
		|| wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id )
	){
		return false;
	}

	$extra = $_POST['extra'];

	// All is OK! Now, we need to save/delete the data

	// Clear all data
	$extra = array_map( 'sanitize_text_field', $extra );
	foreach( $extra as $key => $value ){
		// delete the field if the value is empty
		if( ! $value ){
			delete_post_meta( $post_id, $key );
		}
		else {
			update_post_meta( $post_id, $key, $value ); // add_post_meta() works automatically
		}
	}

	return $post_id;
}

That's it! The custom fields block is ready!

Custom Fields Block for Custom Post Type

If you need to create a block for another post type, for example, "page" (for pages), then register another meta block and describe its HTML code in a new function, which also needs to be specified when registering the block (extra_fields_box_page_func).

You don't need to create a function to handle fields when saving a post, the main thing is to specify the field names as arrays extra[]:

function my_extra_fields() {
	add_meta_box( 'extra_fields', 'Extra Fields', 'extra_fields_box_func', 'post', 'normal', 'high'  );
	add_meta_box( 'extra_fields', 'Extra Fields', 'extra_fields_box_page_func', 'page', 'normal', 'high'  );
}

// html code block for the page post type
function extra_fields_box_page_func() {
   ?>
   <!-- HTML form fields go here -->
   <?php
}

Issue with the Checkbox Type

A drawback of this method is that the extra[] array must be defined, even if it passes an empty value, otherwise the field will not be processed when saving the data.

As a result, a problem arises when using the "checkbox" type: <input type="checkbox">, because a checkbox only passes data if the checkbox is checked and does not pass anything if it is unchecked. However, we need it to pass an empty value so that the code deletes the value if it was saved before.

To work around this "issue," I did the following: before the checkbox field, create a hidden field with the same name attribute and an empty value. As a result, if there is a checkbox, the value of the hidden field is overridden, and if there is no checkbox, an empty value of the hidden field is taken.

So, you need to call the checkbox like this:

<input type="hidden" name="extra[my_checkbox]" value="" />
<input type="checkbox" name="extra[my_checkbox]" value="1" />

The same trick may also be useful for a field with the radio type.

Creating a metabox using a PHP class

This example shows how to create a single field to store an array of data. The array can be expanded or reduced by clicking on + or removed (works on the script).

This is an example of creating a "repeater" field, similar to the ACF plugin (repeater field in the paid version).

As a result, we will get such a metabox:

GitHub
<?php

$my_metabox = new My_Best_Metaboxes( 'post' );
$my_metabox->init();

class My_Best_Metaboxes {

	public $post_type;

	static $meta_prefix = 'company_address';

	public function __construct( $post_type ) {
		$this->post_type = $post_type;
	}

	public function init() {
		add_action( 'add_meta_boxes', [ $this, 'add_metabox' ] );
		add_action( 'save_post_' . $this->post_type, [ $this, 'save_metabox' ] );
		add_action( 'admin_print_footer_scripts', [ $this, 'show_assets' ], 10, 999 );
	}

	## Добавляет матабоксы
	public function add_metabox() {
		add_meta_box( 'box_info_company', 'Информация о компании', [
			$this,
			'render_metabox',
		], $this->post_type, 'advanced', 'high' );
	}

	## Отображает метабокс на странице редактирования поста
	public function render_metabox( $post ) {
		?>
		<table class="form-table company-info">
			<tr>
				<th>
					Адреса компании <span class="dashicons dashicons-plus-alt add-company-address"></span>
				</th>
				<td class="company-address-list">
					<?php
					$input = '
					<span class="item-address">
						<input type="text" name="' . self::$meta_prefix . '[]" value="%s">
						<span class="dashicons dashicons-trash remove-company-address"></span>
					</span>
					';

					$addresses = get_post_meta( $post->ID, self::$meta_prefix, true );

					if( is_array( $addresses ) ){
						foreach( $addresses as $addr ){
							printf( $input, esc_attr( $addr ) );
						}
					}
					else{
						printf( $input, '' );
					}
					?>
				</td>
			</tr>
		</table>
		<?php
	}

	## Очищает и сохраняет значения полей
	public function save_metabox( $post_id ) {

		if( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ){
			return;
		}

		if( ! isset( $_POST[ self::$meta_prefix ] ) ){
			return;
		}

		$addresses = $_POST[ self::$meta_prefix ];

		// очистка
		$addresses = array_map( 'sanitize_text_field', $addresses );

		// уберем пустые адреса
		$addresses = array_filter( $addresses );

		if( $addresses ){
			update_post_meta( $post_id, self::$meta_prefix, $addresses );
		}
		else{
			delete_post_meta( $post_id, self::$meta_prefix );
		}
	}

	## Подключает скрипты и стили
	public function show_assets() {
		if( is_admin() && get_current_screen()->id == $this->post_type ){
			$this->show_styles();
			$this->show_scripts();
		}
	}

	## Выводит на экран стили
	public function show_styles() {
		?>
		<style>
			.add-company-address{
				color: #00a0d2;
				cursor: pointer;
			}

			.company-address-list .item-address{
				display: flex;
				align-items: center;
			}

			.company-address-list .item-address input{
				width: 100%;
				max-width: 400px;
			}

			.remove-company-address{
				color: brown;
				cursor: pointer;
			}
		</style>
		<?php
	}

	## Выводит на экран JS
	public function show_scripts() {
		?>
		<script>
			jQuery( document ).ready( function( $ ){

				var $companyInfo = $( '.company-info' );

				// Добавляет бокс с вводом адреса фирмы
				$( '.add-company-address', $companyInfo ).click( function(){
					var $list = $( '.company-address-list' );
					$item = $list.find( '.item-address' ).first().clone();

					$item.find( 'input' ).val( '' ); // чистим знанчение

					$list.append( $item );
				} );

				// Удаляет бокс с вводом адреса фирмы
				$companyInfo.on( 'click', '.remove-company-address', function(){
					if( $( '.item-address' ).length > 1 ){
						$( this ).closest( '.item-address' ).remove();
					}
					else{
						$( this ).closest( '.item-address' ).find( 'input' ).val( '' );
					}
				} );

			} );
		</script>
		<?php
	}

}

Plugins for creating custom field blocks

  • Advanced Custom Fields (ACF) - the most popular and flexible plugin for creating custom fields. With good documentation.

  • Custom Field Suite - similar to ACF, but less sophisticated.

  • CMB2 - CMB2 is a developer tool for creating: metaboxes, meta-fields. It allows easy management of posts, taxonomy elements, users, comments, or creating custom settings pages.

  • Custom Field Template - a real jack-of-all-trades. With it, you can create any form for any post types, specify forms for individual posts and categories. I think in most cases, you can do without such a jack-of-all-trades.

Conclusion

The simplicity of setting up a custom field block using this method is lost! I by no means want to say that creating blocks in this way is better than using plugins. However, this approach is more flexible, because we can create absolutely any fields and arrange/style them as we like.

Moreover, there is usually no need to create several such blocks and frequently edit them, as can be done using plugins. For example, on this site, I use this approach and a small code in functions.php frees me from the need to use another plugin.

This is more of an educational article, and for many, plugins implementing this task will be a more suitable solution.

1 comment
    Log In