How To Create Custom AJAX Posts Load More Plugin In WordPress


  1. This tutorial will explain how to create your own custom AJAX load more plugin very easily and responsively
  2. This plugin is developed using plain JS instead of the jQuery library
  3. This plugin will load a set of posts of the specific category in grid-card fashion with a load more button
  4. To load remaining posts of that specific category click the load more button
  5. You can download the plugin source from GitHub
  6. Each card holds post image, title, excerpt and read more button for reference you can check the below image
Single Card of 3 infinite scroll grid

Let’s Explore The ShortCode

  1. As described in the readme file of the GitHub source the shortcode can be expressed as [vg_ajax_posts category_name="post-formats" posts_per_page="6"]
  2. In the above shortcode vg_ajax_posts is the shortcode’s name which has two attributes
  3. The first attribute is category_name which accepts any post category slug
  4. The second attribute is posts_per_page which accepts the maximum number of posts to show on every fetch

Let’s Explore The Plugin’s main class

  1. This 100 lines of code are the kernel of this plugin lets we inspect each method of the class VG_Infinite_Scroll in its own sections
  2. If you closely noted all the methods are static method to callback the method in hooks without object instantiation

Method get_custom_posts:

  1. line 24 : the method accepts a single parameter $atts which is an array that contains key-value options passed in the shortcode
  2. line 28: we calling WordPress function shortcode_atts with the first parameter as default value array and the second parameter as user passed value through shortcode
  3. The function shortcode_atts will check if the user passed keys are available in the first parameter if present then it will push that to output array which is returned by this function if not present then the default value is considered and push to the output array
  4. If any attribute’s key passed in the shortcode is not match with any key of the default array then that attributes are simply ignored
  5. line 33: we calling Transient API function set_transient which set or update the value to the given key
  6. The transient data can be cached timely fashion say 1 day or no expiration this can be in database or Redis or Memcache based on your configuration by default the transient cache storage is the database
  7. This function accepts three parameters they are transient name, transient value, TTL (Time To Live) the third parameter accepts some predefined constants as we passed DAY_IN_SECONDS
  8. The reason here why we add our shortcode attributes to the transient data is we can use it in AJAX call
  9. line 34: we call the static method posts_ui which return the HTML structure of the series of posts of the specific category we will see more about this function on its own section as for now just note that it returns the HTML of our cards structure
  10. line 36: we wrap our series of cards into the parent HTML which forms 3 columns or 1 column card(s) based on the screen resolution to be clear it returns an HTML to render to form the grid of posts in the card layout
  11. This static method is hooked into the shortcode with the shortcode name vg_ajax_posts have you noted this is the name we used to call our shortcode in the backend page or post editor

Method posts_ui:

  1. The posts_ui is the private static method which is used to generate the cards HTML and return that HTML
  2. Line 42: we pass our shortcode attributes to the function set_query_var the purpose of this function is globalizing the value by assigning the key-value pair to the global variable $wp_query here the first parameter is the key of data type string and the second parameter is the value of data type mixed
  3. Line 44: called the one of the PHP output control function ob_start() which initiates the output buffering it means all the generated output will be stored in an internal buffer
  4. Line 45: called the method load_template which will output the template by referencing the path which we passed as the first parameter
  5. The rendered HTML is stored in a temporary buffer
  6. Line 46: called the PHP function ob_get_contents() which returns the content of the output buffer which we initiated at line 44 and assign it to the variable $output
  7. Line 47: called the PHP function ob_end_clean() which clean and silently discard the buffer contents
  8. Line 49: return the rendered HTML lets we see about that template file which is located in the plugin root path templates/vg-posts-content.php
  1. Line 2: instantiates a WP_Query object with the parameter $att assign it to the variable $vg_posts_ui_query
  2. Line 4: we call WP_Query member method have_posts() in a conditional statement to check if we have posts for our queried query
  3. If we have posts for our query the conditional statement will allow further execution as the method have_posts() return boolean True
  4. Line 6: method the_post() retrieves the next post in every loop till the loop ends
  5. The remaining HTML part between the <article>...</article> tag is a self-explanatory one

Method fetch_remaining_posts:

  1. This method is used for AJAX call to fetch remaining posts using offset
  2. Line 59: we call the function get_transient with the key get_custom_posts_att which retrieve the transient value
  3. Line 60: we fetch our offset post data from AJAX and push it to the array variable $att with the associative index offset
  4. Line 62: we call our template rendering method posts_ui with the query parameter passed through $att
  5. Finally, we terminate the method call using the function die()

Method get_post_count:

  1. This method is used to get the total number of posts present in a queried post category
  2. Line 73: we fetch our shortcode’s transient data
  3. Line 74: to fetch total count we have to unset our posts_per_page element from the transient data
  4. Line 76: instantiate WP_Query with arguments to filter
  5. Line 78: return count of the number of posts found

Method add_assets:

  1. This method is responsible for importing static content like JS, CSS and pass data to client end i.e. to HTML page
  2. Line 88 & 89: we import our plugin’s required JS & CSS using the enqueuing process
  3. Line 90: function wp_localize_script is used to passing data to main.js by using same handle name as we used in enqueuing the main.js file in the first argument, the second argument string value is used as JS object in the frontend, the third argument holds the object’s properties as key-value pair

Hooking Class Method

  1. Line 97: The function add_shortcode creates a new shortcode here with the name vg_ajax_posts
  2. The second parameter is the callback to call
  3. Our callback function is a class method so we call by passing an array the first index holds the class name as a string and the second index holds the method name as a string
  4. Line 98: hooked our assets method to the action wp_enqueue_scripts, irrespective of its name ends with scripts which is responsible for bringing the CSS and JS to the frontend
  5. Line 99 & 100: these two actions are like two sides of a coin either one will be triggered these two hooks responsible fire an ajax call
  6. You can check the source code of the ajax implementation in WordPress

Let’s Explore The Plain JavaScript

Override Native Fetch Function

  1. Line 3: Assign the native JavaScript fetch function to a variable
  2. Line 6: declare a function fetch under the window object to override the existing fetch function
  3. Because the native fetch function doesn’t support event to identify ajax start and end so we overrode that function
  4. Line 9 & 10: two new event with the name fetchStart and fetchEnd created
  5. Line 13: call the native fetch with the argument passed to our custom fetch and assign the native fetch return value to the variable fetchCall
  6. Line 16: the fetchStart event is dispatched the listening function codes will be executed during the event dispatch
  7. Line 18 – 24: fetchEnd event is dispatched either during the call success or any exception is thrown
  8. Line 26: return the variable fetchCall which was assigned with native fetch return

Listening To The Events

  1. Line 29: implemented an event listener function for fetchStart event
  2. Line 30: added our spinner class vg-spinner to load more button which displays a loading spinner animation to indicate posts are fetching
  3. Line 31: turned off the pointer event i.e. clicking of the load more button won’t trigger any functionality by assigning none to the pointerEvents property
  4. The reason to disabled pointer event is to avoid consecutive ajax call
  5. Line 34: implemented an event listener function for fetchEnd event
  6. Here we do quite the opposite of what we did in previous listener function
  7. Line 35: removed our spinner animation by removing the CSS class vg-spinner to indicate ajax fetch has been completed
  8. Line 36: turned on the pointer event by assigning the pointerEvents property with the value auto which results in event triggering capability enabled to the load more button
  9. Line 38 – 42: the reason for wrapping our core logic inside an anonymous function of setTimeout is used to avoid the race condition of execution our code even before the button generated and rendered in the DOM
  10. The conditional statement compares whether the displayed post card element count is equal to the total record count fetched from WP localize variable
  11. If it is equal then conditional statement be boolean true which results in the execution of inner block whose functionality is to hide the load more button after fetching all posts of the particular category

Frontend Kernel Function

  1. Line 45 – 54: this anonymous function which is called on the onLoad event is the heart (no! no! brain) of functionality which triggers our ajax function actionAjax()
  2. Line 46: fetch the DOM object of the load more button and assign it to the variable loadMoreButtonObj
  3. Line 48: conditional check to confirm loadMoreButtonObj is set to a value and it’s not an undefined one
  4. Line 49: if the condition is validated to true then to the load more button click event we register our ajax function actionAjax()

Get Loaded Posts Count

  1. Line 56: the function get_loaded_post_count() is the simple function to get the count of total loaded posts
  2. Line 57: here we called length property of the method querySelectorAll which retrieves all the matched DOM object for the passed selector string as an argument

JavaScript AJAX Call

  1. Line 60: we declared our ajax function actionAjax
  2. Line 61: called our overridden fetch API function with the localized object property fetch_remaining_post.ajaxurl
  3. This fetch API will work in all modern browser if your’s is legacy one then go for a polyfill
  4. To learn more about fetch API read the MDN documentation
  5. The fetch API second parameter is the object with key-value pair of additional properties to handle the current ajax call
  6. Line 62: call our ajax in POST method type instead of the default Get method type
  7. Line 63: we set a header to handle our data as form data
  8. Line 64 – 67: we pass our query parameters through URLSearchParams utility method
  9. Line 69: the first chained promise takes the fetch API’s response object and the text() method of this object returns all the streamed text in Unicode string
  10. Line 70: the second chained promise execute after retrieval of all streamed data and append it at the end of the post grid


  1. Through this tutorial, you learned to create a plugin to load posts excerpts elegantly
  2. Learned about backend technologies like WordPress plugin creation, shortcode hook, Transient API, passing value to JavaScript, doing Ajax call
  3. Learned about overriding native function, fetch API to do ajax call, adding and listening custom event, promise function and catch the exception, using CSS like selector to fetch DOM object, manipulating DOM using its method and properties, hooking a function in JavaScript events, method chaining
  4. The thing we left is CSS file of this plugin which is self-explanatory so we left it out for you as homework to play and learn with the CSS