Creating a search widget using the WordPress REST API

By Kirsty Burgoine

WordPress developers will be aware the REST API has been around for some time, but as of late last year it was integrated into core, meaning it can now be leveraged in widgets and plugins.

In this article we are going to build a simple search widget with a difference. Using the WordPress REST API, this widget will search an external WordPress website and display the results in the widget.

This post continues from my last one on What you need to know about the WordPress REST API

Building the widget plugin

As the previous article, some basic knowledge of building plugins and widgets is assumed. If you are unsure, there is some suggested reading for you at the end of this article.

Let’s take a look at the existing plugin we created in the last article. Your code should look something like this:


/**
* Plugin Name: KB - REST API Widget
* Author: Kirsty Burgoine
*/

class REST_API_Widget extends WP_Widget {

/**
* Sets up the widgets name etc
*/
public function __construct() {
    $widget_ops = array(
        'classname' => 'rest-api-widget',
        'description' => 'A REST API widget that pulls posts from a different website'
    );
    parent::__construct( 'rest_api_widget', 'REST API Widget', $widget_ops );
}

/**
* Outputs the content of the widget
*
* @param array $args
* @param array $instance
*/
public function widget( $args, $instance ) {
    // outputs the content of the widget

    $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2 /ht_kb/' );
    if( is_wp_error( $response ) ) {
        return;
    }

    $posts = json_decode( wp_remote_retrieve_body( $response ) );

    if( empty( $posts ) ) {
        return;
    }

    echo $args['before_widget'];
    if( !empty( $instance['title'] ) ) {
        echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ) . $args['after_title'];
    }

    // Main Widget Content Here
    if( !empty( $posts ) ) {
        echo '<ul>';
        foreach( $posts as $post ) {
            echo '<li><a href="' . $post->link. '">' . $post->title->rendered . '</a></li>';
        }
        echo '</ul>';
    }
    echo $args['after_widget'];
}

/**
* Outputs the options form on admin
*
* @param array $instance The widget options
*/
public function form( $instance ) {
    //outputs the options form on admin
    $title = ( !empty( $instance['title'] ) ) ? $instance['title'] : '';
    ?>
         <label for="<?php echo $this->get_field_name( 'title' ); ?>">Title: </label>
         <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /> 
    <?php
}
}//end class
add_action( 'widgets_init', function(){
register_widget( 'REST_API_Widget' );
});

Essentially, we created a new plugin. In that plugin we created a widget that displays a list of custom posts from the Heroic Knowledge base from a different website.

What we are going to do now is amend this to create a search feature as well.

Step 1: Amending the basic query to get different results

In the last article you will remember we discussed how to GET a list of posts and then how to change the parameters so that we could get the knowledge base custom post type instead.

Well, it may not be that surprising to learn, that’s not all we can do with the GET method. By using the filtering parameters we can actually use any of the arguments available when calling WP_Query. For example, if we wanted to change the number of posts displayed you could add:


/posts?filter[posts_per_page]=5

Making the remote call something like this:


$response = wp_remote_get( 'http://website-with -api.com.com/wp-json/wp/v2/posts?filter[posts_per_page]=5' );

Or, if we wanted to only display one random post we could add:


/posts?filter[orderby]=rand&filter[posts_per_page]=1

And this is the same for a search parameter. In a standard WP Query, the parameter would look like this:


's' => 'keyword'

Therefore, our remote call needs to look like this:


$response = wp_remote_get( 'http://website-with -api.com.com/wp-json/wp/v2/posts?filter[s]=keyword');

It’s quite simple once you get your head around converting parameters from the WP_Query into the API url string.

For full details about what parameters are available, see the WP_Query codex guide.

We don’t actually want to search standard posts though, we want to search the knowledge base custom post type. However, as we saw in the previous article, this is easy to do because we made our custom post type publicly available by adding a filter. Therefore, all we need to do here is query the knowledge base custom post type by swapping /posts/ with the name of the custom post type. In this case /ht_kb/. The remote call will now look like this:


$response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb?filter[s]=keyword' );

Step 2: The search box and tailoring results

