A quick and dirty pelican search engine
A hacky solution to provide a search engine on a pelican-powered website
"A quick and dirty pelican search engine" on https://aligot-death.space, available at https://aligot-death.space/txt/guides/pelican-search-en
Static website are wonderful: they require very little maintenance, are usually very lightweight and don't require fancy environments.
The problem is that they aren't really made for anything "dynamic", such as internal search engines. Some solutions exist, like Lunr, Pagefind and others, but they usually are compatible with other static website frameworks but not pelican (or at least, not without considerable work), and may require some amount of external tools.
But search engines can be useful. So, on a slow day, I tried to make a self-contained search engine for Pelican, using nothing but vanilla templates and JS.
You can see the simplified demo to look at the code here, and the real one with some nasty stuff here.
The approach is as follow: make a page with a template, which contains a list of all the articles and desired attributes, including the whole (text) content, and use javascript to filter that. Clean (kinda) and simple.
First, create a simple page in content/pages (or your page directory), here using RestructuredText (but you can use Markdown if you prefer, obviously):
1 Search (Demo) 2 ############# 3 4 :date: 2025-08-24 5 :template: custom/search_demo 6 :slug: search_demo 7 :lang: en 8 :status: published 9 :summary: 10 11 This is a simplified version of my pelican's search system for demo purposes
Then, create a template under theme/templates/custom, for instance named search_demo.html to match the provided template above (yes, you can omit the .html). Let's start with your run of the mill stuff:
1 {% extends "base.html" %} 2 <!-- template: custom/search_demo.html --> 3 {% block content %} 4 <main class = "search"> 5 {% block content_title %} 6 {% endblock %} 7 <h1>{{page.title}}</h1> 8 <p class = "summary">{{ page.summary|striptags }}</p> 9 10 {{ page.content }} 11 12 </main><!-- /#content --> 13 {% endblock content %}
Now, let's populate the page. For that purpose, we're going to create a loop with the jinja templating engine, which lists every article as a li element under a ul list. Because the script only use the actual text, we can get fancy and have titles, dates, categories, etc.
Under the {{ page.content }}, put the following code:
1 <section class = "search"> 2 <ul id = "list"> 3 <!-- List articles by recent date --> 4 {% for article in articles|sort(attribute='date', reverse=True) %} 5 <li> 6 <!-- handle "save_as" url override --> 7 <a href = "{% if article.save_as %}{{ article.save_as }}{% else %}{{ article.url }}{% endif %}"><h3>{{article.title}}</h3></a> 8 <div class = "date">{{article.locale_date}}</div>{% if article.category %}<a class = "category" href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>{% endif %}{% if article.tags %}{% for tag in article.tags %}<a class = "tag" href="{{ SITEURL }}/{{ tag.url }}">{{ tag }}</a>{% endfor %}{% endif %} 9 <!-- don't show the whole article, but we need the whole content for search purposes --> 10 <p style = "max-width: 100ch; max-height: 4rem; overflow: hidden;"> 11 <!-- get article content, but strip any html or codes --> 12 {{article.content | striptags | escape }} 13 </p> 14 </li> 15 {% endfor %} 16 </ul> 17 </section>
Now, if you generate your website, you should have a page under /search_demo containing everything on your website.
To make it easier to use, the content of the articles is visually truncated: but the text is still here.
Now, let's add a filtering system. First off, add a search field above the ul:
1 <div class = "search_field"> 2 <input type="search" placeholder="Search..." oninput="filterByName(event)"> 3 </div>
And now, the actual code. As said in the comment, it's based on Chris Gustin's search snippet, except I changed its behavior, so it doesn't show anything unless you type something. You can put that script in a file if you want, but here for conciseness I just added a <script> tag, right above the <section>.
1 <!-- https://medium.com/@cgustin/tutorial-simple-search-filter-with-vanilla-javascript-fdd15b7640bf --> 2 3 <script> 4 function filterByName(event) { 5 const searchTerm = event.target.value.trim().toLowerCase(); 6 const listItems = document.querySelectorAll("ul#list li"); 7 8 listItems.forEach(function(item) { 9 10 // If the search bar is empty, display nothing instead of all the articles 11 if(searchTerm === "") { 12 item.style.display = 'none'; 13 } 14 else { 15 // set "display: none;" on all articles not containing the search terms 16 if (!item.innerText.toLowerCase().includes(searchTerm)) { 17 item.style.display = 'none'; 18 } 19 else { 20 item.style.display = 'revert'; 21 } 22 } 23 }) 24 } 25 </script>
Now, the page shows nothing except the search field. But if you start typing, it should list the corresponding results.
Performances are decent: my website has 800+ articles (albeit most of them just being a single image under art, but also some pretty big articles). The whole webpage with all the text I created in almost ten years is about 1MB.
A big caveat however: right now, it's only able to search for full expressions, such as "sky is obscured", but not combined words like "obscured sky mont thou".
You can download the whole template as is here: