Galleries are an awesome way to show simple pictures in your posts. In this tutorial we will create a WordPress gallery that will show on the post on which we create it. We will also be able to decide the order of the images in our gallery.

The gallery will be attached to a single post so we will have to create a metabox for each post in the admin area where we will insert the gallery images. Interested? Let’s begin:)

All the code you find here can be used in any of your projects both personal and commercial.

We will make a class to handle our WordPress gallery (saving images, loading images, rendering images) and we will also create a class that could be a factory class which will create all the registered galleries. By doing that, we could have two or more galleries on each post. By using the OOP approach you can extend the initial class for the WordPress Gallery and create different gallery rendering.

Please do be patient since this is a long article and the galleries will work once we create the necessary JavaScript at the end of this article.

If you want to see what we are going to build, you have a video at the bottom of this article. Click here to see it.

WordPress Gallery

First, we will create our class that will handle the creation of the metabox and the rendering of the gallery.

We define our attributes that are used to create the metabox with the function add_meta_box. The attribute $id will be used for more than just create the metabox. We will create it when saving the galleries for meta keys in the database and also for using it within the JavaScript code.

<?php
/**
* Sortable WordPress Gallery class to create gallery metabox and save it
*/
class Sortable_WordPress_Gallery {
// ...
/**
* Hook into the appropriate actions when the class is constructed.
*/
public function __construct( $id, $title = '', $context = 'advanced', $priority = 'high' ) {
// ID is required
if( $id == '' ) {
return;
}
$this->id = $id;
// Create title from ID if not provided
if( $title == '' ) {
$this->title = ucfirst( $id );
} else {
$this->title = $title;
}
// Only valid context can be changed
$available_context = array(
'advanced',
'side',
'normal');
if( in_array( $context, $available_context ) ) {
$this->context = $context;
}
// Only valid priorty can be changed
$available_priority = array(
'default',
'high',
'low');
if( in_array( $priority, $available_priority ) ) {
$this->priority = $priority;
}
add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ) );
add_action( 'save_post', array( $this, 'save' ) );
add_filter( 'the_content', array( $this, 'render' ) );
}
// ...
}

In our constructor method we are assigning passed parameters to our attributes. If the title is empty, we will create it from the ID by capitalizing the first character. We do also check if the passed context and priority values are valid. Only if they are valid we will set them. I they are not valid, they will remain with the default value.

At the end of our constructor method we hook two of our methods to actions (one to add the metabox and one to save the metabox values). Let’s first define our method for adding the metabox:

<?php
/**
* Sortable WordPress Gallery class to create gallery metabox and save it
*/
class Sortable_WordPress_Gallery {
// ...
/**
* Adds the meta box container.
*/
public function add_meta_box( $post_type ) {
// Limit meta box to certain post types.
// Can be extended using filter 'sortable_wordpress_gallery_post_types' or
// using a dynamic filter 'sortable_wordpress_gallery_METABOX-ID_post_types'
$post_types = apply_filters( 'sortable_wordpress_gallery_post_types', array( 'post' ) );
$post_types = apply_filters( 'sortable_wordpress_gallery_' . $this->id . '_post_types', $post_types );
if ( in_array( $post_type, $post_types ) ) {
add_meta_box(
$this->id,
$this->title,
array( $this, 'render_meta_box_content' ),
$post_type,
$this->context,
$this->priority
);
}
}
// ...
}

We are defining possible post types on which our metabox will render. The post types can be easily extended using a general filter for the galleries or using a dynamic filter that can be used to specifically target only one metabox.

If the post type we are editing in the admin area matches the ones that are available to that metabox, we will add the metabox. Here we are using another method called render_meta_box_content. This method is used to display the metabox and all the information inside it. Let’s define that now.

Rendering the metabox