So now we’ve looked at amending the query to filter by a given search term, the next step is to create a way to get that search term.

Firstly, we need to add a search box to the plugin. Because this only going to be a simple search form, we can use the get_search_form() function already set up by WordPress instead of creating a seperate search form.

In the plugin file, look for the line with the comment // Main Widget Content Here inside your widget function and add get_search_form(); and edit directly below it so that it looks like this:


// Main Widget Content Here
get_search_form();

if( !empty( $posts ) ) {
    echo '<ul>';
    foreach( $posts as $post ) {
        echo '<li><a href="' . $post->link. '">' . $post->title->rendered . '</a></li>';
    }
    echo '</ul>';
}

Now when we view the widget on page you will see the standard search form and the list of posts from the Knowledge Base underneath it.

standard-search-widget

We know from using get_search_form(); that when we search for something, the search parameter appears in the url like this:


/?s=test

‘s’ being the $_GET parameter and ‘test’ being your search term. Therefore, all that is left is to grab the search term from the URL and use it in the response like so:


if ( isset ( $_GET['s'] ) && !empty( $_GET['s'] ) ) {
    $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb?filter[s]='.$_GET['s'] );
}
else {
    $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb/' );
}

What this code does is check to see if $_GET[‘s’] has been set and is not empty. If it has/is then you include it in your response. If not, then a search is not being performed and you want to show all of the knowledge base posts as normal.

More detail about the get_search_form() function can be found here.

This is where we would expect this article to end right? After all we have set up a search form that queries posts on an external website using the WordPress REST API and then returned those results based on the search parameter. However…

Step 3: Amending the results displayed

If we test the new search widget we can see the widget works as expected, however, it will also return results from the current website in the main page content.

search-template-results
Search widget displaying results for ‘test’ and the page redirecting to the search results template

This is because of the WordPress templating system. WordPress uses the query string to determine which template or set of templates to use. Because, when the search form is submitted, it adds the parameter ‘s’ to the URL, it is telling WordPress to perform a search and redirect to the search results page. Which, in the TwentySixteen theme is search.php.

For more information refer to the template hierarchy documentation.

That is great if we wanted to perform the search on a local website and an external website using the API at the same time because the results for both are displayed. However, in this instance we don’t want to do that. We only want the results returned using the API to display in the widget. The main page should remain unaffected.

There are a number of ways to do this. For this example, we will look at how to amend the get_search_form(); function to no longer use ‘s’ as its search parameter. Instead it will be changed to ‘a’ to bypass the issue.

The first step is to actually modify the form by creating a new function.


function api_search_form( $form ) {

 $form = '<form role="search" method="get" class="search-form" action="' . home_url( '/' ) . '">
         <label>
             <span class="screen-reader-text">' . _x( 'Search for:', 'label' ) . '</span>
             <input type="search" class="search-field" placeholder="' . esc_attr_x( 'Search …', 'placeholder' ) .'" value="' . get_search_query() . '" name="a" title="' . esc_attr_x( 'Search for:', 'label' ) .'" />
         </label>
         <button type="submit" class="search-submit"><span class="screen-reader-text">Search</span></button>
         </form>';

 return $form;
}

 add_filter( 'get_search_form', 'api_search_form', 100 );

The function itself is pretty simple, it’s basically a new form to use instead of the default form. If we look at the reference TwentySixteen theme and inspect the existing search form, we note that the only difference in the code is the name of the search input. Originally it was ‘name=s’ and now it is ‘name=a’. This removes the ‘s’ parameter from the URL.

Note: With this approach, we modify the main search form to search using the API instead of searching the host website. Therefore, it is important to consider that if we use the main search form anywhere else on the website it will no longer redirect to search results from host website website. In order to have two different searches then the easiest solution would be to add the code for this new form directly into the widget instead of amending the get_search_form();

The last step is to add the filter. This tells WordPress to replace the existing form with this one instead. We can the paste the entire code block into the plugin file after the widget class. This could be included the theme’s functions.php instead, but by including it in the plugin, it means that the change to the form is only made if the plugin is activated.

Now that we have changed the name of the input to ‘a’, we have to change what the actual search parameter will be as well. The parameter will now appear in the url as: /?a=test, instead of /?s=test and therefore removing the part of the query string that tells WordPress that this should redirect to the search results template.

So that you can search using the new parameter, update the code written previously for remote call to use the ‘a’ parameter instead of ‘s’.

It should now look like this:


if ( isset ( $_GET['a'] ) && !empty( $_GET['a'] ) ) {
    $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb?filter[s]='.$_GET['a'] );
} else {
    $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb/' );
}

Notice that although the search parameter in $_GET has changed, this does not change the filter in the actual query. This is because the standard WP Query for search is still ‘s’.

Now when we test the widget we should be able to search the Knowledge Base articles on your external website and return the results without being redirected to a search results page.

Search widget displaying results for 'test' without the page redirecting
Search widget displaying results for ‘test’ without the page redirecting

The full code for the widget looks like this:


/**
 * Plugin Name: KB - REST API Widget
 * Author: Kirsty Burgoine
*/

class REST_API_Widget extends WP_Widget {

 /**
 * Sets up the widgets name etc
 */
 public function __construct() {
     $widget_ops = array( 
     'classname' => 'rest-api-widget',
     'description' => 'A REST API widget that pulls posts from a different website');

     parent::__construct( 'rest_api_widget', 'REST API Widget', $widget_ops );
 }



 /**
 * Outputs the content of the widget
 *
 * @param array $args
 * @param array $instance
 */
 public function widget( $args, $instance ) {
     // outputs the content of the widget
     if ( isset ( $_GET['a'] ) && !empty( $_GET['a'] ) ) {
         $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb?filter[s]='.$_GET['a'] );
     } else {
         $response = wp_remote_get( 'http://website-with-api.com/wp-json/wp/v2/ht_kb/' );
     }

     if( is_wp_error( $response ) ) {
         return;
     }

     $posts = json_decode( wp_remote_retrieve_body( $response ) );

     if( empty( $posts ) ) {
         return;
     }

     echo $args['before_widget'];
     if( !empty( $instance['title'] ) ) {
         echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ) . $args['after_title'];
     }
 
     // Main Widget Content Here
     get_search_form();

     if( !empty( $posts ) ) { 
         echo '<ul>';
         foreach( $posts as $post ) {
             echo '<li><a href="' . $post->link. '">' . $post->title->rendered . '</a></li>';
         }
         echo '</ul>'; 
     }
     echo $args['after_widget'];
 }

 /**
 * Outputs the options form on admin
 *
 * @param array $instance The widget options
 */
 public function form( $instance ) {
     // outputs the options form on admin

     $title = ( !empty( $instance['title'] ) ) ? $instance['title'] : '';
     ?>
         <label for="<?php echo $this->get_field_name( 'title' ); ?>">Title: </label>
         <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" 
      name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
     <?php
     }
}

function api_search_form( $form ) {

 $form = '<form role="search" method="get" class="search-form" action="' . home_url( '/' ) . '">
         <label>
             <span class="screen-reader-text">' . _x( 'Search for:', 'label' ) . '</span>
             <input type="search" class="search-field" placeholder="' . esc_attr_x( 'Search …', 'placeholder' ) .'" value="' . get_search_query() . '" name="a" title="' . esc_attr_x( 'Search                 for:', 'label' ) .'" />
         </label>
         <button type="submit" class="search-submit"><span class="screen-reader-text">Search</span></button>
         </form>';

 return $form;
}
add_filter( 'get_search_form', 'api_search_form', 100 );


add_action( 'widgets_init', function(){
 register_widget( 'REST_API_Widget' );
});

This is a very simple implementation of a custom search widget using the REST API. As illustrated earlier, we could add additional filters to the query to limit the number of search results or the order they are displayed in. You could choose to display additional information in the widget such as the search term and the number of results, or, if you know jQuery / Javascript you could take this further and add a nice AJAX effect so that results are returned without the need to refresh the page.

Further Reading:

Leave A Comment?