Meta box controls — image and gallery upload controls

In the previous article, we went through adding the slider and date picker controls. This time I’ll show you how to add image upload and gallery upload controls. For this we’ll use the built in handler. To be honest this was supposed to be covered last time (at least according ot my own to-do paper), but there was a request for slider control, so I decided to cover that first. We’ll continue in the same file as before, basically adding to the existing controls.

PHP part

Inside our mytheme_metabox_controls( $post ) function, we’ll add two variables that will check the meta data of the post for the image and galleries.

$mytheme_featured_image = ( isset( $meta['mytheme_featured_image'][0] ) ) ? $meta['mytheme_featured_image'][0] : '';
$mytheme_gallery = ( isset( $meta['mytheme_gallery'][0] ) ) ? $meta['mytheme_gallery'][0] : '';

While you may think that we are essentially doing the same thing (gallery is just a bunch of images sorted together), the difference is quite big. In the case of the ‘featured image’ we’re storing the image URL, while in the case of the galleries we’ll store the attachment ID’s, so the way we display them and use them is quite different.

Inside our .right_part div, we’ll add

<label for="mytheme_featured_image"><?php esc_html_e( 'Featured Image', 'mytheme' ); ?></label>
<span class="uploaded_image">
<?php if ( '' !== $mytheme_featured_image ) : ?>
<img src="<?php echo esc_url( $mytheme_featured_image ); ?>" />
<?php endif; ?>
<input type="text" name="mytheme_featured_image" value="<?php echo esc_url( $mytheme_featured_image ); ?>" class="featured_image_upload">
<input type="button" name="image_upload" value="<?php esc_html_e( 'Upload Image', 'mytheme' ); ?>" class="button upload_image_button">
<input type="button" name="remove_image_upload" value="<?php esc_html_e( 'Remove Image', 'mytheme' ); ?>" class="button remove_image_button">

Also, for a better appearance, we’ll add the styles

.uploaded_image{display: block; width: 200px;}
.uploaded_image img{width: 200px;}
.featured_image_upload{display: block; margin-bottom: 10px;}

in our inline styles at the top of the custom fields. We want to limit the size of the image container, since you can place a 2000px wide image, and you don’t want to display a 2000px wide image inside the meta box (that would just break the layout and overflow to the page). Also notice that in our code, we added a check to see if the image is present, so that we show the image, only if it’s saved in our meta data. No need to display an empty HTML.

This on its own won’t do much. We need to add some JavaScript to it. We’ve enqueued admin.js file in the previous article to control the slider, and date picker. In the same file we can add

$(document).on( 'click', '.upload_image_button', upload_image_button )
.on( 'click', '.remove_image_button', remove_image_button );

function upload_image_button(e) {
var $this = $( e.currentTarget );
var $input_field = $this.prev();
var $image = $this.parent().find( '.uploaded_image' );
var custom_uploader = ={
title: 'Add Image',
button: {
text: 'Add Image'
multiple: false
custom_uploader.on('select', function() {
var attachment = custom_uploader.state().get( 'selection' ).first().toJSON();
$input_field.val( attachment.url );
$image.html( '<img src="' + attachment.url + '" />' );

function remove_image_button(e) {
var $this = $( e.currentTarget );
var $input_field = $this.parent().find( '.featured_image_upload' );
var $image = $this.parent().find( '.uploaded_image' );

$input_field.val( '' );
$image.html( '' );

We need two functions — one to trigger the upload, and one for the removal of the image. Notice that we used var custom_uploader = =; which is the call to the native WordPress media handle. Here we don’t want to allow multiple images upload, that’s why we added multiple: false, and we are fetching the url of the selected attachment, and adding it to the input field, as well as attaching the <img /> element to the .uploaded_image element. On the remove button click, we just want to find the image, and the value in the input field and we want to remove those (empty them).

Saving the image

Now all we need to do is to save it. Inside the mytheme_save_metaboxes( $post_id ) function, add

if ( isset( $_POST['mytheme_featured_image'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_featured_image', sanitize_text_field( wp_unslash( $_POST['mytheme_featured_image'] ) ) ); // Input var okay.

Gallery upload control

Next, we want to add the gallery upload control. This one is a bit different, although there are similarities with the image upload. First, we add the HTML and PHP part in like with the image control

<label for="mytheme_gallery"><?php esc_html_e( 'Project Gallery', 'mytheme' ); ?></label>
<div class="separator gallery_images">
$img_array = ( isset( $mytheme_gallery ) && '' !== $mytheme_gallery ) ? explode( ',', $mytheme_gallery ) : '';
if ( '' !== $img_array ) {
foreach ( $img_array as $img ) {
echo '<div class="gallery-item" data-id="' . esc_attr( $img ) . '"><div class="remove">x</div>' . wp_get_attachment_image( $img ) . '</div>';
<p class="separator gallery_buttons">
<input id="mytheme_gallery_input" type="hidden" name="mytheme_gallery" value="<?php echo esc_attr( $mytheme_gallery ); ?>" />
<input id="manage_gallery" title="<?php esc_html_e( 'Manage gallery', 'mytheme' ); ?>" type="button" class="button" value="<?php esc_html_e( 'Manage gallery', 'mytheme' ); ?>" />
<input id="empty_gallery" title="<?php esc_html_e( 'Empty gallery', 'mytheme' ); ?>" type="button" class="button" value="<?php esc_html_e( 'Empty gallery', 'mytheme' ); ?>" />

Notice that we are converting our saved list of image IDs into an $img_array. That way we can control the output easier using the for each loop. The last part is almost the same as the image input, but the input field for the image IDs is hidden, since it’s just a list of numbers. To save it we’ll add in our save function that gets called on the save_post action

if ( isset( $_POST['mytheme_gallery'] ) ) { // Input var okay.
update_post_meta( $post_id, 'mytheme_gallery', sanitize_text_field( wp_unslash( $_POST['mytheme_gallery'] ) ) ); // Input var okay.

The important part here is the JavaScript. In our admin.js add

$( document ).on( 'click', '#manage_gallery', upload_gallery_button )
.on( 'click', '#empty_gallery', empty_gallery_button )
.on( 'click', '.gallery-item .remove', empty_single_image );

function upload_gallery_button(e) {
var $this = $( e.currentTarget );
if ( ! $ 'lockedAt' ) || + new Date() - $ 'lockedAt' ) > 300) { // Doubleclick prevent.
var $input_field = $( '#mytheme_gallery_input' );
var ids = $input_field.val();
var gallerysc = '[gallery ids="' + ids + '"]'; gallerysc ).on('update', function(g) {
var id_array = [];
var url_array = [];
$.each(g.models, function(id, img){
url_array.push( img.attributes.url );
id_array.push( );
var ids = id_array.join( "," );
ids = ids.replace( /,\s*$/, "" );
var urls = url_array.join( "," );
urls = urls.replace( /,\s*$/, "" );
$input_field.val( ids );
var html = '';
for (var i = 0 ; i < url_array.length; i++) {
html += '<div class="gallery-item" data-id="' + id_array[i] + '"><div class="remove">x</div><img src="' + url_array[i] + '"></div>';

$( '.gallery_images' ).html( '' ).append( html );
$ 'lockedAt', + new Date() );

function empty_gallery_button(e){
var $input_field = $( '#mytheme_gallery_input' );
$input_field.val( '' );
$( '.gallery_images' ).html( '' );

Array.prototype.remove = function() {
var what, a = arguments, L = a.length, ax;
while (L && this.length) {
what = a[--L];
while ((ax = this.indexOf( what )) !== -1) {
this.splice( ax, 1 );
return this;

function empty_single_image(e){
var $this = $( this );
var value = $this.parent().data( 'id' );
var $this_image = $this.parent().find( 'img' );
var $this_image_url = $this_image.attr( 'src' );
var $input_field = $( '#mytheme_gallery_input' );
var existing_values_arr = $input_field.val().split( ',' );
var new_arr = existing_values_arr.remove( value.toString() );
var replace_str = new_arr.join();
$input_field.val( '' ).val( replace_str );

Now there is a small issue with my syntax highlighter and WordPress automatically detecting the shortcodes in the code above so in the line where it says

var gallerysc = '[gallery ids="' + ids + '"]';

The code should contain just one bracket on each side not two. So just be sure to change this small issue (don’t just copy paste the code :D ).

Notice the addition of the

if ( ! $ 'lockedAt' ) || + new Date() - $ 'lockedAt' ) > 300) { // Doubleclick prevent.
$ 'lockedAt', + new Date() );

code. This is a gem I found on StackOverflow that will prevent any double click issues that happened when you’d click on the ‘Manage gallery’ button. This time we need to call the and not just the frames (single image). The code is a bit longer, but you are basically manipulating the gallery shortcode to show your images. The rest of the code just places images in the .gallery_images element, and the IDs in the hidden input field, so that we can save them. There is also empty gallery code, which just removes everything from the input field and the images element, and the single remove function that will just remove the single image one at the time.

There is also the helper function that is used to remove item from an array by its value (we need it to remove single image from the gallery and the input field).

After this the gallery should work when clicked on the ‘Manage Gallery’ button.

The neat thing about the built in WordPress gallery is that you can add images to it, remove it, reorder the images in it and so on. On the meta box side I’ve added a little x on each image, so that you can remove individual image from the list. You’ll need to add some small inline css for it to be nicely styled:

.post_meta_extras .gallery_buttons input{position: relative; display: inline-block; vertical-align: top; width: auto;}
.post_meta_extras .title{font-size: 14px; padding: 8px 12px 8px 0; margin: 0; line-height: 1.4; font-weight: 600;}
.post_meta_extras .gallery-item{position: relative; display: inline-block; vertical-align: top; margin-right: 10px; margin-bottom: 10px;}
.post_meta_extras .gallery-item img{width: 200px; display: inline-block; vertical-align: top;}
.post_meta_extras .gallery-item .remove{position: absolute; top: 5px; right: 5px; width: 20px; height: 20px; background: #000; border-radius: 50%; border: 1px solid #fff; color: #fff; text-align: center; font-weight: 600;
line-height: 18px; cursor: pointer; display: none;}
.post_meta_extras .gallery-item:hover .remove{display: block;}

The end result looks like this:

Image upload meta box without images

Image upload meta box with no images

Image upload meta box with images

Image upload meta box with images


It’s all nice to have the meta box controls, but how do we use them? For the single image just use the meta url in your page template:

$featured_image = get_post_meta( get_the_ID(), 'mytheme_featured_image', true );

For the gallery it’s a bit different. Since we have attachment IDs we need to fetch them first

$gallery = ( isset( $custom['mytheme_gallery'][0] ) && '' !== $custom['mytheme_gallery'][0] ) ? explode( ',', $custom['mytheme_gallery'][0] ) : '';

And then use for each loop to get the image urls. Then you can even set the background as an inline style and use it as such

foreach ( $gallery as $key => $value ) {
$image_url = wp_get_attachment_url( $value );
$background = ( isset( $image_url ) && '' !== $image_url ) ? 'style="background:url( ' . esc_url( $image_url ) . ' ); -webkit-background-size: cover; background-size: cover; background-repeat: no-repeat; ' . $bg_pos . '"' : '';

I hope you’ll find this tutorial interesting and useful. If you have any questions feel free to post them below, and as always, happy coding :)

Originally published at Made by Denis.