I am working in a WordPress site that has many custom post types, and extensive use of Advanced Custom Fields (ACF). The site also uses node modules, custom code from scratch, and Git in favor of a pre-built theme and "typical" plugins. Many of the tasks I do involve working with code already written.

The site is a safari business. We have Continents, Countries (sometimes called Destinations in certain contexts), Places, and Testimonials - all are custom posts. None of which use any taxonomies at all, ever. The testimonials already have custom fields via ACF connecting the Testimonials with appropriate Continents, Countries, and Places.

My issue is pretty complex, and I'll do my best to explain it.

My goal Create an Ajax filter to display Testimonials by Continent, Country, and (eventually) Place, without a plugin. Continent and Country can be select fields, and only one is required, optionally both can be set.

The site already has an almost identical version of this, working. It has dropdown selects that filter one of the other custom post types, Itineraries. What I need is exactly like this, just for Testimonials instead of Itineraries. That page is https://extraordinaryjourneys.com/plan-your-trip/itineraries/.

It uses NPM Select-custom and a few PHP and JS files. I've included two of them below.

What I have tried I have made duplicated files of the Itineraries files for Testimonials, and trying to make those work just like that Itineraries page working example is. It might be easier to write meta_queries and if so I'll need a little guidance there, but i thought this would be easier. These are the files, including sections I've commented out because they were related to one of the dropdowns from the itineraries page that don't need to be on this testimonials page, like trip budget, or because it's part of me figuring out how this all works.

Where I am stuck I've pushed the code to the staging site, and you can see the console errors. Can you help me troubleshoot the api 404 call? The already existing Itineraries page linked above has the same, but without error. https://staging.extraordinaryjourneys.com/testimonial-troubleshooting You can click the Continent or Country selects and you'll get an alert, which must be related to the api call. Screenshot here showing what you'll see in console.

If there's anything else to include or add I am happy to.

  • hero-filter-testimonials.js
import '../elements/filter-results-grid';

$(document).ready(function () {
   const $body = $('body');
   const $filterTestimonials = $('.filter-testimonials');
   $body.on('click', '.btn-load-more', function (e) {
       getResults(null, parseInt(e.currentTarget.dataset.page));

   $('.clear-all').click(function () {
       window.history.replaceState(null, null, '#');
       triggerSearch(getParamsObject(), $filterTestimonials);

   continentContainer.find('.custom-select__option').on('click', function (e) {
       let continentId = e.currentTarget.dataset.value;

   function showCountriesOptions(continentId = null) {
       const $selectContainer = $filterTestimonials.find('#destination').parent();
       console.log('Line 38 hero-filter-testimonials.js: continentId: ' + continentId);
       if (continentId) {
           $selectContainer.find(".custom-select__option[data-continent='" + continentId + "']").show();
       } else {

   function getResults(origin = null, page = 1) {
       console.log("getResults(): " + getResults);
       let data = {action: 'get_testimonial_results'};


       const $select = $filterTestimonials.find('.custom-select');

       data = getDataFromSelectors(data, $select);

       if (origin && origin === 'continent') {
           if (data['destination']) {
               delete data['destination'];

       console.log("Line below is log.data");
       console.log("Line below is dir.data");

       if (Object.keys(data).length === 1) {
       } else {
           let $loader = $body.find('.filter-loader');
           getFilterResults('get_testimonial_results', 'Testimonials', data, $loader, page);
       addBadges($filterTestimonials, ['continent']);

   window.getResults = getResults;

   let data = getParamsObject();
   console.log("Line below is data");
   console.log("Line below is $filterTestimonials");
   // console.log(JSON.stringify($filterTestimonials,null,2));
   triggerSearch(data, $filterTestimonials);
  • testimonials_results.php This page makes a custom api call that returns a 404, and I don't know why. It also has the query to get testimonials posts.

add_action('rest_api_init', function () {
    register_rest_route('ejourneysnineteen/v1', '/get_testimonial_results', array(
        'methods' => 'POST',
        'callback' => 'get_testimonial_results',
        'permission_callback' => function () {
            return true;

function get_testimonial_results() {
    $pageSize = $_REQUEST['pageSize'] ?? 10;

    $args = [
        'posts_per_page' => $pageSize,
        'ignore_sticky_posts' => 1,
        'post_type' => 'testimonial',
        'orderby' => 'title',
        'order' => 'ASC',

    if (isset($_REQUEST['page'])) {
        $args['paged'] = $_REQUEST['page'];

    if (isset($_REQUEST['continent']) || 
    //  isset($_REQUEST['region']) ||
    //  isset($_REQUEST['accommodation_type']
     ) {
        // $metaQueries = ['relation' => 'AND'];

        if (isset($_REQUEST['region'])) {
            $regions = explode(',', $_REQUEST['region']);
            $subquery = [
                'relation' => 'OR'
            foreach ($regions as $region) {
                $subquery[] = [
                    'key' => 'accommodation_region',
                    'value' => $region,
                    'compare' => 'LIKE'
            $metaQueries[] = $subquery;
        else {
            if (isset($_REQUEST['destination'])) {
                $metaQueries[] = [
                    'key' => 'testimonial_country',
                    'value' => $_REQUEST['destination'],
                    'compare' => 'LIKE',
            else {
                if (isset($_REQUEST['continent'])) {
                    $metaQueries[] = [
                        'key' => 'testimonial_continent',
                        'value' => $_REQUEST['continent'],
                        'compare' => 'LIKE',

              $args = array_merge($args, [
            'meta_query' => $metaQueries

    $result = [];
    if (count($metaQueries) > 1) {
        $query = new WP_Query($args);

        foreach ($query->get_posts() as $post) {
            $post_thumbnail_id = get_post_thumbnail_id($post);
            $image_url = fly_get_attachment_image_src($post_thumbnail_id, [500, 500], true);
            $result[] = [
                'link' => get_the_permalink($post),
                'title' => $post->post_title,
                'excerpt' => html_entity_decode(ellipsis(get_the_excerpt($post), 80)),
                'image_url' => $image_url,
                'category' => 'Testimonial'
        'results' => $result,
        'total' => $query->found_posts,
        'pages' => $query->max_num_pages,

There are other files but I'm so stuck I'm not sure if I should add them all there or not. Is this enough to help me troubleshoot the api call 404?

  • 1
    There are other issues in your code (e.g. the $metaQueries should be defined before the if (isset($_REQUEST['continent']) ...) line), but as for the 404 error, the full message I saw (here via a POST request) is, "No route was found matching the URL and request method.", so how are you loading the testimonials_results.php file? Is it include-d or require-d from within the functions.php file? Does your AJAX request actually make a POST request?
    – Sally CJ
    Feb 11, 2022 at 13:11
  • (Yes I already tried with a POST request and it didn't work, but just making sure your AJAX is using the POST request method) Also, "Screenshot here" - where is it? And actually, instead of creating a custom endpoint, you could simply use the rest_testimonial_query hook to modify the query args so that you could just use the default endpoint to fetch the testimonial posts..
    – Sally CJ
    Feb 11, 2022 at 13:23
  • 1
    Hi Sally, turns out I was not loading testimonials_results at all. I added it as a require_once in functions.php. That got rid of the 404! Thank you for taking a look. What's the best practice for this, here - edit the question for the other issues, upvote comment, or something else?
    – Ben Blue
    Feb 11, 2022 at 14:07
  • Honestly, I'm not too sure, either. But you could just post an answer for the main issue (the 404 error). Anyway, I posted an answer and also answered the other issues..
    – Sally CJ
    Feb 11, 2022 at 16:19
  • 1
    Either way, thank you for the awesome comments! I am just now coming back to this, and referencing your tips.
    – Ben Blue
    Feb 14, 2022 at 19:25

1 Answer 1


As I said in my comment, I made a POST request to your custom endpoint (here) and then received a 404 error with this message: "No route was found matching the URL and request method.", which likely means the route was never registered, or that the request method was not supported, i.e. not in the methods list specified during registration (using register_rest_route()).

So you just need to ensure the route is registered properly and that your (AJAX) request is using the correct HTTP request method, and then the 404 error would be gone.

And in your case, you can ensure the route is registered properly by loading the testimonials_results.php file from within the theme's functions.php file. E.g.

// Assuming the file is at the same level as the functions.php file
require_once __DIR__ . '/testimonials_results.php';

Other issues in your code

  1. The $metaQueries variable is not defined, and should be defined before the if (isset($_REQUEST['continent']) ...) line.

  2. The isset($_REQUEST['region']) should not be commented out, because otherwise if only the region parameter specified, then your custom posts query would never run.

  3. The above posts query will also run only if $metaQueries has 2 or more items, so if the array is empty or has just 1 item, the $query in your return part would be an undefined and results in a PHP notice saying Undefined variable: query.

    And it's up to you on how should that be fixed.. but for example, you could add an else, and return an error.

  4. When the WP_Query class is instantiated with a non-empty args, i.e. new WP_Query($args) as opposed to new WP_Query(), the get_posts() method will be automatically called, hence you should not call it manually.

    So replace the $query->get_posts() with $query->posts to access/retrieve the posts that already retrieved via the automatic call to the get_posts() method.

  5. From the REST API handbook:

    Importantly, a REST API route’s callback should always return data; it shouldn’t attempt to send the response body itself. This ensures that the additional processing that the REST API server does, like handling linking/embedding, sending headers, etc… takes place. In other words, don’t call die( wp_json_encode( $data ) ); or wp_send_json( $data ).

    So don't call wp_send_json_success() (which uses wp_send_json()), and simply return the data. I.e. return [ 'results' => $result, ... ];

Additional Notes/Info

  1. WordPress has a __return_true() function, so you could simply do 'permission_callback' => '__return_true' :)

  2. The $args = array_merge($args, [ 'meta_query' => $metaQueries ]); could just be written as $args['meta_query'] = $metaQueries; ..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.