<?php
/**
* Sortable WordPress Gallery class to create gallery metabox and save it
*/
class Sortable_WordPress_Gallery {
// ...
/**
* Render Meta Box content.
*
* @param WP_Post $post The post object.
*/
public function render_meta_box_content( $post ) {
// Add an nonce field so we can check when saving
wp_nonce_field( $this->id . '_metabox', $this->id . '_metabox_nonce' );
// Get the gallery saved data
$sortable_gallery = get_post_meta( $post->ID, '_' . $this->id . '_sortable_wordpress_gallery', true );
?>
<table class="table">
<tr>
<th style="vertical-align: top;">
<?php _e( 'Slider Images', 'your_text_domain' ); ?>
</th>
<td>
<!-- Creating a dynamic ID using the metabox ID for JavaScript-->
<ul id="<?php echo $this->id; ?>_sortable_wordpress_gallery" class="sortable_wordpress_gallery">
<?php
// Getting all the image IDs by creating an array from string ( 1,3,5 => array( 1, 3, 5) )
$gallery = explode(",", $sortable_gallery );
// If there is any ID, create the image for it
if( count( $gallery ) > 0 && $gallery[0] != '' ) {
foreach ( $gallery as $attachment_id ) {
// Create a LI elememnt
$output = '<li tabindex="0" role="checkbox" aria-label="' . get_the_title( $attachment_id ) . '" aria-checked="true" data-id="' . $attachment_id . '" class="attachment save-ready selected details">';
// Create a container for the image. (Copied from the WP Media Library Modal to use the same styling)
$output .= '<div class="attachment-preview js--select-attachment type-image subtype-jpeg portrait">';
$output .= '<div class="thumbnail">';
$output .= '<div class="centered">';
// Get the URL to that image thumbnail
$output .= '<img src="' . wp_get_attachment_thumb_url( $attachment_id ) . '" draggable="false" alt="">';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>';
// Add the button to remove this image if wanted (we set the data-gallery to target the correct gallery if there are more than one)
$output .= '<button type="button" data-gallery="#' . $this->id . '_sortable_wordpress_gallery" class="button-link check remove-sortable-wordpress-gallery-image" tabindex="0"><span class="media-modal-icon"></span><span class="screen-reader-text">Deselect</span></button>';
$output .= '</li>';
echo $output;
}
}
?>
</ul>
<!-- Hidden input used to save the gallery image IDs into the database -->
<!-- We are also creating dynamic IDs here so that we can easily target them in JavaScript -->
<input type="hidden" id="<?php echo $this->id; ?>_sortable_wordpress_gallery_input" name="_<?php echo $this->id; ?>_sortable_wordpress_gallery" value="<?php echo $sortable_gallery; ?>" />
<!-- Button used to open the WordPress Media Library Modal -->
<button type="button" class="button button-primary add-sortable-wordpress-gallery" data-gallery="#<?php echo $this->id; ?>_sortable_wordpress_gallery"><?php _e( 'Add Images', 'your_text_domain' ); ?></button>
</td>
</tr>
</table>
<?php
}
// ...
}

Feels intimidating? No need. I have commented the most important parts so that you can easily understand what each line does. Once we go into JavaScript, you will have a more clearer understanding on this.

Basically, we are generating a hidden WordPress Nonce field so that we can check on that before saving the data to make it more secure. After that we are getting the image IDs we have saved for that gallery. We use PHP function explode to generate an array from the provided string of image IDs. We check if there are any images and if there are, then we create a LI element with the image inside it for every image.

The HTML inside the LI element and the LI element alone are the same as provided in WordPress Media Library Modal. I decided to use that so that I can mimic the same style used in that modal. At the end of this method we render a button that will be used to open the WordPress Media Library so that we can choose between the images.

Saving the WordPress Gallery data

Now that we have our metabox output defined, we can focus on that second method in our constructor method. Remember it? The method name is save:

<?php
/**
* Sortable WordPress Gallery class to create gallery metabox and save it
*/
class Sortable_WordPress_Gallery {
// ...
/**
* Save the meta when the post is saved.
*
* @param int $post_id The ID of the post being saved.
*/
public function save( $post_id ) {
/*
* We need to verify this came from the our screen and with proper authorization,
* because save_post can be triggered at other times.
*/
// Check if our nonce is set.
if ( ! isset( $_POST[ $this->id . '_metabox_nonce'] ) ) {
return $post_id;
}
$nonce = $_POST[ $this->id . '_metabox_nonce'];
// Verify that the nonce is valid.
if ( ! wp_verify_nonce( $nonce, $this->id . '_metabox' ) ) {
return $post_id;
}
/*
* If this is an autosave, our form has not been submitted,
* so we don't want to do anything.
*/
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return $post_id;
}
// Check the user's permissions.
if ( 'page' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
/* OK, it's safe for us to save the data now. */
$gallery = sanitize_text_field( $_POST[ '_' . $this->id . '_sortable_wordpress_gallery' ] );
// Update the meta field.
update_post_meta( $post_id, '_' . $this->id . '_sortable_wordpress_gallery', $gallery );
}
// ...
}

