× Home
Host the free comment system yourself

Integrate Hashover 2.0 comments in Hugo websites

HashOver Commentsystem from Jacob Barkdull

HashOver 2.0 is a free open source PHP commenting system that can be integrated into static websites on your own webspace. The customisation options are very extensive. This makes it easy to integrate HashOver into your own design.

I do not want to use a comment system that is integrated into my Hugo website as an external service. In the EU, the GDPR sets narrow legal limits on what I can offer my website visitors without extensive permission forms. Since I also want my personal privacy to be protected, I have decided to use HashOver.

HashOver has been around since 2014. The author - Jacob Barkdull - has continuously developed the commenting system since then. An extensive - Documentation - is also available. I would like to take this opportunity to thank Jacob Barkdull for making HashOver available free of charge. I am aware that the development and maintenance of such a complex system is very time-consuming. With this in mind, however, I would still like to describe the things that I have noticed as faulty or in need of improvement.

Since I hope that comments on this website will be used intensively, I have decided to store them in a mySQL database. I do not want an unnecessary mixing of the Hugo system and HashOver. Therefore I installed HashOver in a subdomain of tekki-tipps.de. This allows a clean separation of the commenting system from the website. This blog post describes the preparation for the installation of HashOver, the installation, the integration into the Hugo website, tips for handling and also the from my point of view less nice things of HashOver.

Preparation for the installation of HashOver

First, I created the subdomain comments for tekki-tipps.de via the administration interface of my provider. Assigned an appropriate PHP version and added a Let’s Encrypt SSL certificate. Further steps:

  • Set up a forwarding of the email address hashover [at] comments [.] tekki-tipps[.]de for the comment system. The forwarding saves an additional e-mail account.

  • Then create a MySQL database for the subdomain. The tables are created by the installation routine of HashOver.

  • Download the - HashOver files - on the GitHub page click the code button and download the ZIP file to your local computer and unzip it.

  • The files secrets.php and sensitivesettings.php must be adapted before the file upload.

HashOver - secrets.php

The secrets.php is responsible for mail addresses, admin password, database name, etc. This file can be found under …/hashover-next-master/backend/classes/secrets.php. Before the first start of the HashOver installation, a value must be assigned to the following variables.


// Admin e-mail address to send notifications to
protected $notificationEmail = 'hashover [at] comments [.] tekki-tipps.de';

// E-mail address to use in notifications to normal users
protected $noreplyEmail = 'hashover [at] comments [.] tekki-tipps.de';

// Unique encryption key (case-sensitive)
protected $encryptionKey = 'xxxxxxx…';

// Login name to gain admin rights (case-sensitive)
protected $adminName = 'Frank Kunert';

// Login password to gain admin rights (case-sensitive)
protected $adminPassword = 'xxxxxxx…';

Notifications are sent to this admin email address when new comments or replies to comments are created. Here I have entered the email forwarding address described above. The email address must of course be spelled correctly. My spam protection spelling for this blog post is not really a protection.

This email address is used for informing each other as the sender. But only if the checkbox in the comment form Notify me of replies has been activated. HashOver then uses the noreplyEmail to send an info to the email addresses that have been saved in the comments and want this.

The meaning of a noreply email address is to ask the user not to reply to that email address. If the user does not comply with this, the email will still be sent to the admin through forwarding.

The key is used to encrypt and decrypt sensitive user data, such as the e-mail address. A copy of the entire secrets.php should be kept in a safe place. When updating to a new version of HashOver, the contents of this file must be transferred from the backup.

The admin name is needed for the HashOver login. If a user comment is answered by the admin, HashOver displays the name as sender of the answer. The admin name does not remain secret.

Since the admin name is not secret, a long and secure password should be chosen.


  // Type of database, sqlite or mysql
  protected $databaseType = 'mysql';

  // Database name
  protected $databaseName = 'xxxxxxx…';

  // SQL database host name
  protected $databaseHost = 'localhost';

  // SQL database port number
  protected $databasePort = '3306';

  // SQL database login user
  protected $databaseUser = 'xxxxxxx…';

  // SQL database login password
  protected $databasePassword = 'xxxxxxx…';

  // SQL database character set
  protected $databaseCharset = 'utf8';

The field contents for the database are self-explanatory.

I have not changed the OPTIONAL SMTP MAILER SETUP section. I do not need this information because my provider provides sendmail for the PHP mail function.

HashOver - sensitivesettings.php

If HashOver is installed in a subdomain, this must be made known to HashOver in the file …/hashover-next-master/backend/classes/sensitivesettings.php.

// Whether multiple website support is enabled
public $supportsMultisites = true;

// External domains allowed to remotely load HashOver scripts
public $allowedDomains = array (
	// '*.example.org',
	// '*.example.net'

This is not mentioned in the HashOver documentation. I found the reference on the website of - Johann Oberdorfer .

Installation of HashOver

Transfer the contents of the unzipped and adapted hashover-next-master directory to the web server via SFTP. I created a directory within the subdomain and copied everything into it.

The first start of HashOver generates the database tables:

Further logins can then be made via the following URL:

The user and password of the login correspond to your own admin details in secrets.php.

After logging in, you can configure HashOver extensively. For example, I don’t want users to be able to log into the HashOver installation and customise their own comments. I allow session cookies, but not others.

The - Documentation of the configuration parameters - is very extensive and explains them very precisely in most cases. The parameters are stored in /your-folder/config/settings.json. The following is the settings.json I used:

  "allowed-domains": [
  "supports-multisites": true,
  "language": "de-de",
  "theme": "default-borderless",
  "default-sorting": "ascending",
  "uses-markdown": true,
  "uses-ajax": true,
  "shows-reply-count": false,
  "allows-images": true,
  "allows-likes": false,
  "allows-dislikes": false,
  "uses-moderation": false,
  "pends-user-edits": false,
  "mail-type": "text",
  "mailer": "sendmail",
  "subscribes-user": false,
  "allows-user-replies": false,
  "sets-cookies": false,
  "cookie-expiration": "session",
  "secure-cookies": false,
  "collapses-interface": false,
  "collapses-comments": false,
  "collapse-limit": 3,
  "popularity-threshold": 5,
  "popularity-limit": 2,
  "spam-database": "remote",
  "spam-check-modes": "both",
  "icon-mode": "none",
  "icon-size": 45,
  "gravatar-default": "custom",
  "gravatar-force": false,
  "form-position": "top",
  "name-field": "on",
  "password-field": "off",
  "email-field": "on",
  "website-field": "off",
  "displays-title": false,
  "uses-cancel-buttons": true,
  "uses-labels": false,
  "date-pattern": "dd.MM.YYYY",
  "time-pattern": "HH:mm",
  "server-timezone": "Europe\/Berlin",
  "uses-user-timezone": true,
  "uses-short-dates": false,
  "login-method": "DefaultLogin",
  "allows-login": false,
  "uses-auto-login": false,
  "data-format": "sql",
  "default-name": "Anonymous",
  "reply-mode": "stream",
  "stream-depth": 3,
  "image-format": "png",
  "appends-css": true,
  "appends-rss": false,
  "counts-deletions": false,
  "local-metadata": true,
  "stores-ip-address": false,
  "minifies-javascript": true,
  "minify-level": 4

Integrating HashOver into the Hugo website

As already mentioned several times, the PHP installation of HashOver is in a subdomain in my installation. This means it is outsourced outside the Hugo website. Within the Hugo pages, HashOver is integrated via JavaScript.

My blog has a light and a dark theme, which can be switched by the user. Also, this website is multilingual. Adapting to these aggravating conditions works perfectly with HashOver, except for the date adjustment.

HashOver is delivered with different themes. The changing of the themes can be adjusted via JavaScript. The same applies to the language.

The layout template single.html

I would like to position the comments below the individual blog posts. I have a separate layout for the blog posts.


In this layout, the partial hashOver.html is called as the last action:

{{ define "main" }}
<section class="blog-content hugoblog">
  <article class="container">
    <div class="blog-content-block">
      <div class="sub-title">{{ .Params.subtitle }}</div>
      <h1>{{ .Title }}</h1>
    {{ partial "img-article" . }}
    <div class="blog-content-block reverse">
      <div class="blog-content-text">
      {{ .Content }}
      {{ partial "date-and-tags.html" . }}
    {{ partial "hugo-version" . }}
    {{ partial "hashOver" . }}

Hugo Partial hashOver.html

I explain the partial hashOver.html in detail below. It has the following content:

<section class="comments">
  <p class="h-header">{{- T "hashOverHeader" -}}</p>
  <script type="text/javascript" src="https://comments.tekki-tipps.de/hashover/loader.php"></script>
  {{ $siteLang := .Site.Language.Lang }}
  {{ if eq $siteLang "de" }}
  <div id="hashover"></div>
  <script type="text/javascript">
    var mytheme = localStorage.getItem("currenttheme");
    if( mytheme == "lightTheme") {
      mytheme = "default-borderless";
    else {
      mytheme = "default-dark-borderless";
    var hashover = new HashOver ('hashover', {
      settings: {language: 'de_DE', theme: mytheme}
  <noscript><h2 class="noscript">{{- T "noscriptJS" -}}</h2></noscript>
  {{ else }}
  <div id="hashover"></div>
  <script type="text/javascript">
    var mytheme = localStorage.getItem("currenttheme");
    if( mytheme == "lightTheme") {
      mytheme = "default-borderless";
    else {
      mytheme = "default-dark-borderless";
    var hashover = new HashOver ('hashover', {
      settings: {language: 'us_US', theme: mytheme}
  <noscript><h2 class="noscript">{{- T "noscriptJS" -}}</h2></noscript>
  {{ end }}

The preparatory call of HashOver

<section class="comments">
  <p class="h-header">{{- T "hashOverHeader" -}}</p>
  <script type="text/javascript" src="https://comments.tekki-tipps.de/hashover/loader.php"></script>

First, a multilingual text hint is output with {{- T “hashOverHeader” -}}. HashOver is based on the URL. Multilingual Hugo websites also have different URLs for the different languages. This means that a comment on a German website will only appear on this page. The identical, English blog text does not show this comment and vice versa. Multilingualism creates a lot of extra work. Hugo makes this a lot easier, but still the extra work remains. In the future, I will also publish blog posts about using multilingualism within Hugo.

After that, the loader.php of the HashOver installation is called.

Multilingualism has its price

<section class="comments">
  {{ $siteLang := .Site.Language.Lang }}
  {{ if eq $siteLang "de" }}
  <div id="hashover"></div>
  <script type="text/javascript">
    var mytheme = localStorage.getItem("currenttheme");
    if( mytheme == "lightTheme") {
      mytheme = "default-borderless";
    else {
      mytheme = "default-dark-borderless";
    var hashover = new HashOver ('hashover', {
      settings: {language: 'de_DE', theme: mytheme}
  <noscript><h2 class="noscript">{{- T "noscriptJS" -}}</h2></noscript>
  {{ else }}
  {{ end }}

My blog is published in German and English. Therefore, a if … else … end query is sufficient for me. First, I store the language code of the current web page in a variable. If it is a German language code, I go into the if branch, otherwise - since it is English text in my case - into the else branch.

After that, an empty <div> HTML element with the ID hashover is created. HashOver looks for this ID and fills this <div> with the display of the comments.

As described above, I use a light and a dark theme for this blog. I store which theme is used by the user in the localStorage of the browser used. If my lightTheme is used, I assign a matching theme from HashOver to the variable. Then a new instance of HashOver is generated and the language to be used and the corresponding theme are passed as parameters.

Since HashOver is called via JavaScript, there must of course also be a noscript block with corresponding multilingual output.

Colour integration of HashOver via SCSS

The various themes of HashOver are well thought-out and fulfil their function, including the responsive display on small screens. Nevertheless, in my opinion, there is a need for intervention in some places. Like many things in life, beauty is in the eye of the beholder.

In the beginning, I tried to address HashOver externally via my local MAMP installation and thus adapt the respective theme locally. But I failed miserably.

I had no choice but to painstakingly change the SCSS code locally, create the Hugo website, transfer it to the web server via SFTP and then realise - the colour doesn’t fit after all. For this reason, the changes have been quite sparing:

.comments {
  max-width: $content-blog-width;
  margin: 0 auto;
  padding: 0 1.0rem;
  .h-header {
    border-top: 1px solid var(--wk-accent-border-color);
    padding: 2.0rem 0;
  .noscript {
    color: var(--wk-accent-color-2);
    margin-bottom: 2.0rem;
  .hashover-title {
    color: var(--wk-accent-color-3);
  .hashover-end-links {
    a {
      color: var(--wk-accent-color-3) !important;
    a:hover {
      color: var(--wk-accent-color-3) !important;

HashOver’s own CSS code is loaded by HashOver itself. There is no need to integrate the CSS code in any way. The documentation does say that if you do not include the CSS code manually, the design may be delayed. I have not found this to be the case - see: HashOver 2.0 Documentation - Best Practices .

Show number of comments

In the layout template single.html I call the partial date-and-tags.html. Within this partial, a <span> element with the ID hashover-comment-count is positioned. The ID searches HashOver and replaces the content of the element with the number of post comments.

<div class="comment-count">
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
  <path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
  <path d="M2.165 15.803l.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-. 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z"/>
  <span id="hashover-comment-count">0</span>

Handling tips

I will add to this section as my HashOver experience grows.

Notify me of replies

If the checkbox is activated and someone replies directly to this comment via “Reply”, one receives an e-mail with the reply. If an independent comment is written for the blog post, you will not receive an email.

Admin comments

If one is logged in as admin in a browser, the comment form is automatically filled with the admin’s data.

In one tab I log in to HashOver, in another tab a blog post with comments is open. As an admin logged into the browser, you can delete, edit and reply to user comments within the blog post. This is very convenient. The more I deal with HashOver, the more convinced I am of the comment system.

Bugs, things to improve, feature request

Jacob Barkdull responds to - GitHub Issues - unfortunately not very often. I can understand that when you are in charge of a complex project for years, some questions and hints from users just get on your nerves and seem superfluous to you. The documentation is extensive and very helpful, but some things need more explanation. The worldwide use of the commentary system creates additional problems locally. Example: Switching the date format on a multilingual website.

Remember - HashOver is free of charge. Criticising is easy, changing something yourself is more difficult. My PHP knowledge does not reach the depth - which would be necessary for changes. But maybe a PHP professional is willing to make the appropriate patches available on GitHub out of self-interest.


When replying to a comment and clicking on “Send reply”, the input form remains open. The saved comment is displayed below the still open input form. As soon as the page is redisplayed by a refresh, the input form with the new comment has disappeared. In my opinion, if you click on the “Send reply” button, you only need to refresh the page after saving to correct this error.

The following error is displayed in the browser’s network environment after clicking on “Send reply”:

comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8 Uncaught TypeError: this.reappendMoreLink is not a function
    at HashOver.appendComments (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
    at HashOver.appendComments (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
    at HashOver.AJAXPost (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
    at XMLHttpRequest.commentHandler (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
    at XMLHttpRequest.onSuccess (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
    at XMLHttpRequest.xhr.onreadystatechange (comments.php?cfg[language]=de_DE&cfg[theme]=default-borderless:8)
HashOver Form Error
Error: The input form is not closed.

Things to improve

The date format cannot be changed via JavaScript. This means that on multilingual websites the date format cannot be adjusted. On this website, the English comments have the German date format. This is likely to be irritating.

When displaying a comment, only the date of entry is shown but not the time. uses-user-timezone is enabled, so set to true.

Above the comments, the total number is displayed. If there is more than one comment, the word comment is not expanded to plural. Result: 2 comment instead of 2 comments. The translations are in the directory backend/locales/de-en.php. There are also two entries there:

‘showing-comment’ => ‘%d Kommentar’,
‘showing-comments’ => ‘%d Kommentare’,

But apparently showing-comments' is not used.

Feature Request

For privacy reasons, I use the option Avatar icon display mode = None. Actually, I find the Gravatar option very nice, but as the function is implemented in HashOver, I am not allowed to use it for legal reasons in the EU. The GDPR requires that a user must have the possibility to object to the use of their personal data. The email address is matched directly with Gravatar when avatars are turned on. This is the only way that the personal image can be delivered by Gravatar. This is perfectly ok, because a Gravatar user knows what he is doing. However, the email address of a user who does not have a Gravatar account is transferred to Gravatar without any possibility of objection. The GDPR does not allow such a thing.

It would be nice to have a “Use Gravatar” checkbox in the input form. The checkbox would have to be disabled and only when the checkbox is enabled, the email address is matched with Gravatar. In addition, there should be a field “Admin Gravatar email address” in the settings for the administrator. This separates the admin account from the gravatar account.


So far I am very satisfied with HashOver. The customisation capabilities are very extensive. The bug with the input form should be fixed as soon as possible.

List of links to this post

15 minutes to read
This post was created with Hugo version 0.91.2.

With the German language setting, comments are not displayed in the English version of the website and vice versa.

© 2022 - Frank Kunert  -  About me
A service from webdienste-kunert.de