There’s lots of tutorials that discuss how to implement a Load More feature on WordPress, but the majority of them focus on implementing some kind of “pager” that will load more blog posts. While useful, there are many other instances outside of blog posts that we can lean on Ajax to provide a better user experience. In our case, we didn’t want to have to lean on a javascript slider package so we built a custom image gallery that can handle any amount of images without bogging down load times. This tutorial will run through all the steps on how we pieced together an Ajax call in WordPress to build out a custom gallery.

What We Built and How We Did It

The client request was to develop a “gallery” that would dynamically retrieve three images at a time. It looked like this (and you can preview the live demo, as well):

As we said, this could be done using a standard “slider” and just having a lot of hidden slides. However, this particular gallery would potentially be loaded with dozens of images to ensure there was always something new, and we didn’t want all those images bogging down the load time. Even with LazyLoading, we wanted to keep the DOM clean and ensure we were only loading the images that were being displayed on the screen.

There’s a couple of different ways we could have put something like this together. We could register a Gallery post type and limit them to three images a piece and use Ajax to load one post at a time…but that felt clunky to manage for the client within the Dashboard (and there are plenty of tutorials out there on calling new posts via Ajax). We decided that it would be ideal if we could create a single ACF Gallery field that could be placed on individual pages, and only return three items at a time when a user clicks the “Load More” trigger, so we built just that (and thought it would make a great tutorial to share!).

Before we go any farther, let’s just look at the birds-eye-view of a WordPress Ajax call.

Anatomy of the WordPress Ajax Call

An Ajax call in WordPress is comprised of four main steps:

1) Write the template (markup & metadata)
2) Create variables to pass from PHP to JavaScript
3) Configure the Ajax Handler in PHP
4) Create the JavaScript function (the fun part/Ajax Call)

It can be a bit difficult deciding which order to do these steps in, but we found this order to be the most intuitive, since it moves gradually from PHP towards the Javscript you’ll need to implement.

1) Write the template (markup & metadata)

In this example, we used a single ACF Gallery field. How you set up your custom field data is up to you. In our case, since we had a header we wanted to bring along with the gallery, we put the Gallery field in a group:


Then we wrote the markup and field logic. I’ve commented the code as a guide:

$gallery_region = get_field('region_gallery_slider', $post->ID); // Gallery group field
$images = $gallery_region['images']; // gallery images
$gallery_header = $gallery_region['header']; //gallery header
<?php if (!empty($images)) { //only output if the Gallery field has images ?>
<section class="region-gallery">
  <div class="grid gallery-grid">

    <div class="gallery-entry gallery-nav">
      <div class="inner">
        <a href="" id="load-gallery"><img src="<?php bloginfo('template_directory');?>/assets/img/svg/icon-load-more.svg" alt="Load More Icon"><span class="button-text">See More</span></a>
<!-- this is the toggle which we'll be hooking up to the Javascript Ajax call -->

    <?php foreach ($images as $image): // output images ?>
    <div class="gallery-entry gallery-image">
      <?=wp_get_attachment_image($image['id'], 'gallery-slider');?>
    <?php endforeach;?>
<?php }?>

At this point, we were outputting all the images at once. We only needed three random images at a time, so we wanted to shuffle them and make sure we only get three back at once. We replaced our foreach function above with this:

