This tutorial is for you who want to implement an autocomplete search in WordPress, where we are in full control of the returned matches.

We will apply an autocomplete to the standard WordPress search, but the code highly flexible so you can customize the query to your needs. Whether it is a more specific query by a custom post type, post meta, categories, or queries on completely different type of content such as users or something manual from the database. You can also easily apply the autocomplete on a custom search input instead of the standard WordPress search.

What we will make

Autocomplete is an UI box that appears below the search field showing the matches while you type. Upon clicking on a match the browser will send you to that post’s permalink. This provides the end-user with a faster way to navigate your content since they won’t need to take the extra detour to your search results page. We will use AJAX to continously update the matches while the user types.

The autocomplete is generated with help from jQuery UI Autocomplete, a script that is included in WordPress as default.

Setting up the code

You will need to add the code in either the theme’s functions.php or an active plugin PHP file. This tutorial is based on adding it in a theme. Adjust the paths to fit your project.

The first step is creating a Javascript file that will contain our code to trigger autocomplete. As for this tutorial I’ll create an empty autocomplete.js file in my theme’s assets/js/ folder. Where you place your file is up to you, just remember to adjust the paths below. We’ll come back to this file after we’ve enqueued this file properly in WordPress.

Enqueuing scripts and styles

We need to enqueue our autocomplete Javascript file and we also need to make sure to enqueue jQuery and jQuery UI Autocomplete. In addition, WordPress does not include any styling for jQuery UI libraries so we also need to enqueue a stylesheet for jQuery UI from Google CDN. This is optional of course. You can add the CSS some other way or perhaps you prefer to style it yourself.

We’ll enqueue all scripts and styles in a function hooked to wp_enqueue_scripts:

functions.php
add_action('wp_enqueue_scripts', function() {
	wp_enqueue_script('autocomplete-search', get_stylesheet_directory_uri() . '/assets/js/autocomplete.js', 
		['jquery', 'jquery-ui-autocomplete'], null, true);
});

The wp_enqueue_script() function call above will add our newly created Javascript file with the correct dependencies (the array as third parameter). This ensures that WordPress adds the jQuery and jQuery UI Autocomplete scripts in our WordPress instance.

Next we need to provide some variables we can access from our autocomplete script. We need to know the AJAX URL and we also want to add a nonce for security. We can do this by using wp_localize_script():

functions.php
add_action('wp_enqueue_scripts', function() {
	wp_enqueue_script('autocomplete-search', get_stylesheet_directory_uri() . '/assets/js/autocomplete.js', 
		['jquery', 'jquery-ui-autocomplete'], null, true);
	wp_localize_script('autocomplete-search', 'AutocompleteSearch', [
		'ajax_url' => admin_url('admin-ajax.php'),
		'ajax_nonce' => wp_create_nonce('autocompleteSearchNonce')
	]);
});

The wp_localize_script() function will create a global Javascript variable AutocompleteSearch with two properties; ajax_url and ajax_nonce. With this we can in our autocomplete Javascript file fetch for example the value of WordPress AJAX URL with AutocompleteSearch.ajax_url.

Finally we need to add some styling. As mentioned WordPress does not come with any jQuery UI styling included so we need to add some ourselves. I’ve opted for adding a stylesheet via Google CDN. But we need to know the jQuery UI version number in order to fetch its stylesheet. We could hardcode a version number, but I’m not a fan of hardcoding anything. Below you’ll find a nice trick to take advance of the jQuery UI version information stored in WordPress.

functions.php
add_action('wp_enqueue_scripts', function() {
	...

	$wp_scripts = wp_scripts();
	wp_enqueue_style('jquery-ui-css',
		'//ajax.googleapis.com/ajax/libs/jqueryui/' . $wp_scripts->registered['jquery-ui-autocomplete']->ver . '/themes/smoothness/jquery-ui.css',
		false, null, false
	);
});

We use wp_enqueue_style() to register and add a new stylesheet with the provided CDN path as second argument. In order to fetch a valid jQuery UI version number we use the information provided from the function wp_scripts().

And that’s all we need for enqueuing scripts!

Writing the autocomplete Javascript

Let’s return to our autocomplete.js file. We know that when this script is loaded, jQuery and jQuery UI Autocomplete is already loaded, and we also have access to a global variable with necessary information. Let’s start by setting up a jQuery document ready-function to ensure our code is run after DOM is ready.

autocomplete.js
jQuery(function($) {
	// All code in here
});

If we refer to jQuery UI Autocomplete’s doumentation we learn that we need to make a jQuery selector targeting an input field and run the function autocomplete() on it.

This is a point where you can tweak the code to fit your needs. This tutorial will apply the autocomplete onto the standard WordPress search field (which is usually added in theme by including the search template or as a widget). The search input’s class names may vary from theme to theme. But you might want to target a custom input field or search within a special template. All you need to do is to change the jQuery selector to target the input you want.

In my theme the standard WordPress search field input has a class of .search-field. I’m also adding the parent form class name to further narrow it down so we don’t risk autocomplete being applied to something else using the same class.

According to the Autocomplete documentation we can perform an AJAX call in the property source (which must return an array of items to display in the autocomplete). We’ll use jQuery’s Ajax-function to do just that:

autocomplete.js
jQuery(function($) {
	$('.search-form .search-field').autocomplete({
		source: function(request, response) {
			$.ajax({
				dataType: 'json',
				url: AutocompleteSearch.ajax_url,
				data: {
					term: request.term,
					action: 'autocompleteSearch',
					security: AutocompleteSearch.ajax_nonce,
				},
				success: function(data) {
					response(data);
				}
			});
		},
	});
});

At line #2 is where we tell jQuery UI Autocomplete which input field we want to apply autocomplete on. Change this selector to fit your needs.

In the simplest form Autocomplete expects an array of item objects to the source property. Here we create a function with two parameters; request contains information about our inputted value (request.term), and response which is a callback function we need to call and provide the data. That’s what we do in the AJAX’s success function at line #13.

The AJAX call itself is pretty standard. We define the datatype as JSON – this is important otherwise jQuery UI Autocomplete won’t be able to parse the results. As url we access ajax_url in the global variable we localized to our script earlier; AutocompleteSearch. And as data we pass an object of information. The action property is mandatory and necessary for the next step – which is identifying this specific AJAX request in PHP. We also pass on the inputted term in the input and the nonce for security purposes.

That’s it for the sources property. There’s one other thing we need to add in our autocomplete script. As default in jQuery UI Autocomplete choosing an item simply autofills the input with the chosen element. We want to redirect the user to the post’s URL when choosing an item from the list. So we add a function to the select property:

autocomplete.js
				...
				success: function(data) {
					response(data);
				}
			});
		},		
		select: function(event, ui) {
			window.location.href = ui.item.link;
		},
	});
});

In the array of items we will return from our AJAX call (we will write this next) each item is an object with properties. We will add a custom link property to each item (ui.item) that will contain the permalinks to each post. We pass this URL to window.location.href which will trigger a browser redirect.

And that’s all for the Javascript part! All that remains is writing the PHP part that listens to AJAX requests with the action autocompleteSearch.

Returning results in PHP to AJAX requests

In order to write functions that responds to specific AJAX requests we use the hooks wp_ajax_{action} (logged in visitors) and wp_ajax_nopriv_{action} (non-logged in visitors). We defined the action as autocompleteSearch in our AJAX request above. Refer to my post that explains how AJAX works in WordPress if you are unfamiliar with this.

Let’s set this up in functions.php (or wherever in PHP you add your code):

functions.php
add_action('wp_ajax_nopriv_autocompleteSearch', 'awp_autocomplete_search');
add_action('wp_ajax_autocompleteSearch', 'awp_autocomplete_search');
function awp_autocomplete_search() {
	// echo result
	die();
}

With the above code we hook the same function to the two AJAX hooks. In all functions that are hooked to the wp_ajax AJAX hooks we must make sure to always die() or exit at the end so we don’t echo out undesired output. I’m using WordPress’ wp_die() function.

We can fetch the passed data from Javascript using $_REQUEST (works for both GET and POST requests). In our Javascript code we passed the inputted term in the key ‘term‘. This means we can fetch the value of it in $_REQUEST['term']. We can then perform a query based on this. Remember that we only want to return results that match this search term.

This is another point where you can fully change and tweak the code to fit your needs. This tutorial will perform a standard query on posts and pages but you can tweak the query or perform a completely different query on different data. Perhaps you prefer to or need to perform a manual SQL query instead (it’s certainly more memory efficient). The crucial part here is the array that we echo at the end, which is parsed by our Javascript’s autocomplete code.

functions.php
add_action('wp_ajax_nopriv_autocompleteSearch', 'awp_autocomplete_search');
add_action('wp_ajax_autocompleteSearch', 'awp_autocomplete_search');
function awp_autocomplete_search() {
	check_ajax_referer('autocompleteSearchNonce', 'security');

	$search_term = $_REQUEST['term'];
	if (!isset($_REQUEST['term'])) {
		echo json_encode([]);
	}

	$suggestions = [];
	$query = new WP_Query([
		's' => $search_term,
		'posts_per_page' => -1,
	]);
	if ($query->have_posts()) {
		while ($query->have_posts()) {
			$query->the_post();
			$suggestions[] = [
				'id' => get_the_ID(),
				'label' => get_the_title(),
				'link' => get_the_permalink()
			];
		}
		wp_reset_postdata();
	}
	echo json_encode($suggestions);
	wp_die();
}

First we check if the nonce was valid as to establish some security on our AJAX calls. We can do this by calling the function check_ajax_referer(). I’ve also added code to ensure we don’t perform a heavy request if the returned search term was empty. We should then return an empty json-encoded array.

The example code above performs a WP_Query with a search on the inputted term. We need to set posts_per_page as -1 to ensure we return all matches. Check out the documentation for WP_Query if you want to further tweak the query.

We then loop through the results (line #17) and populate an array with matches (line #19-23). jQuery UI Autocomplete needs as minimum the poperty label for signifying what should be displayed in the autocomplete box. We also pass on permalinks in the key ‘link‘ which is what we use in our Javascript code to redirect the user.

Finally at line #27 we echo the generated array as JSON by using json_encode().

With this PHP part in place, our autocomplete should work! Refresh your site and try it out!

Optional improvements

One common problem is that the number of matches are too many and the autocomplete box gets too large. There are two solutions to this.

One solution is to add the property minLength to the autocomplete() function in Javascript. This property will only trigger the autocomplete box after a certain number of characters has been input. As an example we can require a minimum of 3 characters before triggering autocomplete:

autocomplete.js
		...
		select: function(event, ui) {
			window.location.href = ui.item.link;
		},
		minLength: 3,
	});
});

jQuery UI Autocomplete proposes another solution here. It shows an example of adding a bit of CSS to add a fixed height and an internal scrollbar in the autocomplete box.

Conclusion and complete code

We’ve successfully added an autocomplete to WordPress’ search functionality where we have full control of the matches to return. It’s gives the visitors a faster way to navigate your content as clicking a match will redirect directly to the post, rather than taking a detour in a search results page first. Implementing an autocomplete might not always make sense in all WordPress sites, but can be great for searching specific content or within special templates!

Here is the final code in its entirety:

functions.php
add_action('wp_enqueue_scripts', function() {
	wp_enqueue_script('autocomplete-search', get_stylesheet_directory_uri() . '/assets/js/autocomplete.js', 
		['jquery', 'jquery-ui-autocomplete'], null, true);
	wp_localize_script('autocomplete-search', 'AutocompleteSearch', [
		'ajax_url' => admin_url('admin-ajax.php'),
		'ajax_nonce' => wp_create_nonce('autocompleteSearchNonce')
	]);

	$wp_scripts = wp_scripts();
	wp_enqueue_style('jquery-ui-css',
        '//ajax.googleapis.com/ajax/libs/jqueryui/' . $wp_scripts->registered['jquery-ui-autocomplete']->ver . '/themes/smoothness/jquery-ui.css',
        false, null, false
   	);
});

add_action('wp_ajax_nopriv_autocompleteSearch', 'awp_autocomplete_search');
add_action('wp_ajax_autocompleteSearch', 'awp_autocomplete_search');
function awp_autocomplete_search() {
	check_ajax_referer('autocompleteSearchNonce', 'security');

	$search_term = $_REQUEST['term'];
	if (!isset($_REQUEST['term'])) {
		echo json_encode([]);
	}

	$suggestions = [];
	$query = new WP_Query([
		's' => $search_term,
		'posts_per_page' => -1,
	]);
	if ($query->have_posts()) {
		while ($query->have_posts()) {
			$query->the_post();
			$suggestions[] = [
				'id' => get_the_ID(),
				'label' => get_the_title(),
				'link' => get_the_permalink()
			];
		}
		wp_reset_postdata();
	}
	echo json_encode($suggestions);
	wp_die();
}
autocomplete.js
jQuery(function($) {
	$('.search-form .search-field').autocomplete({
		source: function(request, response) {
			$.ajax({
				dataType: 'json',
				url: AutocompleteSearch.ajax_url,
				data: {
					term: request.term,
					action: 'autocompleteSearch',
					security: AutocompleteSearch.ajax_nonce,
				},
				success: function(data) {
					response(data);
				}
			});
		},
		select: function(event, ui) {
			window.location.href = ui.item.link;
		},
	});
});