This is a generic save method since we first check for the nonce. If there is a nonce we verify it, if not we don’t save anything. If there is a nonce, then we are verifying it. If our nonce if verified we do check it is not an autosave and also if the user has the permission to edit the content.

Once everything is true and valid we are getting the posted data from our metabox (look at the hidden input we have set in the method render_meta_box_content – we are using that name). We are also sanitizing the field to remove any harmful data. The last thing, we are saving the metabox data (image IDs) in the post meta by using the dynamic meta key constructed from the metabox ID.

Rendering the WordPress Gallery

The last thing we have to do inside this class is to define a rendering method. This rendering method will be called inside our factory class. The method will receive the parameter $content which will contain the posts’ content and all other filtered content (if any). We will have to add our gallery inside that content and return it.

<?php
/**
* Sortable WordPress Gallery class to create gallery metabox and save it
*/
class Sortable_WordPress_Gallery {
// ...
/**
* Rendering the gallery
*/
public function render( $content ) {
global $post;
// Get our registered post types
$post_types = apply_filters( 'sortable_wordpress_gallery_post_types', array( 'post' ) );
$post_types = apply_filters( 'sortable_wordpress_gallery_' . $this->id . '_post_types', $post_types );
// Get current post type
$this_post_type = get_post_type( $post );
// If the current post type is also a registered post type, create the gallery
if( in_array( $this_post_type, $post_types ) ) {
// Get all the image IDs
$gallery_images = get_post_meta( $post->ID, '_' . $this->id . '_sortable_wordpress_gallery', true );
if( $gallery_images != '' ) {
// If the data retrieved is not empty, create an array
$gallery_images_array = explode( ',', $gallery_images );
//Create a gallery container with the 3 column layout
$content .= '<div class="gallery gallery-columns-3">';
// For each image ID, create the gallery image
foreach ( $gallery_images_array as $image_id ) {
$html = wp_get_attachment_image( $image_id );
if( $html == '' ) {
continue;
}
$content .= '<figure class="gallery-item">';
$content .= '<a href="' . get_permalink( $image_id ) . '" target="_blank">';
$content .= $html;
$content .= '</a>';
$content .= '</figure>';
}
$content .= '</div>';
}
}
return $content;
}
}

So to render our gallery we are first checking if the current post, article or any content has the post type that is allowed for our meta box. If that post type is allowed then we are getting the data from the database and create a gallery with the 3 column layout. The classes used here are the same classes WordPress uses when creating galleries. By doing that, we are ensuring that our gallery is somewhat compatible with WordPress galleries so if there is any JavaScript that is creating gallery pop-ups or similar, it will also affect our gallery.

WordPress Gallery Factory

We have our main WordPress gallery class defined. By using a factory we can easily set more than one gallery on a post or similar. We could define several galleries and by using our dynamic filters, we can decide which gallery to show on which post type. This factory will create our galleries and also it will enqueue any script or style that is needed for our galleries.

We will enqueue only admin script and style so that our metabox galleries are render as intended. The front side will be handled by your theme or another plugin such as Jetpack so I will leave you decide how to style your galleries.

As with any class, we first define our attributes:

  • $gallery_defaults – the defaults that can be used when instantiating our galleries
  • $galleries – array with every gallery we want to create
  • $loaded_galleries – array with every instantiated gallery

In our constructor method we hook the method do different actions and one filter. First we hook into the action init to get all the galleries that were added via our filter that we will define in the method collect_galleries. Next step is to enqueue all the scripts and styles that are needed for our galleries to work in the admin area.

After that we hook into the action admin_init so that we are sure that the metabox is created only in the admin area. The only filter into which we hook our method is the filter the_content where we will render galleries.

We are enqueueing our scripts and styles using the URL as if we are using a plugin. If you are using this code in your theme, then you should use the method get_template_directory_uri()Both files will be defined after we create our whole WordPress gallery factory class.

Our method collect_galleries is used to collect all galleries that are added in the filter sortable_wordpress_galleries. We are almost done. Now we need to render those galleries in the admin area and also in the content.

In the method create_galleries we are hooking the method load_galleries to the actions inside the edit screens. These method will be used to instantiate every gallery that was collected. By instantiating our galleries, our metaboxes will be created.

The last part is the part where we render our galleries. We are first making sure that our galleries are loaded. Once that is done, we are going through all the loaded galleries and call their method render and passing the $content to them. Each gallery can have a different class and have a different rendering method so by doing it in this way, we are sure that our galleries will be rendered as intended.

Style for our metaboxes

The style is really minimum since we are using classes that the WordPress Media Uploader modal is using. We just have to define some simple CSS rules to display our images in the metabox correctly. This is our style:

.sortable_wordpress_gallery li.attachment {
width: 141px;
height: 141px;
}
.sortable_wordpress_gallery {
display: block;
}
.sortable_wordpress_gallery:after {
display: table;
content: '';
clear: both;
}
view raw admin.css hosted with ❤ by GitHub

JavaScript functionality

This is the last part of this tutorial. If you have made it this far, I congratulate you already! This is the last piece of code that will connect everything. After this you can view a few of the examples on how to add different galleries. Let’s finish this!

I will separate our script file into several parts so that you could easily understand every bit of the code. First, we will create the function that will be used to open the WordPress Media Uploader and also add images to our input and WordPress gallery metabox.

function sortable_image_gallery_media( $imageContainer, $imageInput ) {
'use strict';
var file_frame;
/**
* If an instance of file_frame already exists, then we can open it
* rather than creating a new instance.
*/
if ( undefined !== file_frame ) {
file_frame.open();
return;
}
/**
* If we're this far, then an instance does not exist, so we need to
* create our own.
*
* Here, use the wp.media library to define the settings of the Media
* Uploader. We're opting to use the 'post' frame which is a template
* defined in WordPress core and are initializing the file frame
* with the 'insert' state.
*
* We're also not allowing the user to select more than one image.
*/
file_frame = wp.media({
multiple: true,
});
file_frame.on('open',function() {
var selection = file_frame.state().get('selection');
var ids = $imageInput.val().split(',');
ids.forEach(function(id) {
var attachment = wp.media.attachment(id);
attachment.fetch();
selection.add( attachment ? [ attachment ] : [] );
});
});
// When an image is selected in the media frame...
file_frame.on( 'select', function() {
// Get media attachment details from the frame state
var attachments = file_frame.state().get('selection').toJSON();
var attachmentIDs = [];
$imageContainer.empty();
var $galleryID = $imageContainer.attr("id");
for( var i = 0; i < attachments.length; i++ ) {
if( attachments[ i ].type == "image" ) {
attachmentIDs.push( attachments[ i ].id );
$imageContainer.append( sortable_gallery_image_create( attachments[ i ], $galleryID ) );
}
}
$imageInput.val( attachmentIDs.join() );
sortable_gallery_image_remove();
});
// Now display the actual file_frame
file_frame.open();
}
view raw admin.js hosted with ❤ by GitHub

We are passing two parameters to our function:

  • $imageContainer – gallery container of our metabox
  • $imageInput – the hidden input of our metabox

Here we define a variable file_frame that will hold our WordPress Media Uploader pop-up. Since we do not want the pop-up to get initialized every time we click on the button, we are checking if the variable file_frame is already set. If it is set, then we will open the previously opened WordPress Media Uploader.

If file_frame is not set, then we need to define the WordPress Media Uploader pop-up. We initialize it and allow multiple selection. After that we define a function that will get called once the frame is open. Once it is open, we get the selection object and the image IDs from our hidden input. We are immediately creating an array of the image IDs from the hidden input by splitting the value with each comma (,).

For each image ID we create a WordPress Media attachment and fetch the data for that attachment. The last thing we do is add the attachment to the selection. Now every time the WordPress Media Uploader is open, all our images will be selected so that we do not have to select them all again.

Now that we defined what our file_frame will do when it is opened, we need to define also the function that will get triggered when we select all the images and click the insert button. When we click the insert button this will happen:

  • get all the attachments from our selection
  • create an empty array attachmentIDs that will hold all the selected image IDs
  • delete all the images in our gallery container so that we have an empty container when setting new ones
  • for every selected item that is an image we push its ID to our array attachmentIDs, create an image out of it and append it to our gallery container
  • once we passed through all our selected items, we set the value of our hidden input by joining all the IDs with commas
  • last part is to call the remove function to attach that functionality to newly added images

Once everything has been set we just need to open our WordPress Media pop-up. We have two functions which are used in the above code that have to be defined. We will now define the function sortable_gallery_image_create that is used to create the image out of the attachment object.

function sortable_gallery_image_create( $attachment, $galleryID ) {
var $output = '<li tabindex="0" role="checkbox" aria-label="' + $attachment.title + '" aria-checked="true" data-id="' + $attachment.id + '" class="attachment save-ready selected details">';
$output += '<div class="attachment-preview js--select-attachment type-image subtype-jpeg portrait">';
$output += '<div class="thumbnail">'
$output += '<div class="centered">'
$output += '<img src="' + $attachment.sizes.thumbnail.url + '" draggable="false" alt="">'
$output += '</div>'
$output += '</div>'
$output += '</div>'
$output += '<button type="button" data-gallery="#' + $galleryID + '" class="button-link check asap-image-remove" tabindex="0"><span class="media-modal-icon"></span><span class="screen-reader-text">Deselect</span></button>'
$output += '</li>';
return $output;
}
view raw admin2.js hosted with ❤ by GitHub

In this function we create the output for one image. The HTML here is the same as we did the one in our class Sortable_WordPress_Gallery and method render_meta_box_content. The only difference here is that we populate it from the parameters we pass into this function.

function sortable_gallery_image_remove( ) {
jQuery(".remove-sortable-wordpress-gallery-image").on( 'click', function(){
$id = jQuery(this).parent().attr("data-id");
$gallery = jQuery(this).attr("data-gallery");
$imageInput = jQuery( $gallery + "_input" );
jQuery(this).parent().remove();
var ids = $imageInput.val().split(',');
$idIndex = ids.indexOf( $id );
if( $idIndex >= 0 ) {
ids.splice( $idIndex, 1 );
$imageInput.val( ids.join() );
}
});
}
view raw admin3.js hosted with ❤ by GitHub

The removal function is attaching the function to every element with the class .remove-sortable-wordpress-gallery-image. When we click on that element we are getting the attributes values:

  • attachment ID from data-id on LI element (parent of our .remove-sortable-wordpress-gallery-image element),
  • gallery ID from data-gallery on the clicked element

We also get the hidden input of our gallery by constructing the ID from the gallery ID and _input suffix. After that we remove our LI that was holding the image and the clicked element, we split the IDs from the hidden input to array and we get the index of the attachment ID in the array. If the index is 0 or higher then we remove it from the array. Once it is removed, we join all the other IDs to a string and set the value back to the hidden input.

Now all our functionality is done. The only part to do is to create a call on the button for adding images.

(function($){
$(document).ready(function(){
var imageButton = $(".add-sortable-wordpress-gallery");
sortable_gallery_image_remove();
imageButton.each( function(){
var galleryID = $(this).attr("data-gallery");
var imageContainer = $( galleryID );
var imageInput = $( galleryID + "_input" );
imageContainer.sortable();
imageContainer.on( "sortupdate", function( event, ui ) {
$ids = [];
$images = imageContainer.children("li");
$images.each( function(){
$ids.push( $(this).attr("data-id") );
});
imageInput.val($ids.join());
} );
$(this).on('click', function(){
sortable_image_gallery_media( imageContainer, imageInput );
});
});
});
})(jQuery)
view raw admin4.js hosted with ❤ by GitHub

We are making sure that the document is ready and that all has been already rendered. We get the image buttons by the class .add-sortable-wordpress-gallery and we also call the function to attach the removal functionality of each of the images that are already there. For each of those buttons we get the gallery ID and the hidden input ID. We also get the image container and make the children of that container sortable.  For our ordering to be possible, we need to update the sequence of our IDs in the hidden inputs’ value. This is done by calling a function once the sorting is done.

When the sorting is done, we get all the children elements inside that image container and for each of those elements we get their IDs and push them to an empty array. Once every elements’ ID has been pushed, we join the array elements by commas and set that new value to our hidden input.

Last thing is to create a click trigger on the button to open our WordPress Media pop-up by calling the previously defined function sortable_image_gallery_media.

Now we have our WordPress Gallery done and we can use them as we want.

WordPress Gallery Examples

Here are some of the examples on how to add WordPress Gallery metaboxes.

Only one Gallery

<?php
add_filter( 'sortable_wordpress_galleries', 'add_sortable_gallery' );
function add_sortable_gallery( $galleries ) {
$galleries[] = array(
'class' => 'Sortable_WordPress_Gallery',
'id' => 'post-metabox',
'title' => 'Sortable Gallery'
);
return $galleries;
}

Two Galleries

<?php
add_filter( 'sortable_wordpress_galleries', 'add_sortable_gallery' );
function add_sortable_gallery( $galleries ) {
$galleries[] = array(
'class' => 'Sortable_WordPress_Gallery',
'id' => 'post-metabox',
'title' => 'Sortable Gallery'
);
$galleries[] = array(
'class' => 'Sortable_WordPress_Gallery',
'id' => 'post-metabox-2',
'title' => 'Sortable Gallery-2'
);
return $galleries;
}

Show second Gallery only on Page

<?php
add_filter( 'sortable_wordpress_gallery_post-metabox-2_post_types', 'second_gallery_only_on_page' );
function second_gallery_only_on_page( $post_types ) {
return array( 'page' );
}

Video

Conclusion

Galleries can be really useful from time to time. In this tutorial we have learned how to create a simple sortable WordPress gallery that can be easily extended if needed. By using the OOP approach and a dynamic way of assigning values in JavaScript we can have more than one gallery on the same page and yet use the same code.

Have you ever created your own gallery? Do you have some galleries you like or plugins that you use for creating galleries? Share everything in the comments below!

Become a Sponsor

Posted by Igor Benic

Web Developer who mainly uses WordPress for projects. Working on various project through Codeable & Toptal. Author of several ebooks at https://leanpub.com/u/igorbenic.

10 Comments

  1. Thx again man! Great article!!!!

    Reply

  2. This is totally awesome! Thank you!

    Reply

  3. Thank you very much! I spent the last two days trying to make work something like this. I’ll tweet about it ASAP. You have very useful tutorials.

    Reply

  4. Please help me understand this part:

    add_action( ‘add_meta_boxes’, array( $this, ‘add_meta_box’ ) );

    How does that array() part works?

    I mean, should not it be like:

    add_action( ‘add_meta_boxes’, $this->add_meta_box() );

    Reply

    1. Hi Kamrul,

      the second parameter of the add_action is expected to be a function. The add_action is using the function call_user_func_array to call the passed function to execute: https://www.php.net/manual/en/function.call-user-func-array.php

      You can look at the first examples to see what is going on here.

      Basically, the $this part is referencing the object with the method. Since this is added inside of the same object with the function, we can pass $this.

      The second parameter is then the method inside of that object which is being called.

      Reply

  5. Great tutorial! I’ve been looking for a long time a gallery tutorial such as this one.

    I saw that the gallery is displayed in the front end just after the main content. I would like to choose where to display the gallery in my PHP post template. How could I achieve it?

    Thanks a lot!

    Reply

    1. This code is for the gallery metaboxes on the back end. You can use a similar logic to render the gallery used here, to load it on the front.

      Reply

      1. Mathieu Préaud June 10, 2020 at 10:29 am

        Thanks Igor for the reply. I’m not an expert in PHP but I understand that to retrieve the gallery in the front end outside of the_content() we need to create a new function, right?. Is there any chance you can help with this? Thanks a lot!

        Reply

  6. Awesome tutorial!

    If for example the metaboxes are only displayed on pages, how could we filter to display them in admin on a specific page template (like page-template.php)?

    Thanks a lot.

    Reply

Leave a reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.