Infinite Scroll in WordPress

Infinite scroll, when used correctly, is a wonderful tool for improving engagement on your website. You lower your users’ barrier to getting the next page of content, which increases their chances to find something interesting and click through.

I refer to infinite scrolling as any technique that uses AJAX to load additional content without reloading the page. I’ll provide a general walkthrough of the tools required, then provide two examples of different implementations.


There are three main components to infinite scroll.

  1. The trigger. This is what starts the loading of new content. It is typically scroll-based (ex: load more posts when users is X pixels from bottom of page) or click-based (ex: clicking a “load more” button)
  2. The AJAX query. This is the Javascript actually asking for the new content, and then inserting it into the page.
  3. The WordPress query. The PHP code in your theme or plugin that does a WP Query, formats the data, and returns it when asked by the AJAX query.

Scroll to Load More

The first example is a standard “Scroll to Load More”. When the user is a certain distance from the end of the post listing, more posts are loaded and inserted at the bottom. I implemented this on Western Journalism.

infinite scroll on Western Journalism

The WordPress Query

When you create your AJAX query, you’ll specify an action (see load-more.js, line 23). You’ll then hook a WordPress function to that action, and it will get run any time that AJAX query runs.

The hooks you use are wp_ajax_{action} and wp_ajax_nopriv_{action}. Let’s call our action “be_ajax_load_more”, and we’d hook our function like so:

* AJAX Load More
* @link
function be_ajax_load_more() {
$data = 'Additional posts go here';
wp_send_json_success( $data );
add_action( 'wp_ajax_be_ajax_load_more', 'be_ajax_load_more' );
add_action( 'wp_ajax_nopriv_be_ajax_load_more', 'be_ajax_load_more' );
view raw functions.php hosted with ❤ by GitHub

You’ll pass useful data to your function using the $_POST variable. Here’s a full example of the WordPress query. We’re grabbing query variables from $_POST, doing the query, and returning the result. I’m using a function called be_post_summary() to output the individual post. You’ll need to define this function yourself so it outputs the post formatted the way you’d like.

* AJAX Load More
* @link
function be_ajax_load_more() {
$args = isset( $_POST['query'] ) ? array_map( 'esc_attr', $_POST['query'] ) : array();
$args['post_type'] = isset( $args['post_type'] ) ? esc_attr( $args['post_type'] ) : 'post';
$args['paged'] = esc_attr( $_POST['page'] );
$args['post_status'] = 'publish';
$loop = new WP_Query( $args );
if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post();
endwhile; endif; wp_reset_postdata();
$data = ob_get_clean();
wp_send_json_success( $data );
add_action( 'wp_ajax_be_ajax_load_more', 'be_ajax_load_more' );
add_action( 'wp_ajax_nopriv_be_ajax_load_more', 'be_ajax_load_more' );
view raw functions.php hosted with ❤ by GitHub

Enqueue the Javascript

We need to enqueue a Javascript file which will contain all our relevant JS. We also need to pass along the information our WordPress plugin needs using wp_localize_script().

* Javascript for Load More
function be_load_more_js() {
global $wp_query;
$args = array(
'url' => admin_url( 'admin-ajax.php' ),
'query' => $wp_query->query,
wp_enqueue_script( 'be-load-more', get_stylesheet_directory_uri() . '/js/load-more.js', array( 'jquery' ), '1.0', true );
wp_localize_script( 'be-load-more', 'beloadmore', $args );
add_action( 'wp_enqueue_scripts', 'be_load_more_js' );
view raw functions.php hosted with ❤ by GitHub

In this example I’m also passing along the URL for the AJAX query and the current WordPress query so that we can use it when requesting additional posts.

Setup the Javascript

Now it’s time for the actual Javascript file. In my theme I created a /js directory and load-more.js inside it. You can name and place your file anywhere, but make sure the wp_enqueue_script() above properly links to it. Below the code snippet I’ll walk through what’s happening line-by-line.

$('.post-listing').append( '<span class="load-more"></span>' );
var button = $('.post-listing .load-more');
var page = 2;
var loading = false;
var scrollHandling = {
allow: true,
reallow: function() {
scrollHandling.allow = true;
delay: 400 //(milliseconds) adjust to the highest acceptable value
if( ! loading && scrollHandling.allow ) {
scrollHandling.allow = false;
setTimeout(scrollHandling.reallow, scrollHandling.delay);
var offset = $(button).offset().top - $(window).scrollTop();
if( 2000 > offset ) {
loading = true;
var data = {
action: 'be_ajax_load_more',
page: page,
query: beloadmore.query,
$.post(beloadmore.url, data, function(res) {
if( res.success) {
$('.post-listing').append( );
$('.post-listing').append( button );
page = page + 1;
loading = false;
} else {
// console.log(res);
}).fail(function(xhr, textStatus, e) {
// console.log(xhr.responseText);
view raw load-more.js hosted with ❤ by GitHub

3-4 I’m creating an element with a class of .load-more and sticking it at the bottom of my post container (.post-listing). We’ll detect how far away the user is from this element to determine when to load more posts
5-6 Create some variables we’ll need. Setting page = 2 since we’ll be loading the second page. The loading variable will tell us if we’re actively waiting for the next set of posts, so we don’t send multiple requests.
7-13 Instead of running the next block of code every microsecond the user is scrolling, I’m setting up a timer. When scrolling, it is only allowed to check every 400 microseconds (this can be adjusted if needed).
15 If the user is scrolling…
16 And we’re not already loading posts, and the timer will let us check…
17-18 Reset the timer
19 Calculate distance between the window’s current location and the .load-more element
20 If we’re within 2000px of the .load-more element…
21 Set loading to true since we’re about to load new posts
22-27 Setup the data we’re passing to our WP function. We’re including the action, the page we want to load, and the other query variables.
28 Make the AJAX request to the proper AJAX URL and pass along our data
29-33 If the AJAX request is successful, stick the returned data at the bottom of our post container, then move the .load-more element below it (resetting the distance between the window and the button). Increase our page count (so next time we’ll get page 3), and set loading to false.
34-36 If we don’t get a success message back, do nothing (for now). Uncomment the console.log line and whatever information is received will be displayed in your browser’s console.
37-39 If it fails, do nothing (for now). Like above, you can uncomment the console.log for more details about what went wrong.

And that’s it! We now have infinite scroll working on archive pages. For reference, here’s the full code

Infinite Scroll – Click to Load More

On Brody Law Firm’s website (not live, coming soon), at the bottom of an individual post we display more posts from the same category and a button to load more. The code to power this is very similar to the above example, except we’re triggering it on click rather than on scroll.

infinite scroll on Brody Law Firm

* Javascript for Load More
function be_load_more_js() {
if( ! is_singular( 'post' ) )
$query = array(
'post__not_in' => array( get_queried_object_id() ),
'category_name' => ea_first_term( 'category', 'slug' ),
'posts_per_page' => 3
$args = array(
'url' => admin_url( 'admin-ajax.php' ),
'query' => $query,
wp_enqueue_script( 'be-load-more', get_stylesheet_directory_uri() . '/js/load-more.js', array( 'jquery' ), '1.0', true );
wp_localize_script( 'be-load-more', 'beloadmore', $args );
add_action( 'wp_enqueue_scripts', 'be_load_more_js' );
* AJAX Load More
function be_ajax_load_more() {
$args = isset( $_POST['query'] ) ? array_map( 'esc_attr', $_POST['query'] ) : array();
$args['post_type'] = isset( $args['post_type'] ) ? esc_attr( $args['post_type'] ) : 'post';
$args['paged'] = esc_attr( $_POST['page'] );
$args['post_status'] = 'publish';
$loop = new WP_Query( $args );
if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post();
endwhile; endif; wp_reset_postdata();
$data = ob_get_clean();
wp_send_json_success( $data );
add_action( 'wp_ajax_be_ajax_load_more', 'be_ajax_load_more' );
add_action( 'wp_ajax_nopriv_be_ajax_load_more', 'be_ajax_load_more' );
* First Term
* Helper Function
function ea_first_term( $taxonomy, $field ) {
$terms = get_the_terms( get_the_ID(), $taxonomy );
if( empty( $terms ) || is_wp_error( $terms ) )
return false;
// If there's only one term, use that
if( 1 == count( $terms ) ) {
$term = array_shift( $terms );
} else {
$term = array_shift( $list );
// Output
if( $field && isset( $term->$field ) )
return $term->$field;
return $term;
view raw functions.php hosted with ❤ by GitHub
$('.post-listing').append( '<span class="load-more">Click here to load earlier stories</span>' );
var button = $('.post-listing .load-more');
var page = 2;
var loading = false;
$('body').on('click', '.load-more', function(){
if( ! loading ) {
loading = true;
var data = {
action: 'be_ajax_load_more',
page: page,
query: beloadmore.query,
$.post(beloadmore.url, data, function(res) {
if( res.success) {
$('.post-listing').append( );
$('.post-listing').append( button );
page = page + 1;
loading = false;
} else {
// console.log(res);
}).fail(function(xhr, textStatus, e) {
// console.log(xhr.responseText);
view raw load-more.js hosted with ❤ by GitHub

I’m only enqueuing this script on single posts. For the query, we’re requesting posts in the same category, excluding the current post. Also note how the Javascript file triggers the AJAX query when the .load-more element is clicked, rather than on scroll.

Any Questions?

I hope this in-depth tutorial helps you implement infinite scroll in your own projects.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions


  1. Tom says

    February 2, 2016 at 3:48 pm

    Thanks for posting this! — I’ve just been looking at trying similar with Masonry. What would you recommend for taking the AJAX a step further to load entire posts in the background for presentation in a modal window or lightbox?

    • Bill Erickson says

      February 2, 2016 at 3:59 pm

      I’d use the same approach outlined here. Just update the WP function to return the markup you need (full post content rather than excerpts).

  2. Amber Hinds says

    February 2, 2016 at 5:10 pm

    Thanks for sharing, Bill! I was wondering about this after looking at Pinch of Yum. Is the footer-cta on that site hooked a widget area in with the query in be_ajax_load_more()?

    • Bill Erickson says

      February 2, 2016 at 7:14 pm

      Yes, in my load more function I display 20 posts and then the footer-cta. It’s not a widget area though, it’s just a function I wrote to output the footer cta.

  3. David Chandra says

    February 2, 2016 at 8:07 pm

    thanks for sharing.
    did you ever use jetpack infinite scroll module?
    it also update the URL, so it will be bookmark-able.
    any idea how to do that?

    • Bill Erickson says

      February 2, 2016 at 8:41 pm

      The problem with the Jetpack infinite scroll (and other plugins like it) is they have to work with every theme so use a pretty hacky approach. They actually load the next page (like clicking the “Next” link in your pagination), then pick out just the posts in the content area and insert that. You’re loading a whole page when it would be more efficient to only load the posts required.

      It can also cause issues with ad networks since you’re inflating your pageviews. Infinite scroll is a “pageview” when Jetpack loads the page to grab the posts, but the ad shown on that page isn’t seen by an actual human.

      Finally, with that tool you’re limited to only archive pages since they have the “Next” button Jetpack can press. The approach described above can be used in any instance. When you’re on a single post you can load excerpts of other posts, like I’ve done on Pinch of Yum.

      If you wanted to change the URL you could use pushState/replaceState, or history.js to ensure it works in all browsers.

  4. Scott Lesovic says

    February 3, 2016 at 12:59 am

    In other AJAX-y things I’ve done, I’ve needed to refresh the nonce on every iteration. Is that just not needed here, or is it missing?

    • Bill Erickson says

      February 3, 2016 at 8:01 am

      I don’t believe we need to regenerate the nonce, but it probably wouldn’t hurt.

      From the Codex: “Nor are they used only once, but have a limited “lifetime” after which they expire. During that time period the same nonce will be generated for a given user in a given context. The nonce for that action will remain the same for that user until that nonce life cycle has completed.”

  5. Lorie Ransom says

    February 3, 2016 at 12:28 pm

    Thanks for this! I recently implemented my first Ajax functionality based on another tutorial, but didn’t know about using nonces for it. I’ve now added that in!

  6. Ihab says

    February 4, 2016 at 5:00 am

    I worked through your tutorial through my theme. Called function using be_ajax_load_more(); as template tag and all I got as output is “-1”. Any suggestions?

    • Bill Erickson says

      February 10, 2016 at 8:37 am

      No, I have not tried that plugin. It looks interesting, but I still prefer the method outlined above. It’s simpler to make it match the look and feel of your site when you’re using the same functions to display posts on load and on scroll.

  7. Gunter says

    March 3, 2016 at 5:43 pm

    First of all thanx for sharing your code, very helpful!
    But on question, I wonder why I’m the first asking, I get an error 500 if I leave the “be_post_summary()” within the loop, it’s not a known function, what is this function supposed to do?
    Is it necessary for the functionality?

    thx in advance

    • Bill Erickson says

      March 4, 2016 at 5:15 pm

      You are supposed to replace that function with whatever you want to display. In my example I had a function, be_post_summary(), that outputs a summary of blog posts.

  8. Daniel says

    March 7, 2016 at 7:33 am

    I wrote something pretty similar, checking whether others like you have implemented nonce check for infinity scroll / Ajax carts and other “non-data-critical” applications. Thing is, when caching pages, it gets pretty nasty as everyone is getting the same nonce hence loosing its value. I was wondering whether anyone was going forward on loosing the nonces and use a GET request instead. What are your thoughts on this?


  9. Shaa Taylor says

    April 6, 2016 at 2:26 am

    Thanks you for this code and explanation, it works perfectly and does exactly what i am looking for. Now I can decide how to display the extra data on the page. This is important as how I display the posts that come back is quite complicated and no infinite scroll WP plugin has been able to do what I want.

    Thanks Bill, this is super cool!

  10. AG says

    April 13, 2016 at 6:44 am

    I want to implement Western Journalism example on my wordpress website – homepage.

    I am little lost and need your help. When I follow the instructions you have given above – I receive an error for line 19 in JS File – Anonymous. What do I need to do? Also what is admin-ajax.php and where can I find it?


  11. Lefteris says

    April 18, 2016 at 12:58 pm

    For those who are trying to use this solution on a masonry grid:
    Items on the masonry use absolute positioning so appending the button in the container will not work. You either need to use another method to detect the bottom of the window ($(window).scrollTop() + $(window).height() > $(document).height() – 100) or append the button in the parent element.
    Then add this modified version of the load-more.js

  12. Panos says

    April 25, 2016 at 10:32 am

    How about to load more items when the user scrolls close to the bottom and not on click of the load more posts button? How can this be done?

  13. Núria says

    May 7, 2016 at 8:20 am

    Very usefull and functional code, thanks for it. I implemented it with success.

    But now I’m trying to add a preload gif, using the class .load-more:
    .load-more {
    background: url(“../images/preload.gif”) no-repeat;
    display: inline-block;
    width: 128px;
    height: 15px;

    It’s running ok, but I don’t know where in the js script get the end of the data loaded to change the display of the .load-more to none, and hide it at the end of all posts loaded.

    Any idea? Thanks in advance!

  14. John says

    May 17, 2016 at 5:24 am

    Wow, super easy to implement and really good explained for people with beginner coding skills. Thanks for this Article, a big time saver and a good read to learn how to infinite scroll if you’re not just a Copy Cat.

    • John says

      May 24, 2016 at 2:42 am

      Now I am having a little trouble with getting this to work, while having the Main Query modified with WP_query, so the WordPress Frontpage adds a Custom Post Type to the regular Posts.
      The Script always just returns “0” and when I do a console.log on the query inside the data array, its empty.

      Any recommendations on how to modify your script so it works with that too?

      • Bill Erickson says

        May 24, 2016 at 8:49 am

        In the `be_load_more_js()` function (hooked to wp_enqueue_scripts) the query parameters are added to the $args array. Do a print_r( $args ); to make sure those query arguments match your desired query.

        If that looks right, the next step is to see how it’s processed. Temporarily copy this code into the be_load_more_js() function (this came from be_ajax_load_more() ): After you load it once, you’ll see how the arguments were processed in be_ajax_load_more() and confirm they look correct.

        Just continue troubleshooting down the line until you find the issue.

  15. Giovanni Ganzinotti says

    May 29, 2016 at 10:18 am

    Wonderful tutorial. I was able to implement this piece of code on my index.php. ( Now I want to continue to add the functionality on other pages.

    For example: has a different style so I need to change the the code.

    Another example: here I need to include other custom post types.

    How can I easily extend your code without duplicating it? Conditional statements like is_home() are not working in functions.php and I do not want to duplicate the code, because I need eight different formats.

    Hopefully you can give me a hint.

  16. Doug says

    June 7, 2016 at 8:56 pm

    Just wanted to say thanks for this. Things weren’t very clear to me at first because somehow I was totally unaware of wp_localize_script, which is just flat-out awesome. I was able to adapt this to do exactly what I needed and it’s working great.

  17. Jordan says

    June 8, 2016 at 1:14 am

    Awesome man! Works like a charm! Thank you!

    I did have one question related to the markup of the additional posts. In the be_ajax_load_more() function, I’m outputting a modified version of the genesis loop ->

    On line 17, I have printf( '', genesis_attr( 'entry' ) ); but this does not output the entry class. Is this a scope issue?

    I was able to target a different class to apply the css I needed but curious why it doesn’t output and what the best way to output it would be.

    Thanks man!

    • Bill Erickson says

      June 8, 2016 at 7:57 am

      Hmm, that’s weird, everything looks right to me. I never use that though. I’d use implode( ' ', get_post_class() )

    • Gabe Lloyd says

      April 17, 2017 at 6:14 pm

      I’m getting the same issue. I couldn’t see the pastie example you posted. Where did you put the `implode(‘ ‘, get_post_class())`?

      • Bill Erickson says

        April 18, 2017 at 11:02 am

        In the post above, I use be_post_summary(); to represent the actual output of the post. It would be inside that function that I would output implode( ' ', get_post_class() ) on the post entry.

        • Gabe Lloyd says

          April 19, 2017 at 11:55 pm

          Thanks, Bill. I’m just running a regular get_template_part() within the new loop where you are calling the be_post_summary() function. All my content areas within the templates use the post_class.

          if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post();
          get_template_part( 'template-parts/post/content', get_post_format() );
          endwhile; endif; wp_reset_postdata();

  18. Tony says

    June 8, 2016 at 11:54 pm

    Hi Bill,

    Thanks for sharing this. It works great, but I’m having trouble with a modification and I’m hoping you can point me in the right direction.

    I’d like the “Load more posts…” button to load a different amount of posts that page 1. For example, if I land on the blog page and see 10 posts, I’d like the load more button to load an additional 5 posts per click.

    I tried some things with posts_per_page and offset, but it was doing strange things. I’ll keep working on it, but if you have any pointers on how best to approach this I’d really appreciate it.

  19. Gerhard says

    June 9, 2016 at 5:57 am

    Hi Bill

    Firstly, thanks you for the article, you saved my butt there. 🙂

    I just have one question, how would I check to see if I reached the last page? That way I can add a statement to not show the “load more” button again.


    • Bill Erickson says

      June 9, 2016 at 7:53 am

      $wp_query->max_num_pages shows you the maximum number of pages for the current query.

      So instead of using Javascript to add the “Load More” button (line 20 of load-more.js), include it in the ajax function be_ajax_load_more(), and before displaying it make sure $wp_query->max_num_pages > $_POST['page']

      • Tony Eppright says

        June 15, 2016 at 11:16 pm

        How would you recommend adding the check so that it doesn’t repeat itself? Since be_ajax_load_more() is being duplicated, adding the button in the function repeats itself also.

        • Nick Davis says

          June 16, 2016 at 3:25 am

          First of all thanks to Bill for an awesome tutorial.

          Tony – I don’t know if this is the best way (Bill might tell me this is horrible 😉 ) but I was working on something similar just last night (still in local development), at the moment I’m using jQuery to remove the first instance of the Load More button when the script successfully runs.

          (And of course the ‘second’ (new) Load More button only loads if there’s more posts to show anyway, as per Bill’s advice above).

        • Bill Erickson says

          June 16, 2016 at 8:11 am

          In the javascript file, right after you set loading = true, add a line to remove the button, like $('.post-listing .load-more').remove();

        • Tony Eppright says

          June 16, 2016 at 2:01 pm

          Thank you guys for pointing me in the right direction. That didn’t work for me, but I was able to pull in $wp_query->max_num_pages via wp_localize_script and use that for a conditional statement in the load-more.js script.

          Added a loading spinner and new posts fade in, working great now! Thanks again for your help!!!

  20. Darci says

    June 29, 2016 at 9:46 am

    I’m getting an error trying to implement this on MAMP: http://localhost/wp-admin/admin-ajax.php 500 (Internal Server Error)

    My debug log says: Error in GetAllFileNamesFromDirectoryWithFileMask.

    I have plugins on my site that use AJAX, and they’re still working properly.

    Any chance you’ll know what to do? I’m new to the dev side of things and I feel this is getting nitty gritty.

    Thanks in advance

  21. Juan says

    July 1, 2016 at 3:23 pm

    Hi Bill, Excellent article. I wanted to ask, how does the be_post_summary() function work? does it return the markup for the loop? Do you create that function elsewhere?

    • Bill Erickson says

      July 1, 2016 at 3:28 pm

      Yes, it handles the markup for the posts. I didn’t include it in the code above because the specific markup I used isn’t relevant to the tutorial and everyone’s implementation will use different markup.

      If the function will only be used for the infinite scroll then I put it in the same file with all the other infinite scroll code. But often times it’s a generalized function used to display content anywhere throughout the site (main loop, related posts after post, infinite scroll…) in which case it’s usually in inc/post-summary.php in my theme.

  22. Ritchie says

    July 6, 2016 at 10:16 am

    For a not so good in programming. This does not help me. There are no instruction on how to add in the front-end of front-page template. Reading comments doesn’t help either. 🙁

  23. Ritchie says

    July 6, 2016 at 12:02 pm

    I made it work but the loop doesn’t reset or stop. and also, it ruins my parallax background image.

  24. Nick Berry says

    July 6, 2016 at 5:20 pm

    I tried the 1st method and got an error that says “Uncaught TypeError: Cannot read property ‘top’ of undefined”. After spending too much time to resolve it I ended up having to use Jetpack to meet my deadline. I would like to use this method in the future if I can figure it out.

    • simon says

      July 12, 2016 at 9:29 am

      i think your error is to do with these 2 lines:

      wp_enqueue_script( ‘be-load-more’, plugins_url( ‘/js/scripts.js’, __FILE__ ), array(‘jquery’), ‘1.0’, true );
      wp_localize_script( ‘be-load-more’, ‘beloadmore’, $args );

    • Gabe Lloyd says

      April 19, 2017 at 11:42 pm

      I had a similar issue. You have to create the before setting it to the variable button.

      I did something like $('#main').append( '' );
      var button = $('.load-more');
      so the creation of the class is done before the variable.

      Hope that helps.

  25. simon says

    July 12, 2016 at 9:32 am

    im trying to implement this for the template archive.php, how would this template file look? do I still use the loop in there?

    • Bill Erickson says

      July 12, 2016 at 11:40 am

      Yes, the loop produces the first page’s results. The infinite scroll simply loads the additional page’s results as you scroll or click.

      The only thing you need to remove is the pagination links. It might be a good idea to do that using Javascript so that if the JS doesn’t load right, it gracefully degrades to standard paginated posts.

      • Jessica says

        July 13, 2016 at 7:38 pm

        I’m using a Genesis Child theme. I’m also interested in what Simon asked, is there any tutorial online that shows what the php would look like modified to load the archive/categories using infinite scroll for Genesis while hiding pagination?

        Even how to apply it to the footer after comments of a single post? I’m assuming you could somehow use the Genesis hooks but have no idea of how to modify it.

        • Bill Erickson says

          July 14, 2016 at 7:48 am

          Both of the tutorials above apply to the main Genesis loop. One shows how to load more posts as the user scrolls, and the other shows how to load more after the user clicks a button.

          What specifically are you looking for that’s not covered above?

          • Jessica says

            July 14, 2016 at 5:41 pm

            Thank you for your response Bill.

            I added the first functions script and javascript file to see what happened once I did, but I after checking, it didn’t modify anything in my child themes behavior.

            I managed to identify the css triggers:

            .archive-pagination div.pagination-next a (so long as I’m using a Prev/Next pagination rather than numbers)

            But I’m not sure how to use your provided code to infinite scroll the:
            a) Category/tag pages (instead of number pagination)
            b) After single posts (after the comments section)

  26. Terrell says

    July 14, 2016 at 9:05 am

    Hi, I tried this method and it didn’t do anything. I was looking to add infinite scroll on my single.php so that when the user scrolls to the end of the post it loads the previous post for at least 10 post.

    I tried the second method and nothing happened. I placed the code in my functions.php and I also created the load-more.js

    Can I get some assistance. I have been trying to figure out how to do this for a week and I have tried all the free wordpress plugins for this and none of them work as I like.

  27. AJ Clarke says

    July 24, 2016 at 1:01 am

    Works perfectly. Thanks Bill for the guide 😉 I had created my own load more function in the past, but this is a lot simpler then what I had.

  28. Lauren Gray says

    August 21, 2016 at 9:03 am

    Bill, your code was a welcome reprieve from attempting to make this on my own – thank you for sharing!

    I’ve created a version that supports column classes, custom settings by page, and both auto + button loading. I’d love feedback on how to improve this code – it’s working, but I haven’t optimized it. Anyone who is looking for something similar is welcome to use this as a starting point!

    BTW, Bill, you have an extra closing bracket on line 26 of the final functions.php file (on-click version).

    • Bill Erickson says

      August 22, 2016 at 8:40 am

      Looks great! The only improvement I’d recommend is moving the “load more” Javascript into its own function since it’s used in two different contexts.

  29. Matt Pramschufer says

    August 23, 2016 at 9:34 am

    For the purposes of newbies here. If someone is looking to display the standard genesis formatting for the loaded content you could simply have

    function be_post_summary(){
    do_action( ‘genesis_before_entry’ );
    printf( ”, genesis_attr( ‘entry’ ) );
    do_action( ‘genesis_entry_header’ );
    do_action( ‘genesis_before_entry_content’ );
    printf( ”, genesis_attr( ‘entry-content’ ) );
    do_action( ‘genesis_entry_content’ );
    echo ”;
    do_action( ‘genesis_after_entry_content’ );
    do_action( ‘genesis_entry_footer’ );
    echo ”;
    do_action( ‘genesis_after_entry’ );

  30. Pankaj S. says

    August 29, 2016 at 6:12 am

    Hi Bill, Thanks for share this awesome tutorial…
    One question for you. Suggest me how can i optimize this ajax triggered again and again. Is it possible to add fixed area for ajax trigger. For example when we reach the bottom of wrapper div then ajax call instead of every scroll.
    Thanks again 🙂

    • Bill Erickson says

      August 29, 2016 at 8:55 am

      That’s exactly what we’re doing. We’re adding an item at the bottom of the page (.load-more) and once the browser reaches it, load more posts. The problem is the browser won’t tell you when you reach it – you have to check the distance while scrolling. The “scroll handling” part is how we minimize how often our code runs. Instead of running every millisecond that the user scrolls, we set a timer and only run every 400 milliseconds (you can change this to whatever you like).

      On line 12 you set the delay on the timer. On line 16-18 you’ll see it checks the scroll timer to make sure it’s allowed to load, and if so it resets the timer.

  31. Brett Pollett says

    September 9, 2016 at 9:28 am

    Hey Bill, this tutorial was great and super easy to implement, I just had one issue. It worked great when I was using it on my custom post type archive page, but then I wanted to convert that archive into a page template and the ajax load stopped working. It seems as though this function is not even called on a page template that includes a query. Is there a simple modification to the script to allow it to be called on page templates as well?

    • Bill Erickson says

      September 9, 2016 at 9:35 am

      The problem is this code uses the query settings for the Main Query, not your custom query. See this line. You would need to change this to pass your custom query arguments.

      • John Buchmann says

        September 12, 2016 at 11:11 am

        I’m in a similar (same?) situation as Brett. I have a Search Results “page template” that uses a custom query. The results are from a Custom Post Type.

        The Page Template does a query (based on user input) and adds all results into an ARRAY of IDs.

        In the “Ajax Load More” code, if I hard code an array of IDs as one of the $args, it totally works. For example:

        $args[‘post__in’] = array (5, 6, 7, 8, 9, 10, 11, 12);

        The IDs obviously will be different depending on the search the end user makes.

        (I hard code it for proof of concept, but in the real site the array would be a variable)

        How could it be possible to pass in this array rather than hard code it? If I can pass this array in, it will surely allow for infinite scrolling for a custom query. Any advice you can share would be really helpful! Thx!

        • Bill Erickson says

          September 12, 2016 at 11:19 am

          Why are you doing one query (to get the IDs), then a separate query (to get the posts)? What if the results of the search generate 50,000 post IDs? Are you going to try passing an array that large?

          You should just pass the search query (ex: ?s=something) and the page (ex: get_query_var( ‘paged’ )) to WordPress to do the query itself. Then the infinite scroll code can simply get the next page of these results.

          • John Buchmann says

            September 12, 2016 at 2:33 pm

            Thanks for such a fast reply!

            Unfortunately the site search isn’t just a simple “s=”. It’s a Real Estate site with many custom search parameters, (min/max price, property type, location, etc.) from custom data entry fields. So the user isn’t just searching the title and content of a post.

            I suppose the search code could be re-written so that it doesn’t return a list of IDs, and is instead a bunch of $args for a wp_query statement. But before I commit to that, in testing I hard coded the $args in to simulate an actual search…

            On initial page load the correct results show up on page 1 (as expected), but page 2 ignores the query and outputs results that can duplicate stuff in page 1. I think the problem is, in the ‘query’ argument for beloadmore:

            ‘query’ => $wp_query->query,

            returns this when I do a print_r:


            “search-results” is my Page Template that displays the custom search results. This doesn’t appear to be a proper query. So there is still the issue of somehow passing in the custom query. Thoughts?

            Alternatively, my original quesiton about passing in a potentially long array I think would still be viable. I’ve seen my theme pull in results in the thousands, and it’s just as quick as if it were a small array. 🙂

            I feel like I’m so close. I would probably give up by now if it weren’t for the fact that I spent nearly 3 full days on this. I feel like i’m at the point of no return. 🙂

          • Bill Erickson says

            September 12, 2016 at 2:38 pm

            Correct. As mentioned above, this code is designed to use the main WordPress query. You’ll want to replace that line with whatever you want to pass along to do your custom query.

            I think the best approach would be to pass along all the relevant GET parameters that accompany your custom search fields, so that you can do a single meta query. But you could also pass along an array of post IDs if you wanted.

            Pass along whatever parameters you want in the be_load_more_js() function, then use those parameters to do a WP_Query in the be_ajax_load_more() function.

          • John Buchmann says

            September 12, 2016 at 2:52 pm

            Hmmm… I’ll have to let that response soak in and hopefully make sense as I experiment with your advice. 🙂 Thx so much, you have been of great help and your article is indispensable. If I figure this out I’ll let you know!

  32. meysam says

    September 14, 2016 at 12:26 am

    Thanks for sharing, Bill!
    I was a problem . i want use this cods on the post’s of category. how can i use it ?? ?
    i don’t have navigation number , i have load more button in the archive category.

  33. John Buchmann says

    September 14, 2016 at 9:35 pm

    Again, thanks for the great article. I was finally able to get it all working for the most part, but just when I think I’ve got it licked, I ran into a problem…

    In the be_post_summary() function, I can output HTML, the_title(), the_excerpt(), etc. But I cannot get post meta with this:

    ID, “price”, true);

    It prints a blank string.

    This is a custom field called “price”. I also have a bunch of other custom meta fields that I need to display. Note: I’m looping through posts from a Custom Post Type, not regular blog posts.

    Can you offer any advice on how to pull this in? Thanks!

    • John Buchmann says

      September 14, 2016 at 9:36 pm

      Whoops, my code got messed up because I wrapped it in PHP tags. This is the get_post_meta:

      echo get_post_meta($post->ID, “price_value”, true);

    • Bill Erickson says

      September 15, 2016 at 7:49 am

      My guess is you didn’t call the global $post; first. A simpler approach is to use get_the_ID() instead:

      echo get_post_meta( get_the_ID(), 'price_value', true );

  34. Kurt says

    September 29, 2016 at 1:04 pm

    I’m having issues getting this to work on an archive for a Custom Post Type.

    Should I be following the code on this page or the code in this link?

    And when I update the content for a custom post type do I change this line?
    $args[‘post_type’] = isset( $args[‘post_type’] ) ? $args[‘post_type’] : ‘post’;
    $args[‘post_type’] = isset( $args[‘post_type’] ) ? $args[‘post_type’] : ‘work’;

    Ultimately I want to get this to work using a custom query (WP_Query) but for now if I can get this to work on an archive page that will be a good start.

    • Bill Erickson says

      September 30, 2016 at 9:25 am

      Either code should work. The one you linked to simply combines the different sections of code featured above in this post (I think, I didn’t run a diff across them, that’s just eyeballing it).

      No, you shouldn’t need to specify the post type. You’re passing all the query vars on this line. On a custom post type archive, one of those query vars will be 'post_type' => 'my_cpt'. The standard blog archive doesn’t include a post type parameter, which is why we’re setting it to ‘post’ if it isn’t set already.

  35. Estefany Pacheco says

    October 10, 2016 at 10:43 am

    I have a custom page integrated with WordPress I’m trying to use the 2nd method and this is what i get “Uncaught ReferenceError: beloadmore is not defined”, I’ve installed WordPress in a folder, not directly on my root may this be my issue? if it is where should I paste the code?

  36. Gareth says

    October 26, 2016 at 10:49 am

    Hi Bill, thanks for your solution its working nicely for me. However do you happen to know how to hide the load more button if there arent any more posts to load?

  37. TJ says

    November 8, 2016 at 4:17 pm

    I try to implement the load more, but the ajax call is always empty.
    Firebug says {“success”:true,”data”:””} when I click the load more button.

    print_r( $args ); says:
    Array ( [nonce] => 203be4fbb7 [url] => [query] => Array ( [post__not_in] => Array ( [0] => 7 ) [category_name] => [posts_per_page] => 3 ) )

    I have a custom post type and I query the posts and display them as masonry.

    Why ist the query empty and how can I solve it

  38. Billy Yeung says

    November 17, 2016 at 2:43 pm

    Seems there is a common behaviour with Infinite Scroll is that the browser’s back button always take the user back to page 1, no matter how far the user had ever scrolled.

    For example, if I scroll down several pages and click on one of the post and then hit the “Back” button, the browser will start all over again and loading only page 1.

    Brody Law Firm’s website also takes me back to page 1; but interestly, Western Journalism website can always take me back to where I had scrolled to… (tested using Chrome on desktop)

    And thanks for this great article and all the explanation.

    • Bill Erickson says

      November 17, 2016 at 2:52 pm

      You can use pushState to change the browser’s history, updating it with each new load. But make sure if someone accesses that URL directly (ex: /page/3) they get the expected content.

      As an example, see this line in my plugin BE Full Post Scroll. This plugin adds infinite scroll to the single post, loading the next full post and updating the URL. You can see it in use on Denison Forum.

      • Carles says

        January 4, 2017 at 9:29 am

        Hi Bill,

        the link doesn’t work. I’m very interested in this code with the History API applied to it, could you re-post it somewhere so we can have a look at it?


  39. Spwned says

    November 23, 2016 at 5:10 am

    Hi Erick, thanks a lot for this great tutorial.

    The only thing which is has not been included was hiding the “Load More” button upon not having any additional posts to parse.

    Any idea how this could be accomplished?

    Thanks & Kind Regards

  40. Michael says

    December 20, 2016 at 6:58 am

    Hi, the infinite scroll breaks if the user idles on the page for more than 24h. Is it possible to regenerate the nonce after the lifetime has expired?

  41. Suj says

    January 9, 2017 at 11:34 pm

    Great Post. I have tried the 2nd method but i get this error from the Firebug console : “ReferenceError: beloadmore is not defined”. Could some one help direct where the issue could be?


  42. Paco says

    January 10, 2017 at 6:35 pm

    Thank you for providing this excellent tutorial. I have successfully implemented this on my own project and had a question, is it possible to remove the absolute links from the loaded articles? I noticed that when the action is triggered with the load more button it changes the permalinks into absolute URL’s and this creates some conflict with my setup. I was wondering how you would approach this?

    • Cedric says

      July 6, 2018 at 11:03 am

      I use Yoast and the call don’t work with the permalink and primary category. Proper Context is not sent to server side and post_link_category is never call…
      Is somebody already get into this issue ?

  43. Ryan says

    March 1, 2017 at 2:39 pm

    Hey, thank you for the code but I can’t make it works!

    I use it only for the div displaying my posts in front-page.php but it load all the posts on every pages, like my category.php pages.

    But, it load everything without scrolling, I don’t understand why.

    This is in a 100% handmade theme so there is only the minimal used code :/

    • Bill Erickson says

      March 8, 2017 at 10:01 am

      Make sure the code is located in front-page.php, or you’re checking if( is_front_page() ) before running it.

  44. Benoît Chantre says

    March 29, 2017 at 10:18 am

    Thank you for this great article.

    I was able to make it work on archives and on a front-page with a secondary loop.

    When I need to query multiple post types on the front-page, I get a notice because there’s an array to string conversion. It disappears if I replace `array_map( ‘esc_attr’, $_POST[‘query’])` by `$_POST[‘query’]` in be_ajax_load_more().

  45. Chazz Layne says

    April 25, 2017 at 7:27 pm

    Hey Bill,

    Wonderful write-up, thanks for publishing this how-to. I’ve been tinkering with it for a couple days now and with the help of the above comments got it dialed in for my “home” page needs, complete with an excluded category and offset posts_per_page from the first page onward.

    The trouble is, the above category exclusion and offset posts_per_page wind up applying to all places where infinite scrolling is used, such as categories and archives. I’m attempting to modify the be_ajax_load_more function so it only adds the exclusion and offset $args when on the home page, but is_home always returns false within be_ajax_load_more. Am I missing something obvious?

    • Bill Erickson says

      April 26, 2017 at 5:56 pm

      Rather than trying to detect that from within the ajax query, I recommend you pass that data along as a localized argument. In be_load_more_js(), add something like if( is_home() ) $args['is_home'] = true; and then look for that in the ajax query.

      Or even better, we’re passing the query along as an argument, so if is_home(), modify that query with whatever changes you want (excluded category and offset).

      • Chazz Layne says

        April 27, 2017 at 3:13 pm

        Thanks Bill, I figured it was something simple…I should have seen that. 🙂

        I’ve almost got it working going that second route: the excluded category and offset appear to be working flawlessly, but pagination is getting lost in the mix somewhere. Page 2 loads as expected, but page 3 and onward are just a repeat of page 2. I’d added the following to be_load_more_js:

        $ppp = get_option(‘posts_per_page’);
        $paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1;
        if (is_home()){ // Alter the home query
        $args[‘query’][‘cat’] = -1528;
        $args[‘query’][‘posts_per_page’] = $ppp;
        $args[‘query’][‘offset’] = 21 + $ppp * ($paged – 1);

        It seems the value of $paged from $wp_query is stuck on the initial value for page 1. I’d prefer to keep the JS unaltered and do all the math in PHP, but from what I’m reading the only way to make it work is to do the offset calculation in the JS. Is that correct?

      • Bill Erickson says

        April 27, 2017 at 3:18 pm

        Correct, you need the page variable in JS because you’re loading new content via JS – page number is changing without the entire page being reloaded.

        In the Javascript file, change this line to offset: 21 + beloadmore.ppp * ( page - 1)

        Then include posts per page (ppp) as one of the variables you’re localizing here.

        • Chazz Layne says

          April 27, 2017 at 4:45 pm

          Thanks for the quick reply, that got me back on track.

          It dawned on me that a hybrid solution might accomplish what I need, while keeping the JS as vanilla as possible—so it functions without edit on other pages, or even other sites with different offsets. For posterity, here’s what I wound up with in be_load_more_js:

          if (is_home()){ // Alter the home query
          $args[‘ishome’] = 1; // Tell jQuery we’re home
          $args[‘query’][‘cat’] = -1528; // Keep skipping the Legacy category
          $args[‘query’][‘posts_per_page’] = get_option(‘posts_per_page’); // Restore the default WP PPP

          …and picking it up again in be_ajax_load_more:

          if ($_POST[‘ishome’] == 1){ // Altered home query offset
          $args[‘offset’] = 21 + $args[‘posts_per_page’] * ($args[‘paged’] – 2);

          Then I just added this after line 25 in load-more.js:

          ishome: beloadmore.ishome,

  46. Carson says

    May 9, 2017 at 10:19 am

    Hello Bill,

    This seems like a very useful plugin, but what I’m looking for in particular is one that keeps loading posts much like your “BE Full Post Scroll” plugin. Problem is, the GitHub repository no longer exists.
    Initially, I tried implementing the first option of this infinite scroll plugin, and I was returned with:
    “Uncaught TypeError: Cannot read property ‘top’ of undefined
    at load-more.js?ver=1.0:19”

    I decided to implement the “functions.php” code into the actual functions.php theme file, and the error pops up whenever I reach the bottom of the post, likely indicating its use. Regardless, is there any chance you still have the necessary functions available for the scroll used on the Denton Forum posts? I feel that option would be a better fit for my website after the redesign.

      • Carson says

        May 9, 2017 at 7:00 pm

        Thanks for the quick reply, Bill.
        I tried saving the plugin as a ZIP and installing it conventionally (upload), but nothing appears to work in terms of navigation. According to the source code, it DOES call the JS functions from ScrollSpy, leaving behind this CDATA element.
        /* */

        Said URL:

        • Carson says

          May 9, 2017 at 7:02 pm

          EDIT: Sorry, should have seen that coming when I left the syntax.

          var args = {“container”:”.content”,”post”:”.entry”,”next”:”.post-navigation a[rel=\”prev\”]”,”offset”:”2000″,”delay”:”400″,”debug”:””};
          /* ]]>

          • Bill Erickson says

            May 9, 2017 at 7:06 pm

            You’ll need to customize the arguments to match the class names used in your theme. Use the be_full_post_scroll_args filter, see here.

        • Carson says

          May 10, 2017 at 2:36 am

          Okay, so far I have been able to load more pages, but the result seems far from your example, the Denison Forums. For instance, only one additional page will load, the URL stays the same, and the header and footer are duplicated. Any particular reason why? Wrong class attributes?

          • Bill Erickson says

            May 10, 2017 at 7:45 am

            Yes, it sounds like you aren’t using the correct classes.
            – Container is the element to which the newly retrieved post is appended. The default is .content
            – Post is the element that is the post; this is appended to the container
            – Next is the link that contains the URL for the next post you want loaded. You should use get_previous_post_link() somewhere on the page, and use this parameter to target that link

            If you need additional help implementing this, I recommend you hire someone on Codeable.

          • Roselle says

            September 9, 2017 at 4:42 am

            Hi Carson,

            Did you ever get this to work? I’ve been going over the classes again and again and again –I’m about to break my keyboard in half! Maybe I should sleep, it is 6am after all.

            I’d greatly appreciate your help!


  47. Carson says

    May 9, 2017 at 7:28 pm

    Thanks for the tip. In regard to the link I provided above, is there any chance you could point out which classes should be assigned to the $args array based on the source code? I believe that might be the part where I’ve had trouble in the past.

  48. sunshine says

    June 6, 2017 at 12:10 pm

    Dang. I can read php and javascript and understand what I am reading. I am comfortable tweaking, deleting, adding php code and am building a theme from scratch right now, slowly. Began as a vanilla html markup coder many, many years ago. But, while I want to do this all myself and get why this is better than plugins, which I tend to hate after many happy years with Drupal, I guess it’s best for me to go to Codeable also. Can you tell me, though, what skills should I look for in a codeable developer to effect this infinity scroll for a home page that will be displaying a 3-col grid of different custom content types. That will help me narrow my search. Thank you in advance.

    • Bill Erickson says

      June 7, 2017 at 8:36 am

      A familiarity with WordPress and Javascript.

      But really, there are plugins that do a good job of infinite scroll. I believe Jetpack has a module for it. This tutorial is really for developers who want to build it themselves. I recommend trying existing plugins before hiring someone to build it custom.

      • Melissa says

        August 1, 2017 at 9:23 am

        I agree Bill, there are some good free plugins for this. I actually really like Ajax Load More (
        This plugin uses the same method for loading posts as you outline in your tutorial and you can use the same functions to display posts which makes it easy to match the styling.

        Thanks for the great tutorial BTW.

  49. Diah says

    June 13, 2017 at 11:57 am

    Thanks for the tutorial, Bill.
    I’m trying to implement it my archive page but I’m not quite sure what I’m doing wrong.
    Does this feature override the “Blog pages show at most” setting in “Reading Settings” on the WP Dashboard?
    Do I have to do anything to my index.php page?
    Right now it looks like this:

    if (is_single()) {
    get_template_part(‘partials/loop’, ‘single’);
    } else {
    get_template_part(‘partials/archive-loop’, ‘index’);

    and my archive-loop-index.php page has a 3 column layout.

    Thanks again!

  50. Megan says

    July 26, 2017 at 7:27 pm

    Hi Bill,

    Thanks so much for this post, and for taking the time to answer comments !

    I have read through all of the comments but am still struggling to fully implement this solution for a custom query on a page. It works beautifully if I hard code the args in be_ajax_load_more. Any direction on how to pass the appropriate query arguments would be much appreciated!

  51. Roselle says

    September 9, 2017 at 2:40 pm

    Bill, O Mighty WordPress/Genesis Guru…

    Thank you so much for your time and effort posting this, and helping us as much as you have already. I know you don’t have to debug any of our errors (for free, I might add), but thanks for responding to our comments and helping as much as you can.

    With that being said, I wont take up too much of your time, just hoping you could point me in the right direction…

    Okay, so I am using your ‘be-full-post-scroll’ plugin, filtered it with my own arguments, and created the ‘content-partial.php’ template.

    I can get the next (actually previous) post to load, BUT it loaded the site header and site footer too. So I removed them in the template. After that, it seemed like everything was fine –except that when I looked at the page source, it was still outputting the html meta and scripts of the next post. Also, the URL in the address bar doesn’t change at all. 🙈

    Looking over the previous comments, I could it be that the classes in my $args are wrong…? But when I look at the code, the classes .content and .entry should work. I just don’t even know. What can I do?

    Thanks in advance for your help!

    • Bill Erickson says

      September 11, 2017 at 4:06 pm

      It sounds like your partial is loading an entire page rather than actually a “partial” page. Inside that file you should only output the markup you need. If you’re using genesis, don’t call the genesis(); function as that will load EVERYTHING.

  52. Roselle says

    September 24, 2017 at 2:13 pm


    Thanks for pointing me in the right direction re: the template. I’m no longer calling genesis(); so I got that part working fine now.

    I still have the issue of the URL not changing on scroll down or up (and back/forward button functionality as an extension of the URL change).

    If you/anyone here knows how to remedy that, I’d greatly appreciate it 😇


  53. Albert says

    October 17, 2017 at 8:20 am

    Thanks for this tutorial, but as I am fairly new to ajax, javascript and php, it would appreciate if you can give me a simple example of be_post_summary(), just to give me some idea where to begin

    Thank you

      • Albert says

        October 17, 2017 at 8:51 am

        Thank you for the answer, just i cannot implement it in the index.php part of the template
        Any help on how to do it?

        • Albert says

          October 17, 2017 at 9:22 am

          Sorry just an error form my part, I have commented out the be_post_summary().

          But it does not work, because if i delete $args[‘paged’] = esc_attr( $_POST[‘page’] );, it loads the same 3 posts over and over each time i click the button, and I re insert $args[‘paged’] = esc_attr( $_POST[‘page’] ); it just loads only one post and thats it

          Any help?

        • Bill Erickson says

          October 17, 2017 at 1:22 pm

          I recommend looking at what arguments you’re passing to the AJAX function, specifically the ‘page’ parameter.

  54. Ankish Kumar says

    February 8, 2018 at 2:33 am

    Everything gets working for an infinite scroll, but I also want a GIF image before additional sets of posts are loaded. Is this possible?

  55. Bill Erickson says

    February 9, 2018 at 2:31 pm

    When you get to loading = true; (here), you could add $(button).addClass('active');, and then remove the class when you get to loading = false; here.

    Then in your CSS, do something like { background: url(loading.gif) no-repeat center center;}.

  56. yliac says

    May 16, 2018 at 3:58 pm

    Hi Bill,

    Thanks for the informative post.

    I have ajax infinite scroll built into my theme on a viral video website and im trying to figure out how to customize the trigger to load more posts around halfway down the page instead of once the user reaches the bottom of the page?

    Any insights are much appreciated


    • Bill Erickson says

      May 18, 2018 at 7:47 am

      On this line of the Javascript file, the “2000” refers to how many pixels from the bottom you should trigger the AJAX query. Increasing this number will make the query trigger sooner.

      You could also get creative with it. Instead of hardcoding a number, you could do half the height of the screen, or half the height of the article’s content, or anything else you want.

  57. Mike says

    May 27, 2018 at 8:36 am

    I’ve gone through this tutorial + the comments numerous times and I must be missing some steps that are assumed. I get the first two steps (update functions.php with the two functions and create the load-more.js file), but then what? How do you then modify the theme files to actually implement it? Assume a completely vanilla self-hosted WordPress install with infinite scroll going on a typical blog page.

    • Bill Erickson says

      May 28, 2018 at 8:02 am

      After you have modified the theme to load the JavaScript files and define the AJAX response in functions.php (which it sounds like you have done), you simply need to update .post-listing in the JavaScript file to use the correct class names for whatever HTML element contains all your posts.

  58. Berk says

    September 13, 2018 at 6:46 pm

    Hello Bill, first of all thank you for your writing and your help. I use the “load more” script but how do I remove the load more button after the posts are exhausted ?

  59. grant tailor says

    December 5, 2018 at 9:33 pm

    Getting POST 500 error on admin-ajax.php

    can you explain more how to get the class .post-listing and also .load-more?
    that part if very important in this working but you left that part out

    • Bill Erickson says

      December 5, 2018 at 9:33 pm

      Those classes have been added to my template files. You’ll either need to add them to your template files in the appropriate place, or review your theme’s existing classes and use a class that’s already there (ex: .site-main might be used instead of .post-listing).

      • Imran Chaudhary says

        December 18, 2018 at 4:42 am

        Thanks for this tutorial but I’m getting the same as a few other people:

        POST /admin-ajax.php 500 (Internal Server Error).

        Any suggestions?

        • Bill Erickson says

          December 18, 2018 at 11:22 am

          You’ll have to look in your PHP error log, or use console.log throughout your JavaScript to help debug the issue.

  60. Yesh says

    January 23, 2019 at 12:00 pm

    Hi Bill, Thanks so much for the very insightful tutorial. I am trying to develop an infinite scroll and am having trouble, kind of similar to and my exact problem is this I see that the query parameter that needs to be passed is already set, not empty and being passed but it doesn’t work as expected. Can you please let me know, what to do

  61. Tim says

    February 1, 2019 at 6:18 am

    The 500 error appears because bp_post_summary(); doesn’t exist, which gets called in be_ajax_load_more();

    You’ll need to replace it with your own template data or template PHP file. For me, it was loop-template/content.php:

    get_template_part( ‘loop-templates/content’, get_post_format() );

  62. Nick Wilmot says

    May 15, 2019 at 5:41 am

    Hello Bill,
    As always, thanks for this post!

    What’s the best way of changing the ‘orderby’ value?

    Using the ‘pre_get_posts’ hook only works for the initial set of posts, and not the posts loaded via the AJAX load more.

    • Bill Erickson says

      May 15, 2019 at 9:12 am

      In the be_load_more_js() function we’re including all the relevant query parameters, so you could include your orderby there as well.

      • Nick Wilmot says

        May 15, 2019 at 10:00 am

        Thanks Bill, hmm I still have something going askew somewhere. I’m on a big learning curve with JS at the moment.

        I’ve added:
        ‘orderby’ => ‘title’
        //to the “$args” in the be_load_more_js() function, which in this case is placed on a Genesis CPT archive template.

        orderby: beloadmore.orderby,
        //to the JS

        $args[‘orderby’] = esc_attr( $_POST[‘orderby’] );
        //to the AJAX function in functions.php

        • Bill Erickson says

          May 15, 2019 at 10:12 am

          That all looks right to me. You’ll need to do some debugging to see where it’s going wrong.

          (Do these separately)

          At the end of be_load_more_js(), add print_r( $args ); exit; to see what’s actually in the args.

          In the JS file, add console.log( beloadmore ); to see what’s being received by the JS file (look in the browser’s console – open your browser’s inspector and press ~)

          In the AJAX function, either add the received data to the error log (error_log( print_r( $_POST ) );) or temporarily return the $_POST data ( wp_send_json_success( $_POST );) and in the JS file, log the response ( if( res.success ) { console.log( ); })

          That should help you track down where its being lost.

          • Nick Wilmot says

            May 15, 2019 at 10:59 am

            Thank you for your insight Bill, I just learnt a few new debug tips:-)

            The ‘orderby’ $arg value of ‘title’ is definitely passing all the way through to the AJAX function, and I can echo it out from within the AJAX loop. Maybe syntax isn’t accepted by WP_Query for the orderby value, as even if I override it to ($args[‘orderby’] = ‘title’;), it’s ignored and default ordering is applied?

            The posts in the AJAX loop are correct, it’s subtracting the first page fine, and not duplicating anything. They’re just not running in the correct ‘orderby’ sequence

            • Bill Erickson says

              May 15, 2019 at 12:10 pm

              ‘title’ is a legitimate orderby option (see here).

              The only thing I can think of is something is hooked to pre_get_posts and overriding the orderby parameter. Hook your own function into pre_get_posts with a priority of 999 and print_r( $query ); exit; to see what’s happening.

  63. Nick Wilmot says

    May 15, 2019 at 3:09 pm

    By syntax I meant the way by which the $args are presented is not as per the codex for a WP_Query. However, when I strip that out and replace it with an array as per codex, with hard values set, it still won’t behave and respect the ordering! And nothing abnormal reporting in pre_get_posts either.

    Something quirky going on somewhere! I’m sure I’ll resolve it eventually.

    Thanks so much for your advice, Bill… I learnt a few things and really appricate your input.

  64. Nick Wilmot says

    May 15, 2019 at 3:19 pm


    If I run ( remove_all_filters(‘posts_orderby’); ) before the new WP_Query(), it’s all fine and dandy!

    Thanks again for your help:-)

Leave A Reply