How to Load WordPress Posts With Ajax

While working on a WordPress-powered website, sometimes you may come across a situation where you want to load WordPress posts with Ajax. In this tutorial, we will achieve it without pagination links. Instead, it will have a ‘Load More’ button/link and on clicking this button the next set of posts will display. This process continues until all posts are shown.

To see it in action, let’s assume you have a couple of posts and you need to display 2 posts the first time. Then by clicking on ‘Load More’ the next 2 posts should display. And it will continue until the last post.

This approach is useful as your web page will not refresh to view the next set of articles. Instead, it just appends the next posts in the DOM without reloading a page. We will keep loading WordPress posts inside the div container with the help of Ajax and jQuery. It adds a better UX and also saves an extra load on the server.

That being said, let’s see how to load posts with Ajax and jQuery in WordPress.

Getting Started

To recap the flow, when someone visits your page for the first time, you need to show a few posts. Thereafter next posts will display after clicking on the ‘Load More’ button.

For this, I will write the code in a custom page template. Create a new page called ‘Blog’ in the backend. Now, to display your posts on this page create a custom template page-blog.php in your active theme’s directory. I kept the name as page-blog.php following the template hierarchy which suggests the format as a page-{slug}.php. Adjust this slug as per your requirement.

Let’s display the first 2 posts when users visit the blog page. With the help of WP_Query class, you can do it as shown below. Add the following code to the custom template.

<?php
$args = array(
    'post_type' => 'post',
    'post_status' => 'publish',
    'posts_per_page' => '2',
    'paged' => 1,
);
$blog_posts = new WP_Query( $args );
?>
<div class="entry-content">
    <?php if ( $blog_posts->have_posts() ) : ?>
        <div class="blog-posts">
            <?php while ( $blog_posts->have_posts() ) : $blog_posts->the_post(); ?>
                <h2><?php the_title(); ?></h2>
                <?php the_excerpt(); ?>
            <?php endwhile; ?>
            <?php wp_reset_postdata(); ?>
        </div>
        <div class="loadmore">Load More...</div>
    <?php endif; ?>
</div><!-- .entry-content -->

Here, I used the ‘paged’ key with the value ‘1’. The ‘paged’ is a key used in WordPress when it comes to pagination. The ‘paged' => 1 means the first page.

I also added the div element having class loadmore and text ‘Load More’. Using a given class you can add styling to this container. On clicking this ‘Load More’, I’ll send an Ajax request, get the next set of posts, and append the response to the div having a class ‘blog-posts’.

Note: You can also use the same technique for the custom post types. Notice, I am passing post_type as an argument to the WP_Query.

Load WordPress Posts with Ajax

Next, to integrate Ajax include a custom.js file in the WordPress environment. Create this file inside the js directory and add the below code to the functions.php file.

function blog_scripts() {
    // Register the script
    wp_register_script( 'custom-script', get_stylesheet_directory_uri(). '/js/custom.js', array('jquery'), false, true );
 
    // Localize the script with new data
    $script_data_array = array(
        'ajaxurl' => admin_url( 'admin-ajax.php' ),
        'security' => wp_create_nonce( 'load_more_posts' ),
    );
    wp_localize_script( 'custom-script', 'blog', $script_data_array );
 
    // Enqueued script with localized data.
    wp_enqueue_script( 'custom-script' );
}
add_action( 'wp_enqueue_scripts', 'blog_scripts' );

This code will include custom.js in the WordPress environment. I’m also passing 2 values to it.

  • ajaxurl : In WordPress, it is required to call the admin-ajax.php URL to accomplish the Ajax request. I set this URL to the ajaxurl key.
  • security : It’ll hold a nonce to avoid CSRF attacks.

Next, let’s write a jQuery code that handles the following stuff.

  • Give an Ajax call along with a few values.
  • Receives the response.
  • Append the response to the div having a ‘blog-posts’ class.
  • Hide the ‘Load More’ button on receiving an empty response.

custom.js

var page = 2;
jQuery(function($) {
    $('body').on('click', '.loadmore', function() {
        var data = {
            'action': 'load_posts_by_ajax',
            'page': page,
            'security': blog.security
        };
 
        $.post(blog.ajaxurl, data, function(response) {
            if($.trim(response) != '') {
                $('.blog-posts').append(response);
                page++;
            } else {
                $('.loadmore').hide();
            }
        });
    });
});

In the above code, I declared a JavaScript variable ‘page’ with an initial value of 2. This is because we need to get the posts starting from the second page(posts on the first page are already displayed.). I am incrementing this ‘page’ variable after receiving the Ajax response which will give us the next paginate records.

I used the action parameter ‘load_posts_by_ajax’ which needs to map with WordPress actions. Open your functions.php file and add the below lines to it.

add_action('wp_ajax_load_posts_by_ajax', 'load_posts_by_ajax_callback');
add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_posts_by_ajax_callback');

The ‘wp_ajax_{action}’ fires Ajax action which refers to the callback function which is ‘load_posts_by_ajax_callback’. The ‘wp_ajax_nopriv_{action}’ executes Ajax action for users that are not logged in. Let’s define this method in the functions.php file.

<?php
...
function load_posts_by_ajax_callback() {
    check_ajax_referer('load_more_posts', 'security');
    $args = array(
        'post_type' => 'post',
        'post_status' => 'publish',
        'posts_per_page' => '2',
        'paged' => $_POST['page'],
    );
    $blog_posts = new WP_Query( $args );
    ?>
 
    <?php if ( $blog_posts->have_posts() ) : ?>
        <?php while ( $blog_posts->have_posts() ) : $blog_posts->the_post(); ?>
            <h2><?php the_title(); ?></h2>
            <?php the_excerpt(); ?>
        <?php endwhile; ?>
        <?php wp_reset_postdata(); ?>
    <?php endif; ?>
    <?php
    wp_die();
}

This code takes the ‘page’ value and uses it for the ‘paged’ parameter. It fetches posts, loops through it, and builds the output.

Now when you click on the ‘Load More’ button it will load the next posts and return back the response. The container for ‘Load More’ will be there until you get all your posts.

I hope you learned how to load WordPress posts with Ajax and jQuery. You should now easily integrate it into your WordPress projects. All you need to do is adjust some styling matching to your theme and might change the value for posts_per_page.

Related Articles

If you liked this article, then please subscribe to our YouTube Channel for video tutorials.

104 thoughts on “How to Load WordPress Posts With Ajax

  1. Hi there and thanks for the tutorial!
    Is it possible to have initially 0 posts in a div and then on click populate this div via ajax with a specific number of posts? Thanks in advance!

  2. Sir the JS code isn’t working.
    Console error
    custom.js?ver=1:10 Uncaught ReferenceError: blog is not defined

    Sir please help what did I wrong?

  3. Thank you for the tutorial, it helped me a lot. On my website I’d like the page load ALL the posts when the Load more-button is clicked. Do you have any idea how could I do this? I can get it to load next page or the one after that, but can’t get them to load at the same time… I really appreciate your help Sajid.

    1. It seems you want to load all posts on click of Load More button. In that case, set -1 value for the ‘posts_per_page’ key in the Ajax callback method.
      -1 means returns all posts.

  4. Thanks a lot for this, it works great. I’m having an issue though where the first post is displayed last. I’m using ‘orderby’ => ‘date’ with a custom post type but apart from that the code is the same. Any ideas?

  5. Hi,

    First of all thanks for the loading more code 🙂

    Can you please guide me how to add loading icon when you click on Load more button?

    Thanks

  6. Hi,
    Thanks. who is looking will find 🙂
    It works and lists all products as it should.
    I would like to use it to list taxonomy but I can’t get terms ID there.
    When I do it manually, it works.
    $category = ’63’;
    $ args = array (
    ‘post_type’ => ‘products’,
    ‘tax_query’ => array (
    array (
    ‘taxonomy’ => ‘category’,
    ‘terms’ => $category
    )
    ),
    ‘post_status’ => ‘publish’,
    ‘posts_per_page’ => ‘4’,
    ‘paged’ => $ paged,
    );

    Can you please advise me how to get that variable there?

    Thanks

  7. Got it. For everyone needing button to disappear automatically here is the code:

    var publication = 2;
    jQuery(function($) {
    function appendPost(data, callback) {
    $.post(blog.ajaxurl, data, function(response) {
    if ($.trim(response) == ”) {
    $(‘.load_more_publications’).hide();
    }
    $(‘.publications-wrapper’).append(response);
    publication++;
    data.page = publication;
    $.post(blog.ajaxurl, data, function(response) {
    if ($.trim(response) == ”) {
    $(‘.load_more_publications’).hide();
    }
    });
    });
    }

    $(‘body’).on(‘click’, ‘.load_more_publications’, function() {
    var data = {
    action: ‘load_posts_by_ajax’,
    page: publication,
    security: blog.security,
    };
    appendPost(data);
    });
    });

    1. Hi, just wanted to thank you for your comment. The code is working fine, the load more button dissapears when there is no more posts to be loaded.

  8. Hey guys, did anybody figured out how to automatically hide button when there is no more posts without clicking the button?

  9. Hi there,
    Thank you so much for sharing with us.

    But I want to use it for two different post types. How can I get this?

    #Help.

  10. Hi, I have a problem. When I click the “Load more …” button, the posts are shown in duplicate. How can I solve that? I am new to ajax … thank you very much

  11. This part of the code:

    if(response != ”) {
    $(‘.blog-posts’).append(response);
    page++;

    }

    Does not return an empty string ( ‘ ‘ ) even if there are no more posts, and this is why the button does not disappear.

    Use if($.trim(response) != ”) to remove whitespace and allow checking for non-response

  12. Is there a way to have a message say “loading” or something when you click the button, there is a slight delay as it gathers the content and so it would be nice to have a “loading” message so users know its working and not broken

  13. Hi, thanks for the post!
    Works great but I would like to load more posts on scroll when reaching the end of an container element.
    What’s the best practice here?

  14. Perfect tutorial! One question, how do I hide the Load more button when it’s reached the max number of posts? Thanks

      1. Thanks for replying. I changed my script to the following.

        It shows the button and loads more on click as it should do.
        But it still shows when there is no more to load, It only hides when I click the button again.
        Your help is much appreciated.

        $.post(ajaxurl, data, function(response) {
        if(response != ”) {
        $(‘.my-posts’).append(response);
        page++;
        } else {
        $(‘.load-more’).hide();
        }
        });

      2. Here is my full script:

        $(‘body’).on(‘click’, ‘.loadmore’, function() {
        var data = {
        ‘action’: ‘load_posts_by_ajax’,
        ‘page’: page,
        ‘security’: ”
        };

        $.post(ajaxurl, data, function(response) {
        if(response != ”) {
        $(‘.my-posts’).append(response);
        page++;
        } else {
        $(‘.loadmore’).hide();
        }
        });
        });

  15. I have got the basic version working, but how do I adapt it for my needs… ?

    – I have a custom query – how do I accommodate this? Actually, multiple similar query blocks on the same taxonomy template, each output with foreach, and each of which has slightly different arguments. One problem with the code as-is is that it does not discriminate. How can I made the call specific to the section in which it was clicked and to use the same query?
    – Also, when I click “Load more”, *all* sections load new posts. But it should only be the section which was clicked.
    – I could adapt the .my-posts class in the HTML to something like .my-posts-somethingunique – but how can I make the Javascript service anything more than .my-posts, and how can I ensure that load comes from the correct “Load more” link click? Something to do with a data= parameter in the tag… ?

    More about my case at https://wordpress.stackexchange.com/questions/328685/how-to-load-more-posts-via-ajax from before I got this basic version working.

    Thanks.

  16. Hi Sir,
    Implemented the steps you mentioned, but i have showing :-
    wp-admin/admin-ajax.php 500 (Internal Server Error)
    I had installed Ajax Load More plugin.. but now i removed it , also i increased the memmory limit in wp-config.php

  17. Hi, I find a solution for loadmore button. It will be hidden if no more post.
    var data = {
    ‘action’: ‘load_posts_by_ajax’,
    ‘page’: page,
    ‘security’: ‘@php echo wp_create_nonce(“load_more_posts”); @endphp’,
    ‘max_page’: ‘@php echo $posts->max_num_pages + 1; @endphp’
    };
    if(response != ”) {
    $(‘.post-boxs’).append(response);
    page++;
    }

    if (page == data[‘max_page’]) {
    $(‘.load-more’).hide();
    }

  18. Thank you for the code but I have problems with metabox data in my-posts div wrapper.

    For some reason the_content() or the_title() works well but for example values like $dayevent = get_post_meta( $post->ID, ‘dayevent’, true ); no.
    Any help appreciated.

  19. Hi !
    Great article !
    But i had a problem with posts_per_page
    If i set defaulft show post are 9 post, then click load more 3 post but it not work.
    What happen with that? and how can i fix them?
    Need your help ! Thanks. 🙁

  20. Hi! in the loop of repeated entries, tell me how to remove? $my_posts = new WP_Query( $args );
    if ( $my_posts->have_posts() ) :
    ?>
    have_posts() ) : $my_posts->the_post() ?>

  21. 1. How can I use load more button for custom posts type taxonomy term like category. I use filtering option of custom post category
    2. How can I remove load more button when all of my posts are loaded. Please send coding script of above query.

    1. When I click load more button always show 0.every click a 0 is added. How can i remove or hide this 0 value. Please give me a suggestion.

  22. Hi Its a nice article, which helps me to achieve something I wanted very long time. Even non coder can learn this and try this out.

    Here is my request:
    Can you add Lazy load image before loading posts and hide after loaded. Also is the any chance we can add the fadeIn effect to the posts when they added?

  23. How can we add select dropdown to select categories and load posts from that category ?
    Im trying to add this functionality here in this code

  24. With this idea the button is not hide automatically. Here user must be click on Button, then after if There is no posts then button will be hide.
    if(response != ”) {
    $(‘.my-posts’).append(response);
    page++;
    } else {
    $(‘.load-more’).hide();
    }

    But i want that without clicking the button at last, the button should automatically hide, if there is no posts.
    How to do that? any idea?

      1. $.post(ajaxurl, data, function(response) {
        console.log(response);
        if(response == ”){
        $(‘.loadmore’).hide();
        }
        $(‘.jmasonry’).append(response);
        page++;
        });

  25. Hello, I think it is working but with some caveats…. The initial button doesn’t get hided after click and the button doesn’t move, meaning it should be at the end of the loaded posts… oddly if there are no more posts and the actual already used button gets clicked it populates duplicated entries Mmm. Any fix?

    1. We have written $(‘.my-posts’).append(response); And our Load More button is outside of div with class my-posts. So Load More will always stick at the end. And after each response, we are incrementing page variable so there is no chance of duplicate records. Maybe in your case you are missing the position of Load More.

      1. Oh you are right I fixed that it is working ok 😉
        What do you recomend for hiding the load more button when there are no more posts to display and after clicking the load more button?
        For the second maybe I should mix the following code:

        $(function(){
        $(“.loadmore”).on(‘click’,function() {
        $(this).css(‘visibility’,’hidden’);
        });
        });

        with the code in the guide:

        var ajaxurl = “”;
        var page = 2;
        jQuery(function($) {
        $(‘body’).on(‘click’, ‘.loadmore’, function() {
        var data = {
        ‘action’: ‘load_posts_by_ajax’,
        ‘page’: page,
        ‘security’: ”
        };

        $.post(ajaxurl, data, function(response) {
        $(‘.my-posts’).append(response);
        page++;
        });
        });
        });

        1. You should manage it from ajax callback functions. Check where WP_Query returns records. If not send flag(like noresult) in response else send next posts(which we are currently returning).
          And then in JavaScript, you can hide the Load more on the basis of a flag.

          1. Ok really wish I knew that much enough to follow that instructions, for which I’m afraid I don’t. If you guide me (like pointing me to the reference) I will read it/learn and try it out. Regards.

          2. When no posts are available we get empty ajax response which you can use to hide load more button. In the JavaScript code of ajax response change the code like
            if(response != ”) {
            $(‘.my-posts’).append(response);
            page++;
            } else {
            $(‘.load-more’).hide();
            }

          3. Ok edited the code as follows:

            var ajaxurl = “”;
            var page = 2;
            jQuery(function($) {
            $(‘body’).on(‘click’, ‘.loadmore’, function() {
            var data = {
            ‘action’: ‘load_posts_by_ajax’,
            ‘page’: page,
            ‘security’: ”

            };

            $.post(ajaxurl, data, function(response) {
            if(response != ”) {
            $(‘.my-posts’).append(response);
            page++;
            } else {
            $(‘.load-more’).hide();
            }
            });
            });
            });

            But the button is not loading more posts.. will keep trying changes..

  26. Hi,
    I’m using this script in my blog section button click is working but i am going to implement window scroll is not firing please advise to me.

  27. Hi Artisan,

    Now I’m wondering something else. I would like for the button to dissapear when all posts are loaded, it that possible?

    Thank you!

    1. Yes, it is possible. Wrap your each post in a div with the common class.
      By this way, calling ‘length’ method we come to know the count of post divs.
      On the top of a front page PHP file, fetch the count of all posts and assign it to a variable.
      Assign this PHP variable to JavaScript variable. Now on each Ajax response check if a count of post divs reaches a value of a variable. If both values are same then hide load more button.
      Let me know if you understand this or need more help.

      1. Hi,
        I’m also interested n this, but i’m lost on details, can you help me with hiding load more button at the end of posts ?

        Thanks

        1. I’ve solved this by adding “else” to the loop to creating a new “div” that will show a message if no more posts. After Edit this:

          No More Posts!
          0 ) $(‘.loadmore’).hide();

  28. Hi again!

    I figured it out!
    I had the same .class on my div in my first query as in my second query!

    1. “I had the same .class on my div in my first query as in my second query!” Hello Victor! Can you share the steps on how you did it?

    2. Yes got it working as well… in case someone wonders how:
      Rename all “my-posts” for the second instance of ajax for something like “my-posts2”, same with “security2”, “loadmore2”,
      wp_ajax_load_posts_by_ajax2′, ‘load_posts_by_ajax_callback2’);
      add_action(‘wp_ajax_nopriv_load_posts_by_ajax2’, ‘load_posts_by_ajax_callback2, ‘load_posts_by_ajax2, “load_more_posts2”, “.my-posts2” Hope it helps…

      1. Forgot to add you may need to create a second instance of the code for each ajax instance. For example for three ajax buttons you should have:
        For the first ajax button:

        var ajaxurl = “”;
        var page = 2;
        jQuery(function($) {
        $(‘body’).on(‘click’, ‘.loadmore’, function() {
        var data = {
        ‘action’: ‘load_posts_by_ajax’,
        ‘page’: page,
        ‘security’: ”

        };

        $.post(ajaxurl, data, function(response) {
        $(‘.my-posts’).append(response);
        page++;
        });
        });
        });

        For the second ajax button:

        var ajaxurl = “”;
        var page = 2;
        jQuery(function($) {
        $(‘body’).on(‘click’, ‘.loadmore2’, function() {
        var data = {
        ‘action’: ‘load_posts_by_ajax2’,
        ‘page’: page,
        ‘security2’: ”

        };

        $.post(ajaxurl, data, function(response) {
        $(‘.my-posts2’).append(response);
        page++;
        });
        });
        });

        For the third ajax button:

        var ajaxurl = “”;
        var page = 2;
        jQuery(function($) {
        $(‘body’).on(‘click’, ‘.loadmore3’, function() {
        var data = {
        ‘action’: ‘load_posts_by_ajax3’,
        ‘page’: page,
        ‘security3’: ”

        };

        $.post(ajaxurl, data, function(response) {
        $(‘.my-posts3’).append(response);
        page++;
        });
        });
        });

        Maybe this could be done in a more efficient way.. but thats at least should work

        1. I realized there’s a problem with using in the same page two ajax buttons… they share the page, so that if for example I use the first button once and ten use the other button, the later will load not the second page but the third… Haven’t figured out yet how to solve it… must be something like assigning a different name for each page?

          1. So far the second button looks like this:

            1. in header:
            var page1 = 2;
            ‘page1’: page1,

            2. in functions.php
            $paged1 = $_POST[‘page1’];
            ‘paged1’ => $paged1,

            3. in the template
            $paged1 = ( get_query_var( ‘paged1’ ) );
            ‘paged1’ => 1,

          2. Use 3 different pages variables for each ajax call so it does not get repeated for each other.
            var page1 = 2;var page2 = 2;var page3 = 2;

          3. @Sajid Can you elaborate more because I don’t think you mean the above… like dummy proof it may serve others as well

          4. I got it, the values that have to be modified are (I added an extra “7”):
            in footer or header
            var page7
            ‘page7’: page7,
            page7++;
            in functions
            $paged7 = $_POST[‘page7’];
            ‘paged’ => $paged7,

  29. Hi Artisan,

    I’ve implemented your code and it works great when the query is on a page with no other queries. However, I would like to implement it on my front page. On that page I have two other queries. When i press the Load More-button, the ajax-function loads the correct posts but it loads the posts twice.

    I’ve tried to use the wp_post_reset but it doesn’t work.
    Have you noticed this before?

  30. John, It is possible to apply load more post of different categories multiple times. In you case need to work on some logic to achieve the final goal.

  31. Great bro !

    What if: Let`s assume, I do have posts slider on my page, then I want to display the content of a post that I clicked, but I want it to be shown below the post slider.

Leave a Reply

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