$i = 0;
shuffle($images); // shuffle the array
  foreach ($images as $image): 
    if($i >= 3) { // if iteration is greater than or equal to 3, do not proceed further
    else { // otherwise, output the images ?>
<div class="gallery-entry gallery-image">
  <?=wp_get_attachment_image($image['id'], 'gallery-slider');?>
$i++; // increment our iterator for each index in the array 

At this stage, this should all be fairly familiar to you: we have an ACF image gallery that is outputting three random/shuffled images on each page load. Styling with CSS is up to you on whether you do that at this point or not, but we like to do that early on. Your individual gallery is not likely to look anything like this, and this tutorial is not dependent on any particular CSS to work, but here is the CSS for this particular gallery, if you find it helpful.

2) Create variables to pass from PHP to JavaScript

Now that the markup/metadata/logic preparation was done, we shifted focus to the Ajax preparation. The first step was to create some variables to be linked between PHP and Javascript.

We used the wp_add_inline_script() function in WordPress which, for our purpose, exposed a data object that we could use within a script. Code is below, with comments:

function gallery_load_more_init() {
  $post_id = get_the_ID(); // we'll be using this later to grab the gallery field on the page
 wp_register_script('gallery_ajax', false); // register the script with no dependencies
 wp_add_inline_script( 'gallery_ajax', 'const gallery_params = ' . json_encode( array( // note: "gallery_params" is script object
   'ajaxurl' => site_url() . '/wp-admin/admin-ajax.php', //this is the WordPress Ajax URL
   'post_id' => $post_id, // reference the Post ID declared above
   'security' => wp_create_nonce('gallery-nonce'), // create an nonce for security hardening
   ) ), 'before' );
add_action('wp_enqueue_scripts', 'gallery_load_more_init'); // render the script on the page

Great, now we had dynamic PHP variables being created within PHP, and being rendered on the page as JavaScript variables:

Still, this was all preparation, and nothing was actually making the Ajax call yet. The next two steps hooked up the Ajax handler to our metadata, and finally initialized the call in JavaScript.

3) Configure the Ajax Handler in PHP

The Ajax Handler is the action that happens when we “trigger” the Ajax call. Think of this part as what data you want to be dynamically retrieved and injected into the page once the user clicks the Ajax trigger (in our case, the Load More button).

(keep in mind that all this code is directly relevant to the slider being built here, but your particular markup and data architecture will likely be different)

Code below, with comments:

function gallery_ajax_handler() {

 if (!check_ajax_referer('gallery-nonce', 'security', false)) { // set up our security nonce
  wp_send_json_error('Invalid security token sent.');
  wp_die(); // if no match, exit
 $output = ""; // open the output for concatenation
 $post_id = $_POST['post_id']; // our post_id variable tied to our custom field on the page, referenced in our inline script
 $gallery_region = get_field('region_gallery_slider', $post_id); // ACF group field with the specific post/page ID being viewed
 $images = $gallery_region['images']; // Gallery field
 $i = 0;
 shuffle($images); //note the next lines are the same as the PHP we wrote in step one!
 foreach ($images as $image):
  if ($i >= 3) {
  } else {
   $output .= "<div class='gallery-entry gallery-image gallery-loaded'>";
   $output .= wp_get_attachment_image($image['id'], 'gallery-slider');
   $output .= "</div>";

 echo $output; // output $images

 wp_die(); // always include this after Ajax calls

add_action('wp_ajax_load_gallery', 'gallery_ajax_handler');
add_action('wp_ajax_nopriv_load_gallery', 'gallery_ajax_handler');

Note these two lines contain specific naming conventions:

add_action('wp_ajax_load_gallery', 'gallery_ajax_handler');
add_action('wp_ajax_nopriv_load_gallery', 'gallery_ajax_handler');

The naming convention will always be:

  • wp_ajax_{your_action_name} (fires authenticated Ajax actions for logged-in users)
  • wp_ajax_nopriv_{your_action_name} (fires non-authenticated Ajax actions for logged-out users)

4) Create the JavaScript function (the Ajax Call)

OK, now we had almost everything in place:

  • We had metadata configured to handle data, and our markup was outputting it on the page
  • We had a script creating dynamic PHP variables and rendering them as Javascript variables on the page
  • We had our Ajax action set up to handle the custom data we wanted to retrieve and return, once the Ajax call was triggered

The last step was the fun part: actually making the Ajax call! We were writing mostly in PHP all this time, but now we needed to pivot over to JavaScript to tie this all together. We leaned on jQuery to run our Ajax call, since it’s already packaged with WordPress, and is arguably a lot more concise and elegant than writing a vanilla JS XMLHttpRequest, especially since jQuery has a lot of built in callbacks. Here is the JS function that load triggered the Ajax Handler we implemented, code below with comments:

$('#load-gallery').on('click', function (e) { // when our trigger is clicked

    e.preventDefault(); // prevent any default behaviors from this anchor

      button = $(this), // trigger
      imageEntry = $(''), //images
      data = { // JS data object with variables defined in PHP Ajax handler
        'action': 'load_gallery', // Ajax function defined in handler that contains data we're pulling in
        'post_id': gallery_params.post_id, // inline post ID variable
        "security": // inline nonce variable

    $.post({ // can also use $.ajax and declare 'post' type, but this is a shortcut in newer jQuery versions
      url: gallery_params.ajaxurl, // inline WordPress Ajax URL variable defined in handler
      data: data, // object defined above
      beforeSend: function (xhr) { // runs before ajax request is sent

        // add loading state. you can do any number of things here, get creative! 

      success: function (data) { // runs right after Ajax call succeeds
        if (data) { // only run if the data object exists
          //remove loading state
          button.find('.button-text').text('See More'); 

          imageEntry.remove(); // clear existing slides
          $('').after(data); // append data (after the nav element, in our case)


How you decide to implement this script/function is up to you; a lot of tutorials have you register this script independently and enqueue it on the page as a new request to the JS file. Since we like to include all our JS in a build system to reduce HTTP requests, we are placing this file in our minified JS file that is enqueued with our custom themes.

Success! Our slider was now pulling in three random new images dynamically from an ACF Gallery (I’ve also placed some CSS transitions to the images as they load).

You can also check out the live demo here, and try it for yourself!

To recap, here’s what is happening:

  • HTML element/trigger (in our case, #load-gallery anchor tag) is clicked
  • We run an jQuery Ajax call ($.post shortcut method), which fires the load_gallery action using the inline variables from our gallery_load_more_init() inline script
  • The load_gallery action declared in our gallery_ajax_handler runs (and checks security nonce)
  • It proceeds to run get_field() for the post ID being viewed, and retrieves our Gallery field from our page. At that point, it runs a standard PHP foreach returning three random images
  • Those images are returned as a data object from the load_gallery action
  • That data object is appended via JavaScript into our template markup (within the success callback in our Ajax call)
  • User sees three new images

Once you get your head around the approach of enqueuing your script variables and setting up the Ajax action (which is really the meat of it, since it is the method which retrieves/returns the data from the database dynamically), the rest is pretty standard custom web development that you’re likely used to doing.

And once you do understand how to get data from WordPress and inject it dynamically into a page*, you can do so much more! You can check out a tutorial on how to implement a Load More function for blog posts, or you can do something like loading your search results using Ajax. Leaning on Ajax can provide a very custom and “top-shelf” UX to your custom sites, and in use-cases like the one described here, can also be a boon to page load time, as well!

*It’s probably prudent to mention that using WordPress’ “Admin Ajax” approach is only one method you can use to retrieve data with Ajax, since you can also use the REST API method, which is more recent/modern, but not necessarily “better”. Both methods have their pros/cons, so read up on which one is desirable for your particular needs. We’ve opted for the Admin Ajax route, since we really wanted the response to be returned in HTML, rather than JSON.