AJAX live search in WordPress
Adding AJAX to a search form makes the experience far more dynamic and user-friendly. This article walks through how to build one from scratch.
Problem: We need to display search results dynamically in WordPress without a full page reload.
Solution: The approach is straightforward — follow the three steps below.
Step 1. Add the search form to the desired page:
<form id="search-form">
<div class="form-group">
<input type="search" name="q" id="search-query"
placeholder="<?php esc_attr_e( 'Search...', 'domain' ); ?>"
autocomplete="off" maxlength="64" tabindex="1">
<span class="input-btn">
<button class="btn btn-custom" type="submit" tabindex="-1"></button>
</span>
</div>
</form>
<ul id="data" class="data" tabindex="0"></ul>
Step 2. Create the front-end handler. For example, create a js folder and add a script.js file:
( function( $ ) {
$( document ).ready( function() {
var minLength = 3;
$( '#search-query' ).on( 'keyup', function() {
var value = $( this ).val();
if ( value.length >= minLength ) {
$.ajax( {
url: ajax.url,
type: 'post',
data: {
action: 'fetch_data',
keyword: value,
nonce: ajax.nonce,
},
success: function( data ) {
$( '#data' ).show().html( data );
},
} );
} else {
$( '#data' ).empty();
}
} );
} );
} )( jQuery );
Step 3. Set up the back-end handler in functions.php:
add_action( 'wp_enqueue_scripts', 'enqueue_live_search_scripts' );
function enqueue_live_search_scripts() {
wp_enqueue_script(
'live-search',
get_template_directory_uri() . '/js/script.js',
[ 'jquery' ],
null,
true
);
wp_localize_script( 'live-search', 'ajax', [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'search-form-nonce' ),
] );
}
add_action( 'wp_ajax_fetch_data', 'fetch_search_data' );
add_action( 'wp_ajax_nopriv_fetch_data', 'fetch_search_data' );
function fetch_search_data() {
check_ajax_referer( 'search-form-nonce', 'nonce' );
$keyword = sanitize_text_field( $_POST['keyword'] ?? '' );
if ( strlen( $keyword ) < 3 ) {
wp_die();
}
$query = new WP_Query( [
'post_type' => 'post',
'post_status' => 'publish',
'fields' => 'ids',
'no_found_rows' => true,
'posts_per_page' => 10,
's' => $keyword,
] );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
?>
<li class="result"><a href="<?php echo esc_url( get_permalink() ); ?>"><?php the_title(); ?></a></li>
<?php
}
wp_reset_postdata();
} else {
echo '<li class="no-results">' . esc_html__( 'No results found.', 'domain' ) . '</li>';
}
wp_die();
}
NOTE: Set a minimum query length (3 characters here) to avoid triggering AJAX on every keystroke. Add a debounce delay on the keyup event to further reduce server load — wait for the user to pause typing before firing the request. On the back end, always sanitize the keyword with sanitize_text_field() before passing it to WP_Query.
Sources: