{"id":126988,"date":"2022-01-13T11:00:00","date_gmt":"2022-01-13T11:00:00","guid":{"rendered":"http:\/\/premium.wpmudev.org\/blog\/?p=126988"},"modified":"2022-02-23T00:09:35","modified_gmt":"2022-02-23T00:09:35","slug":"build-your-own-custom-wordpress-search","status":"publish","type":"post","link":"https:\/\/wpmudev.com\/blog\/build-your-own-custom-wordpress-search\/","title":{"rendered":"Build Your Own Custom WordPress Search"},"content":{"rendered":"<p>The WordPress search function is much maligned and there are numerous plugins available to add enhancements but they don&#8217;t always provide what you want, especially if you are trying to build a secondary search engine that has specific requirements.<\/p>\n<p>In this article, we&#8217;ll look at how easy it is to build your own custom WordPress search and discover some surprising WordPress search secrets on the way.<\/p>\n<ul>\n<li><a href=\"#extending\">Extending WordPress Search Through An Extended Search Form<\/a>\n<ul>\n<li><a href=\"#phrase\">Search By Phrase<\/a><\/li>\n<li><a href=\"#match\">Finding An Exact Match<\/a><\/li>\n<li><a href=\"#form\">Customizing The Search Form<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#form-not-enough\">When A Custom Form Is Not Enough<\/a><\/li>\n<li><a href=\"#perfect\">Perfect When You Want A Second Search<\/a><\/li>\n<\/ul>\n<figure id=\"attachment_127095\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-127095\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/03\/custom_search_featurd.jpg\" alt=\"Photo of AMC Javelin customized blue hardtop with supercharged AMC V8.\" width=\"700\" height=\"300\" \/><figcaption class=\"wp-caption-text\">Pimp your WordPress search!<\/figcaption><\/figure>\n<p>Despite the numerous plugins that will enhance the WordPress search from ordering by relevancy to adding facet searching to including custom fields in the search, there are occasions when any combination of plugins won&#8217;t do exactly what you want and you are left with no option but to get your hands dirty and write your own.<\/p>\n<p>In this article, I will walk you through a custom search case study. You&#8217;ll find that it&#8217;s not actually that difficult to build-your-own search page and you&#8217;ll discover some hidden, perhaps, features of the built-in search function.<\/p>\n<p>But before I do that, let&#8217;s take a look at what you can achieve simply by putting your own search form together.<\/p>\n<h2 id=\"extending\">Extending WordPress Search Through An Extended Search Form<\/h2>\n<p>Behind a WordPress search sits the <a title=\"Read more at the WordPress Codex\" href=\"https:\/\/developer.wordpress.org\/reference\/classes\/wp_query\/\" rel=\"noopener\" target=\"_blank\"><em>WP_Query<\/em><\/a> class. If you&#8217;ve done any previous work with your own custom loops then you&#8217;ll likely be familiar with <em>WP_Query<\/em>.<\/p>\n<p><em>WP_Query<\/em> has a stack of possible parameters, many of which can simply be specified in a search form (or directly on an URL) that will change the search behavior.<\/p>\n<p>For example, to turn a normal search into a search on a custom post type called product:<\/p>\n<pre>http:\/\/www.yoursite.com\/?s=football&amp;post_type=product<\/pre>\n<p>This will only return results for <em>product<\/em> custom posts where the title or content contains the word <em>football<\/em>.<\/p>\n<p>If you look through the <em>WP_Query<\/em> parameter list (it&#8217;s pretty extensive) you&#8217;ll find a whole host of string and integer-based parameters that you can simply code on a URL to change the search behavior from including and excluding categories, to adding a taxonomy search to restricting the search to certain authors.<\/p>\n<p>[Remember, it is only a search if the <em>s<\/em> parameter is included in the URL.]<\/p>\n<p>There&#8217;s also a couple of extra parameters that you won&#8217;t find listed that can dramatically change the behavior and yet, amazingly, are not documented in the Codex.<\/p>\n<h3 id=\"phrase\">Search By Phrase<\/h3>\n<p>By default, WordPress searches by keyword. What this means is that when passed the search query<em> football boots<\/em>, WordPress builds the following for the WHERE clause:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n((wp_posts.post_title LIKE &#039;%football%&#039;) OR\r\n(wp_posts.post_content LIKE &#039;%football%&#039;))\r\nAND\r\n((wp_posts.post_title LIKE &#039;%boots%&#039;) OR\r\n(wp_posts.post_content LIKE &#039;%boots%&#039;))\r\n\r\n<\/pre>\n<p>As you can see, this is not a phrase search but a search looking for <em>football<\/em> in the title or content and <em>boots<\/em> in the title or content. So, a post that has <em>boot<\/em> in the title and <em>football<\/em> in the content will match which is probably not what the searcher is really looking for.<\/p>\n<p>You can, however, make WordPress do a phrase search, simply by adding <em>sentence=1<\/em> to the URL which changes the WHERE clause to:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n((wp_posts.post_title LIKE &#039;%football boots%&#039;) OR\r\n(wp_posts.post_content LIKE &#039;%football boots%&#039;))\r\n\r\n<\/pre>\n<p>Now the search is for the phrase and the title or content has to actually contain <em>football boots<\/em>\u00a0to be found. Try this on your own site by searching and then adding <em>&amp;sentence=1<\/em> to the URL and see what difference this makes to the results.<\/p>\n<h3 id=\"match\">Finding An Exact Match<\/h3>\n<p>Related to <em>sentence<\/em> but even more specific is <em>exact<\/em>. Adding <em>exact=1<\/em> to the URL changes the WHERE clause to:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n((wp_posts.post_title LIKE &#039;football boots&#039;) OR\r\n(wp_posts.post_content LIKE &#039;football boots&#039;))\r\n\r\n<\/pre>\n<p>Rather than playing spot the difference, I&#8217;ll tell you that the only difference between the <em>sentence<\/em>\u00a0WHERE clause and the <em>exact<\/em>\u00a0WHERE clause is the removal of the <em>%<\/em> around the phrase in the LIKE statements. That removal makes a big difference, though, because now the title or content has to match the search query, not just contain it.<\/p>\n<p>So, if no product has the title <em>football boots<\/em> then there will be no results. Use <em>exact<\/em> with care.<\/p>\n<h3 id=\"form\">Customizing The Search Form<\/h3>\n<p>The default WordPress search form is very simple:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n&lt;form role=&quot;search&quot; method=&quot;get&quot; id=&quot;searchform&quot; class=&quot;searchform&quot; action=&quot;http:\/\/www.test.dev\/&quot;&gt;\r\n&lt;div&gt;\r\n&lt;label class=&quot;screen-reader-text&quot; for=&quot;s&quot;&gt;Search for:&lt;\/label&gt;\r\n&lt;input type=&quot;text&quot; value=&quot;&quot; name=&quot;s&quot; id=&quot;s&quot; \/&gt;\r\n&lt;input type=&quot;submit&quot; id=&quot;searchsubmit&quot; value=&quot;Search&quot; \/&gt;\r\n&lt;\/div&gt;\r\n&lt;\/form&gt;\r\n\r\n<\/pre>\n<p>If we want to change the search behavior, then, all we need to do is add our own fields to the form.<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n&lt;form role=&quot;search&quot; method=&quot;get&quot; id=&quot;searchform&quot; action=&quot;http:\/\/www.test.dev\/&quot;&gt;\r\n&lt;div&gt;\r\n&lt;label for=&quot;s&quot;&gt;Search for:&lt;\/label&gt;\r\n&lt;input type=&quot;text&quot; value=&quot;&quot; name=&quot;s&quot; id=&quot;s&quot; \/&gt;\r\n&lt;input type=&quot;hidden&quot; value=&quot;1&quot; name=&quot;sentence&quot; \/&gt;\r\n&lt;input type=&quot;hidden&quot; value=&quot;product&quot; name=&quot;post_type&quot; \/&gt;\r\n&lt;input type=&quot;submit&quot; id=&quot;searchsubmit&quot; value=&quot;Search&quot; \/&gt;\r\n&lt;\/div&gt;\r\n&lt;\/form&gt;\r\n\r\n<\/pre>\n<p>This search form, when submitted, will generate the following URL:<\/p>\n<pre>http:\/\/www.test.dev\/?s={query}&amp;sentence=1&amp;post_type=product<\/pre>\n<p>It will still invoke the default search results page but those results will be for product custom post types that contain the search phrase in either their title or their content.<\/p>\n<p>The easiest way to build your own search form, assuming that you want to leave the default form as is, is to\u00a0create a new page template with the search form coded to your requirements and assign this to a specific page.<\/p>\n<p>If, however, you want to update the default search form &#8211; to trigger phrase searching, for example &#8211; you have two options assuming that your template hasn&#8217;t hard-coded the search form into the template:<\/p>\n<ol>\n<li>The first is put your custom search form into a template file called <em>searchform.php<\/em>. Whenever the <em>get_search_form()<\/em> function is called it will look for, and use, this template first.<\/li>\n<li>The second is to use the <em>get_search_form<\/em> filter to replace to force WordPress to use your custom search form.<\/li>\n<\/ol>\n<p>Both these techniques are <a title=\"Read more in the WordPress codex\" href=\"https:\/\/codex.wordpress.org\/Function_Reference\/get_search_form\" rel=\"noopener\" target=\"_blank\">described in detail<\/a> in the WordPress Codex.<\/p>\n<h2 id=\"form-not-enough\">When A Custom Form Is Not Enough<\/h2>\n<p>Although you can do a great deal with a custom form, there are scenarios when you need to build your WP_Query and handle the results yourself, in particular when you are adding a second search function.<\/p>\n<h3 id=\"case\">Case Study Background<\/h3>\n<p>This case study concerns an e-commerce site for an organization that sells a mix of physical and digital products. Most of the digital products are back-issues of two magazines, along with booklets both in paper and digital formats.<\/p>\n<p>The organization wanted to provide a &#8220;library&#8221; search that would allow visitors to search just the magazines and the booklets for certain phrases (the magazines have a full list of contents in the content). Whilst the site already had a product search, the results were less satisfactory as:<\/p>\n<ul>\n<li>often had too many irrelevant results<\/li>\n<li>included all products<\/li>\n<li>gave no indication of where the search term was matched, search results were just the product image<\/li>\n<\/ul>\n<p>In order to leave the current searches in tact, a new search function was built that:<\/p>\n<ul>\n<li>enforced searching by phrase rather than keyword<\/li>\n<li>restricted the search to just the magazine and booklet categories<\/li>\n<li>displayed and highlighted the text that contains the search phrase match<\/li>\n<\/ul>\n<p>The first two requirements could actually be achieved with the following custom search form:<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n\r\n&lt;form role=&quot;search&quot; method=&quot;get&quot; id=&quot;searchform&quot; action=&quot;http:\/\/www.test.dev\/&quot;&gt;\r\n&lt;div&gt;\r\n&lt;label for=&quot;s&quot;&gt;Search for:&lt;\/label&gt;\r\n&lt;input type=&quot;text&quot; value=&quot;&quot; name=&quot;s&quot; id=&quot;s&quot; \/&gt;\r\n&lt;input type=&quot;hidden&quot; value=&quot;1&quot; name=&quot;sentence&quot; \/&gt;\r\n&lt;input type=&quot;hidden&quot; value=&quot;product&quot; name=&quot;post_type&quot; \/&gt;\r\n&lt;input type=&quot;hidden&quot; value=&quot;product_cat&quot; name=&quot;magazines,books&quot; \/&gt;\r\n&lt;input type=&quot;submit&quot; id=&quot;searchsubmit&quot; value=&quot;Search&quot; \/&gt;\r\n&lt;\/div&gt;\r\n&lt;\/form&gt;\r\n\r\n<\/pre>\n<p>However, this wouldn&#8217;t help with the search listing layout and the highlighting of the search phrase, so a new page template was built and assigned to a new dedicated page.<\/p>\n<p>Here&#8217;s the main logic in the template:<\/p>\n<!-- Missing Gist ID -->\n<p>As you can see the form is pretty much the standard WordPress form as all the manipulation of the <em>WP_Query<\/em> call is done via the coding where:<\/p>\n<ul>\n<li><em>post_type<\/em> is set to product<\/li>\n<li><em>sentence<\/em> is set to 1 to trigger phrase searching<\/li>\n<li>a taxonomy parameter is added to restrict the search to the three product categories<\/li>\n<li>ordering is set to date and menu_order descending<\/li>\n<li>all posts are to be returned<\/li>\n<\/ul>\n<p>It&#8217;s important to note that there is no paging in this solution. For a custom, specific search it won&#8217;t always be necessary for pagination, especially if phrase searching is triggered.<\/p>\n<p>Once the <em>WP_Query<\/em> is executed it is a matter of outputting any search results. You&#8217;ll notice that if there are no search results then a few related products are shown instead to try and keep the visitor on the page.<\/p>\n<p>Outputting the results requires two additional functions, one to pull out the text that contains the matching search phrase and another to highlight the phrase itself.<\/p>\n<!-- Missing Gist ID -->\n<p>Highlighting the phrase is achieved just by using a simple regex expression.<\/p>\n<p>Pulling out the text containing the search phrase was not so easy. It was greatly aided by the fact that each product&#8217;s content used a standard approach of listing the contents in an unordered list with a header and a description but trying to find a regex to pull out the list item proved beyond me, so I resorted to string manipulation.<\/p>\n<p>The result, though, is pretty good:<\/p>\n<figure id=\"attachment_127093\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-127093\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/03\/custom_search.jpg\" alt=\"Screenshot of a search results page with phrase highlighting\" width=\"700\" height=\"829\" \/><figcaption class=\"wp-caption-text\">Searching by phrase instead of keyword dramatically improves results<\/figcaption><\/figure>\n<h2 id=\"perfect\">Perfect When You Want A Second Search<\/h2>\n<p>Custom search forms and functions are perfect for when you want to add a second search function to run alongside the main search function and have really specific requirements.<\/p>\n<p>In a number of scenarios, you might be able to achieve what you want by simply having a custom search form that passes the appropriate parameters to built-in in search function. You can easily test this out before embarking on building your own custom search function by adding the appropriate query variables to the standard search URL and seeing if you get the results you require.<\/p>\n<p>That said, coding your own search function is not that difficult and gives you access to an even greater range of parameters to control the search behavior.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to build your own custom WordPress search and discover some surprising WordPress search secrets on the way. <\/p>\n","protected":false},"author":262394,"featured_media":191611,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"blog_reading_time":"","wds_primary_category":0,"wds_primary_tutorials_categories":0,"footnotes":""},"categories":[263],"tags":[186],"tutorials_categories":[],"class_list":["post-126988","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-search"],"_links":{"self":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/126988","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/users\/262394"}],"replies":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/comments?post=126988"}],"version-history":[{"count":16,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/126988\/revisions"}],"predecessor-version":[{"id":206105,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/126988\/revisions\/206105"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media\/191611"}],"wp:attachment":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media?parent=126988"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/categories?post=126988"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tags?post=126988"},{"taxonomy":"tutorials_categories","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tutorials_categories?post=126988"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}