# A simple javascript based full text search function

**URL:** <https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119>\
**Category:** tips & tricks\
**Created:** [October 30, 2020, 9:15am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119 "2020-10-30T09:15:20Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![emk](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/emk/32/10223_2.png) [@emk](https://discourse.gohugo.io/u/emk)\
**Post date:** [October 30, 2020, 9:15am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/1 "2020-10-30T09:15:20Z")

</div>

The full text search presented here by [Tanja Becker](http://tanjabecker.de) (Javascript) and [myself](https://weitblick.org) (integration in Hugo) is based on a JSON array that contains all of the website’s content and a Javascript search function that processes the array. The JSON array is generated automatically by HUGO. Integrating the search function into an existing HUGO website is very easy with the following steps:

1. Create Template _index.json_
2. Configure _config.toml_
3. Insert Javascript _search.js_ into website
4. Create shortcode _search.html_ and adjust if necessary
5. Create Web page _search.md_

[Example…](https://weitblick.org/search/)

* * *

## JSON array

Create a template **index.json** in the directory `themes/<myTheme>/layouts/` :

_index.json:_

```
[ {{- $i := 0 -}}
{{- range where .Site.RegularPages "Section" "ne" "" -}}
   {{- if not .Params.noSearch -}}
      {{- if gt $i 0 }},{{ end -}}
      {"date":"{{ .Date.Unix }}", "url":"{{ .Permalink }}", "title":{{ .Title | jsonify }}, "summary":{{ with .Description}}{{ . | plainify | jsonify }}{{ else }}{{ .Summary | plainify | jsonify }}{{ end }}, "content":{{ .Content | plainify | jsonify }},"tags":[{{- $t := 0 }}{{- range .Param "tags" -}}{{ if gt $t 0 }},{{ end }}{{ . | jsonify }}{{ $t = add $t 1 }}{{ end -}}], "section": {{ .Section | jsonify -}} }
      {{- $i = add $i 1 -}}
   {{- end -}}
{{- end -}} ]

```

This creates a JSON array that automatically contains the content of all pages from all sections. Pages with `noSearch: true` in the _Front Matter_ are ignored. _Description_ overwrites _Summary_ .

Add the following setting in **config.toml** :

```
[outputs]
   home = ["HTML", "JSON"]

```

This ensures that a file index.json (based on the template index.json) is automatically created in the document root, which contains the entire content of the website.

## Search function

- Save the Javascript **search.js** in `static/js/search.js`  
_Download [search.js](https://weitblick.org/js/search.js)_
- Create a shortcode **search.html** in the directory `themes/<myTheme>/layouts/shortcodes/` with the following content
- and include it with `{{<search>}}` in your **search.md** search page.

_search.md:_

```
---
title: "Site Search"
summary: "Search website for keywords"
date: 2020-10-29
---

{{< search >}}

```

_search.html:_

```
<form id="custom-search" name="custom-search" method="post" action="" onsubmit="customSearchResults(); return false;">
    <p>
    <input id="custom-search-field" type="text" name="search" value="" title="Search String" placeholder="SearchString">
    <!-- <input type="submit" value="Suchen"> -->
    </p>
    <!-- <p><em>Search Section:</em><br>
        <input type="checkbox"name="section[]" value="site" checked="checked"> all<br>
        <input type="checkbox" name="section[]" value="post"> Blog<br>
        <input type="checkbox" name="section[]" value="other-section"> Other Section
    </p> -->
    <p>
        <input type="radio" name="option" value="AND" checked="checked"> UND-Suche<br>
        <input type="radio" name="option" value="OR"> ODER-Suche
    </p>
</form>

<div id="custom-search-results"></div>

<script>
// CUSTOM AREA
let params = {
    json_src : '/index.json', // for multiple sources: comma separated list of JSONarrays
    minlength : 3,
    defaultsearch : 'AND',
    sort_date : 'DESC',
    autocomplete : 1, // 0: form needs a submit button
    section_search : 0, // 1: needs checkboxes with name="section[]"
    badwords : 'and,there,the,this,that,you', //ignore this words
    json_wait : '<p><em>loading...</em></p>',
    json_ready : '<p><em>Please enter a search term</em></p>',
    extern_icon : ' (external link)', // marker for external links (optional)
    err_badstring : '<p>To short!</p>',
    err_noresult : '<p>Unfortunately no search result. Please try again.</p>',
    err_norequest : '<p style="text-align: center; color:red;">The full text search is currently not available.</p>',
    err_filefailed : '<p style="text-align: center;color: red;">Search file could not be retrieved.</p>',
    res_one_item : '<p><em>[CNT] RESULT</em></p>',
    res_more_items : '<p><em>[CNT] RESULTS</em></p>',
    res_out_top : '<ul>',
    res_out_bottom : '</ul>',
    res_item_tpl : '<li><a href="[URL]">[TITLE]</a><br>[DATE]:[SUMMARY]<br><em>[SECTION][TAGS]</em></li>',
    add_searchlink : '<p><a href="https://duckduckgo.com/?q=site:yourdomain.com [QUERY]" target="_blank"><i>Not satisfied with the search results? External search via DuckDuckGo ...</i></a></p>'
};

// Translation of section name (optional)
let section_trans = {
    "post" : "Blog",
    // "other-section" : "Other Section"
};

let searchfield_weight = {
    "title" : 5,
    "tags" : 5,
    "summary" : 2,
    "content" : 1
};
// CUSTOM AREA END
</script>
<script type="text/javascript" src="/js/search.js"></script>

```

_Download source code [search.html](https://weitblick.org/search.html)_

## Notes

This example provides a simple search function that works in every HUGO website (the HTML code in _search.html_ may have to be adapted to the existing theme):  
[test here …](https://weitblick.org/search/)

- The search begins as soon as at least 3 characters have been typed ( `minlength` ).
- `badwords` contains a list of words to be ignored.
- If you prefer to trigger the search via the submit button, you have to set `autocomplete` to “0”.
- You can also call the search page via [?query=searchterm](https://weitblick.org/search/?query=Suchbegriff)
- The search results are sorted according to the number of hits for the search term, with `searchfield_weight` as a weighting. “5” means that the hit in the corresponding field is weighted with a factor of 5.
- The search function searches through all _Sections_. If you want to show in the search results which section the result comes from, you can use `section_trans` to translate the internal section key.
- It is also possible to link to an external search engine (in the example DuckDuckGo), if the internal search does not provide a satisfactory result in individual cases (`add_searchlink`)

## More options

- The search function can search different sections separately. This must be set `section_search` to “1” and the corresponding checkboxes added to the search form (commented out in the example).
- It is possible to specify several JSON arrays as the source for the search. This is especially useful if you want a single search function to search multiple websites. To indicate in the results which hits are local and which are from an external website, each item in the array of the external source can contain the value `"extern":"1"`. This causes the string from `extern_icon` to be added to the search result after `[TITLE]`. Example: [bienenkiste.de/suchen/?query=Königin](https://bienenkiste.de/suchen/?query=K%C3%B6nigin)
- With very large JSON files, it may take a moment for the file to load. During this time is `json_wait` displayed. A _spinner/loader icon_ can be inserted here - if available in the theme. As soon as the file is loaded `json_ready` is displayed.

* * *

_(Translation of my German blog article [Volltextsuche für Hugo](https://weitblick.org/post/simple-static-site-search-hugo-jamstack/))_

---

<div class="post-metadata">

**Author:** ![GothSeiDank](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/gothseidank/32/13028_2.png) [@GothSeiDank](https://discourse.gohugo.io/u/GothSeiDank)\
**Post date:** [October 31, 2020, 10:33pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/2 "2020-10-31T22:33:54Z")

</div>

Funny, I have released a similar article lately, but with a different goal:

> **[Building a full text search for Hugo](https://tuxstash.de/tutorials/how-to-hugo-static-search/)**
>
> A full text search is not always a simple task for static-site-generators.

This is a full text search, however, the caveat is to use a database with a small rest-like script.  
The sql file for the database is generated when Hugo runs and the Javascript does the search.

The downside of your solution is of course that it is keyword only and that the download size grows as your blog grows and the search will become slower over time, especially with limited data rates in non western countries.

But, as we Germans say, you got to die one death.

---

<div class="post-metadata">

**Author:** ![emk](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/emk/32/10223_2.png) [@emk](https://discourse.gohugo.io/u/emk)\
**Post date:** [November 1, 2020, 3:29pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/3 "2020-11-01T15:29:22Z")

</div>

Interesting solution! Will be remembered for other problems. Thanks!  
You’re right: I had a different goal. I wanted to write a recipe for HUGO dummies that can be used without any programming or server admin knowledge.  
I was also worried at first that it might be a bit slow. But I think for 90% of HUGO websites it will probably be enough and run super fast. The example [https://bienenkiste.de/suchen/](https://bienenkiste.de/suchen/) searches almost 200 pages. The two JSON files together are ~650 KB in size. Many homepages are 10 times “heavier” today. And the javascript runs very very fast.  
The main problem with larger sites is that I have not provided pagination. But you can compensate this a little by increasing `minlength`.

---

<div class="post-metadata">

**Author:** ![aral](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/aral/32/10573_2.png) [@aral](https://discourse.gohugo.io/u/aral)\
**Post date:** [November 4, 2020, 4:08pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/4 "2020-11-04T16:08:38Z")

</div>

This sounds awesome, thanks for sharing it. I’m going to look into how we can integrate this into some starter sites we’re working on including in Site.js ([https://sitejs.org](https://sitejs.org)).

I’d also be interested in seeing what the performance characteristics would be if it was to use JSDB ([https://ar.al/2020/10/20/introducing-jsdb/](https://ar.al/2020/10/20/introducing-jsdb/)) instead of JSON.

---

<div class="post-metadata">

**Author:** ![lb13](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/lb13/32/10190_2.png) [@lb13](https://discourse.gohugo.io/u/lb13)\
**Post date:** [February 10, 2021, 7:55am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/5 "2021-02-10T07:55:37Z")

</div>

Just wanted to say thanks for this code! I was able to integrate it into my new site and it works great 🙂

[https://educationfunding.uk](https://educationfunding.uk)

---

<div class="post-metadata">

**Author:** ![csgeek](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/csgeek/32/12412_2.png) [@csgeek](https://discourse.gohugo.io/u/csgeek)\
**Post date:** [March 15, 2021, 10:50pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/6 "2021-03-15T22:50:06Z")

</div>

Same here this was a great article and really fast/easy to get setup. I’d much rather use this than have a gulp task that generates some JSON you have to remember to deploy as part of your release.

Thank you for the contribution.

---

<div class="post-metadata">

**Author:** ![flamenco108](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/flamenco108/32/14429_2.png) [@flamenco108](https://discourse.gohugo.io/u/flamenco108)\
**Post date:** [May 23, 2021, 12:39pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/7 "2021-05-23T12:39:50Z")

</div>

I’ve just applied your code. As a programming-dummy and hugo-novice thank you for this beatiful piece of work! It works flawlessly, immediately after I created all files required in instruction.  
Thanks 😃

---

<div class="post-metadata">

**Author:** ![qianglu](https://avatars.discourse-cdn.com/v4/letter/q/5daacb/32.png) [@qianglu](https://discourse.gohugo.io/u/qianglu)\
**Post date:** [November 16, 2021, 6:42pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/8 "2021-11-16T18:42:44Z")

</div>

This is so simple and easy to set up. Thank you for your work!

Just wander if you could highlight the keyword searching.

Qiang Lu

---

<div class="post-metadata">

**Author:** ![idarek](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/idarek/32/26229_2.png) [@idarek](https://discourse.gohugo.io/u/idarek)\
**Post date:** [November 17, 2021, 12:26pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/9 "2021-11-17T12:26:36Z")

</div>

Superb. Work like a charm and can integrate with existing search boxes on site. Really impressed!

---

<div class="post-metadata">

**Author:** ![emk](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/emk/32/10223_2.png) [@emk](https://discourse.gohugo.io/u/emk)\
**Post date:** [November 18, 2021, 10:38am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/10 "2021-11-18T10:38:07Z")

</div>

> [@qianglu](#):
>
> Just wander if you could highlight the keyword searching.

I don’t think this function is that important. Maybe someone else would like to extend the script? There are javascripts for keyword highlighting on the internet. What is missing is the looping through of the keyword to the displayed page.

---

<div class="post-metadata">

**Author:** ![indigorobin](https://avatars.discourse-cdn.com/v4/letter/i/a5b964/32.png) [@indigorobin](https://discourse.gohugo.io/u/indigorobin)\
**Post date:** [April 28, 2022, 4:28pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/11 "2022-04-28T16:28:24Z")

</div>

This works great for me locally. When I export the site, it is just showing “loading” under the search. A warning is displaying “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at file:///index.json. (Reason: CORS request not HTTP).”

I am still just getting started with Hugo. I think it has to do with the url it’s fetching it from, but I am not too sure where in the script it is calling the file up. Any guidance would be helpful.

---

<div class="post-metadata">

**Author:** ![emk](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/emk/32/10223_2.png) [@emk](https://discourse.gohugo.io/u/emk)\
**Post date:** [April 28, 2022, 4:41pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/12 "2022-04-28T16:41:08Z")

</div>

The problem is the wrong URL ///index.json automaticly generated based on baseurl in config.toml.  
To possible ways to solve the problem:

1. specify the correct baseurl (e.g. baseurl = “[https://my-website.com/](https://my-website.com/)”) # Include trailing slash!
2. insert the full URL auf index.json in the CUSTOM AREA of search.html (e.g. json\_src:‘[https://my-website.com/index.json](https://my-website.com/index.json)’)

(no. 2 doesn’t work local. I would prefer no. 1)

---

<div class="post-metadata">

**Author:** ![indigorobin](https://avatars.discourse-cdn.com/v4/letter/i/a5b964/32.png) [@indigorobin](https://discourse.gohugo.io/u/indigorobin)\
**Post date:** [April 28, 2022, 5:37pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/13 "2022-04-28T17:37:14Z")

</div>

Thanks for the reply- do you think a relative link might work?

---

<div class="post-metadata">

**Author:** ![emk](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/emk/32/10223_2.png) [@emk](https://discourse.gohugo.io/u/emk)\
**Post date:** [April 29, 2022, 7:47am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/14 "2022-04-29T07:47:07Z")

</div>

try it. (I’don’t know what you specified in config.toml, baseurl) see: [Gohugo without baseurl ? or with relative url only?](https://discourse.gohugo.io/t/gohugo-without-baseurl-or-with-relative-url-only/2779)

---

<div class="post-metadata">

**Author:** ![rubens](https://avatars.discourse-cdn.com/v4/letter/r/54ee81/32.png) [@rubens](https://discourse.gohugo.io/u/rubens)\
**Post date:** [June 16, 2022, 4:53pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/15 "2022-06-16T16:53:52Z")

</div>

How would you make this work on a multilingual site? This search works only for the default language when `defaultContentLanguageInSubdir = false` is set in the `config.toml`. Otherwise I get the following error message: `Search file could not be retrieved.`

I think the `index.json` needs to be adjusted for that but I am not able to figure out how.  
Thank you in advance!

---

<div class="post-metadata">

**Author:** ![idarek](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/idarek/32/26229_2.png) [@idarek](https://discourse.gohugo.io/u/idarek)\
**Post date:** [June 16, 2022, 9:49pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/16 "2022-06-16T21:49:22Z")

</div>

I am using this in two languages on [my site](https://dariusz.wieckiewicz.org/en/), not a big issue.

Firtly, use `i18n` to translate strings in a template, like

```auto
[Search]
other = "Search"

```

Then, in the config, for each language, you may want to specify `/search/` string manually.  
Example:

```auto
[languages]
  [languages.pl]
    search = "/szukaj/"

  [languages.en]
    search = "/en/search/"

```

You need to create two markdown files for results.  
For example, PL is my default, so I got search.md, and for EN search.en.md

> each markdown with specified `url` like `url: /szukaj/` or `/url: /en/search/`

You will need to replace search action in to adjust to multi languages

```auto
action="/search/?query="

```

This will call shortcode `{{< search >}}`  
Then you need two shortcode files. My example is `search.html` and `search.en.html.

> Noticed that you calling shortcode by using just `search` NOT `search.en`.

Let me know when you will be stuck and I can help you with that.

---

<div class="post-metadata">

**Author:** ![rubens](https://avatars.discourse-cdn.com/v4/letter/r/54ee81/32.png) [@rubens](https://discourse.gohugo.io/u/rubens)\
**Post date:** [June 17, 2022, 11:57am UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/17 "2022-06-17T11:57:56Z")

</div>

Thank you for your help. I eventually realized what needed to be adjusted. It is the `search.html` that has to be adapted to each language. So, for instance, `search.en.html` needs the following line:  
`json_src : /en/index.json`

All solved now for me.  
And thanks to the author of this search function!

---

<div class="post-metadata">

**Author:** ![felsqualle](https://avatars.discourse-cdn.com/v4/letter/f/e79b87/32.png) [@felsqualle](https://discourse.gohugo.io/u/felsqualle)\
**Post date:** [November 23, 2022, 7:43pm UTC](https://discourse.gohugo.io/t/a-simple-javascript-based-full-text-search-function/29119/18 "2022-11-23T19:43:36Z")

</div>

Thank you very much! Flawless integration, it just works and is easily customizable. 10/10. 🙂
