diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index cd57b632e2ea2b9ab005c7185a309a9594f123ff..0304440ef627ee453f646bc8eec4809dc1553024 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -75,6 +75,27 @@ BUILD_NAVBAR_BRAND_NAME=CaosDB
 BUILD_TITLE_BRAND_NAME=CaosDB
 BUILD_FAVICON=pics/caosdb_logo_42.png
 
+##############################################################################
+# queryForm properties
+##############################################################################
+
+# Initialize the free search to generate search queries which search only
+# within one of several options of roles or entity names.
+#
+# E.g. when `options` is "Person, Experiment, Sample" the user can select
+# one of these options before submitting the query. When the user types in
+# something that doesn't looks like a CQL-query, a query is generated
+# instead which goes like: FIND Persion WHICH HAS A PROPERTY LIKE
+# "*something*".
+#
+# Note: This feature is disabled by default. Enable it by specifying the
+# build variable BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS as a
+# comma-separated list of **properly quoted** expressions, e.g.
+# "FILE 'numpy array', 'Plant Experiemnt', 'quotes_not_necessary'".
+# Otherwise, the server will throw a syntax error.
+BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS=
+
+
 ##############################################################################
 # Footer properties
 ##############################################################################
@@ -121,6 +142,7 @@ BUILD_MODULE_EXT_TRIGGER_CRAWLER_FORM_TOOLBOX="Tools"
 # MODULE_DEPENDENCIES array.
 ##############################################################################
 JS_DIST_BUNDLE=TRUE
+
 ##############################################################################
 # TRUE means that all javascript sources which are no mentioned in the
 # MODULE_DEPENDENCIES array will be added in no particular order into the
@@ -128,6 +150,7 @@ JS_DIST_BUNDLE=TRUE
 # appear in the dit file) you need to add them to the MODULE_DEPENDENCIES.
 ##############################################################################
 AUTO_DISCOVER_MODULES=TRUE
+
 ##############################################################################
 # Module dependencies
 # Override or extend to specify the order of js files in the resulting
@@ -146,6 +169,7 @@ MODULE_DEPENDENCIES=(
     webcaosdb.js
     pako.js
     utif.js
+    ext_version_history.js
     caosdb.js
     form_elements.js
     ext_autocomplete.js
diff --git a/src/core/js/ext_version_history.js b/src/core/js/ext_version_history.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea65481589c2f95405833f7b4a929f450ea6da96
--- /dev/null
+++ b/src/core/js/ext_version_history.js
@@ -0,0 +1,238 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2019-2022 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2019-2022 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
+ * Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * ** end header
+ */
+
+
+/**
+ * This module provides the functionality to load the full version history (for
+ * privileged users) and export it to tsv.
+ *
+ * @module version_history
+ */
+var version_history = new function () {
+
+    const logger = log.getLogger("version_history");
+    this.logger = logger;
+
+    this._has_version_fragment = function () {
+        const fragment = window.location.hash.substr(1);
+        return fragment === 'version_history';
+    }
+
+    this._get = connection.get;
+    /**
+     * Retrieve the version history of an entity and return a table with the
+     * history.
+     *
+     * @function retrieve_history
+     * @param {string} entity - the entity id with or without version id.
+     * @return {HTMLElement} A table with the version history.
+     */
+    this.retrieve_history = async function (entity) {
+        const xml = this._get(transaction
+            .generateEntitiesUri([entity]) + "?H");
+        const html = (await transformation.transformEntities(xml))[0];
+        const history_table = $(html).find(".caosdb-f-entity-version-history");
+        return history_table[0];
+    }
+
+    /**
+     * Initalize the buttons for loading the version history.
+     *
+     * The buttons are visible when the entity has only the normal version info
+     * attached and the current user has the permissions to retrieve the
+     * version history.
+     *
+     * The buttons trigger the retrieval of the version history and append the
+     * version history to the version info modal.
+     *
+     * @function init_load_history_buttons
+     */
+    this.init_load_history_buttons = function () {
+        for (let entity of $(".caosdb-entity-panel")) {
+            const is_permitted = hasEntityPermission(entity, "RETRIEVE:HISTORY");
+            if (!is_permitted) {
+                continue;
+            }
+            const entity_id_version = getEntityIdVersion(entity);
+            const version_info = $(entity)
+                .find(".caosdb-f-entity-version-info");
+            const button = $(version_info)
+                .find(".caosdb-f-entity-version-load-history-btn");
+            button.show();
+            button
+                .click(async () => {
+                    button.prop("disabled", true);
+                    const wait = createWaitingNotification("Retrieving full history. Please wait.");
+                    const sparse = $(version_info)
+                        .find(".caosdb-f-entity-version-history");
+                    sparse.find(".modal-body *").replaceWith(wait);
+
+                    const history_table = await version_history
+                        .retrieve_history(entity_id_version);
+                    sparse.replaceWith(history_table);
+                    version_history.init_export_history_buttons(entity);
+                    version_history.init_restore_version_buttons(entity);
+                });
+        }
+    }
+
+    /**
+     * Transform the HTML table with the version history to tsv.
+     *
+     * @function get_history_tsv
+     * @param {HTMLElement} history_table - the HTML representation of the
+     *     version history.
+     * @return {string} the version history as downloadable tsv string,
+     *     suitable for the href attribute of a link or window.location.
+     */
+    this.get_history_tsv = function (history_table) {
+        const rows = [];
+        for (let row of $(history_table).find("tr")) {
+            const cells = $(row).find(".export-data").toArray().map(x => x.textContent);
+            rows.push(cells);
+        }
+        return caosdb_utils.create_tsv_table(rows);
+    }
+
+    /**
+     * Initialize the export buttons of `entity`.
+     *
+     * The buttons are only visible when the version history is visible and
+     * trigger a download of a tsv file which contains the version history.
+     *
+     * The buttons trigger the download of a tsv file with the version history.
+     *
+     * @function init_export_history_buttons
+     * @param {HTMLElement} [entity] - if undefined, the export buttons of all
+     *     page entities are initialized.
+     */
+    this.init_export_history_buttons = function (entity) {
+        entity = entity || $(".caosdb-entity-panel");
+        for (let version_info of $(entity)
+                .find(".caosdb-f-entity-version-info")) {
+            $(version_info).find(".caosdb-f-entity-version-export-history-btn")
+                .click(() => {
+                    const html_table = $(version_info).find("table")[0];
+                    const history_tsv = this.get_history_tsv(html_table);
+                    version_history._download_tsv(history_tsv);
+                });
+        }
+    }
+
+    /**
+     * Initialize the restore old version buttons of `entity`.
+     *
+     * The buttons are only visible when the user is allowed to update the
+     * entity.
+     *
+     * The causes a retrieve of the specified version of the entity and then an
+     * update that restores that version.
+     *
+     * @function init_restore_version_buttons
+     * @param {HTMLElement} [entity] - if undefined, the export buttons of all
+     *     page entities are initialized.
+     */
+    this.init_restore_version_buttons = function (entity) {
+        var entities = [entity] || $(".caosdb-entity-panel");
+
+        for (let _entity of entities) {
+            // initialize buttons only if the user is allowed to update the entity
+            if (hasEntityPermission(_entity, "UPDATE:*") || hasEntityPermission(_entity, "UPDATE:DESCRIPTION")) {
+                for (let version_info of
+                        $(_entity).find(".caosdb-f-entity-version-info")) {
+                    // find the restore button
+                    $(version_info).find(".caosdb-f-entity-version-restore-btn")
+                        .toggleClass("d-none", false) // show button
+                        .click(async (eve) => {
+                            // the version id is stored in the restore button's
+                            // data-version-id attribute
+                            const versionid = eve.delegateTarget.getAttribute("data-version-id")
+                            const reload = () => {
+                                window.location.reload();
+                            }
+                            const _alert = form_elements.make_alert({
+                                title: "Warning",
+                                message: "You are going to restore this version of the entity.",
+                                proceed_callback: async () => {
+                                    try {
+                                        await restore_old_version(versionid);
+                                        $(_alert).remove();
+                                        // reload after sucessful update
+                                        $(version_info).find(".modal-body").prepend(
+                                            $(`<div class="alert alert-success" role="alert">Restore successful! <p>You are being forwarded to the latest version of this entity or you can click <a href="#" onclick="window.location.reload()">here</a>.</p></div>`));
+                                        setTimeout(reload, 5000);
+                                    } catch (e) {
+                                        logger.error(e);
+                                        // print errors in an alert div
+                                        $(version_info).find(".modal-body").prepend(
+                                            $(`<div class="alert alert-danger alert-dismissible " role="alert"> <button class="btn-close" data-bs-dismiss="alert" aria-label="close"></button> Restore failed! <p>${e.message}</p></div>`));
+
+                                    }
+                                },
+                                cancel_callback: () => {
+                                    // do nothing
+                                    $(_alert).remove();
+                                    $(version_info).find("table").show();
+                                },
+                                proceed_text: "Yes, restore!",
+                                remember_my_decision_id: "restore_entity",
+                            });
+
+                            $(version_info).find("table").after(_alert).hide();
+                            $(_alert).addClass("text-end");
+                        });
+                }
+            }
+        }
+    }
+
+    this._download_tsv = function (tsv_link) {
+        window.location.href = tsv_link;
+    }
+
+
+    /**
+     * @function init
+     */
+    this.init = function () {
+        this.init_load_history_buttons();
+        this.init_export_history_buttons();
+        this.init_restore_version_buttons();
+
+        // check for the version_history fragment and open the modal if present.
+        if (this._has_version_fragment()) {
+            const first_entity = $(".caosdb-entity-panel")[0];
+            if (first_entity && hasEntityPermission(first_entity, "RETRIEVE:HISTORY")) {
+                logger.debug("Showing full version modal for first entity");
+                const version_button = $(first_entity).find(".caosdb-f-entity-version-button");
+                version_button.click();
+                const full_version_history_button = $(first_entity).find(".caosdb-f-entity-version-load-history-btn");
+                full_version_history_button.click();
+            }
+        }
+    }
+}
+
+caosdb_modules.register(version_history);
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index 3f26cd7b4aa127b7c3d2065f0eece10194332618..bfe0eb35c8e72e6493ad273ae0ed5a727019c628 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -26,6 +26,13 @@
  */
 'use strict';
 
+/**
+ * Core functionality of the CaosDB web interface.
+ *
+ * @module webcaosdb
+ * @global
+ */
+
 window.addEventListener('error', (e) => globalError(e.error));
 
 var globalError = function (error) {
@@ -61,6 +68,9 @@ var globalClassNames = new function () {
 
 /**
  * navbar module contains convenience functions for the navbar.
+ *
+ * @module navbar
+ * @global
  */
 this.navbar = new function () {
 
@@ -299,6 +309,10 @@ this.navbar = new function () {
 }
 
 
+/**
+ * @module caosdb_utils
+ * @global
+ */
 this.caosdb_utils = new function () {
     this.assert_string = function (obj, name, optional = false) {
         if (typeof obj === "undefined" && optional) {
@@ -370,6 +384,9 @@ this.caosdb_utils = new function () {
 
 /**
  * connection module contains all ajax calls.
+ *
+ * @module connection
+ * @global
  */
 this.connection = new function () {
     const logger = log.getLogger("connection");
@@ -544,6 +561,9 @@ this.connection = new function () {
 /**
  * transformation module contains all code for tranforming xml into html via
  * xslt.
+ *
+ * @module transformation
+ * @global
  */
 this.transformation = new function () {
     /**
@@ -656,6 +676,9 @@ this.transformation = new function () {
 /**
  * transaction module contains all code for insertion, update and deletion of 
  * entities. Currently, only updates are implemented.
+ *
+ * @module transaction
+ * @global
  */
 this.transaction = new function () {
     this.classNameUpdateForm = "caosdb-update-entity-form";
@@ -995,205 +1018,9 @@ this.transaction = new function () {
 }
 
 /**
- * This module provides the functionality to load the full version history (for
- * privileged users) and export it to tsv.
+ * @module paging
+ * @global
  */
-var version_history = new function () {
-
-    const logger = log.getLogger("version_history");
-    this.logger = logger;
-
-    this._has_version_fragment = function () {
-        const fragment = window.location.hash.substr(1);
-        return fragment === 'version_history';
-    }
-
-    this._get = connection.get;
-    /**
-     * Retrieve the version history of an entity and return a table with the
-     * history.
-     *
-     * @param {string} entity - the entity id with or without version id.
-     * @return {HTMLElement} A table with the version history.
-     */
-    this.retrieve_history = async function (entity) {
-        const xml = this._get(transaction
-            .generateEntitiesUri([entity]) + "?H");
-        const html = (await transformation.transformEntities(xml))[0];
-        const history_table = $(html).find(".caosdb-f-entity-version-history");
-        return history_table[0];
-    }
-
-    /**
-     * Initalize the buttons for loading the version history.
-     *
-     * The buttons are visible when the entity has only the normal version info
-     * attached and the current user has the permissions to retrieve the
-     * version history.
-     *
-     * The buttons trigger the retrieval of the version history and append the
-     * version history to the version info modal.
-     */
-    this.init_load_history_buttons = function () {
-        for (let entity of $(".caosdb-entity-panel")) {
-            const is_permitted = hasEntityPermission(entity, "RETRIEVE:HISTORY");
-            if (!is_permitted) {
-                continue;
-            }
-            const entity_id_version = getEntityIdVersion(entity);
-            const version_info = $(entity)
-                .find(".caosdb-f-entity-version-info");
-            const button = $(version_info)
-                .find(".caosdb-f-entity-version-load-history-btn");
-            button.show();
-            button
-                .click(async () => {
-                    button.prop("disabled", true);
-                    const wait = createWaitingNotification("Retrieving full history. Please wait.");
-                    const sparse = $(version_info)
-                        .find(".caosdb-f-entity-version-history");
-                    sparse.find(".modal-body *").replaceWith(wait);
-
-                    const history_table = await version_history
-                        .retrieve_history(entity_id_version);
-                    sparse.replaceWith(history_table);
-                    version_history.init_export_history_buttons(entity);
-                    version_history.init_restore_version_buttons(entity);
-                });
-        }
-    }
-
-    /**
-     * Transform the HTML table with the version history to tsv.
-     *
-     * @param {HTMLElement} history_table - the HTML representation of the
-     *     version history.
-     * @return {string} the version history as downloadable tsv string,
-     *     suitable for the href attribute of a link or window.location.
-     */
-    this.get_history_tsv = function (history_table) {
-        const rows = [];
-        for (let row of $(history_table).find("tr")) {
-            const cells = $(row).find(".export-data").toArray().map(x => x.textContent);
-            rows.push(cells);
-        }
-        return caosdb_utils.create_tsv_table(rows);
-    }
-
-    /**
-     * Initialize the export buttons of `entity`.
-     *
-     * The buttons are only visible when the version history is visible and
-     * trigger a download of a tsv file which contains the version history.
-     *
-     * The buttons trigger the download of a tsv file with the version history.
-     *
-     * @param {HTMLElement} [entity] - if undefined, the export buttons of all
-     *     page entities are initialized.
-     */
-    this.init_export_history_buttons = function (entity) {
-        entity = entity || $(".caosdb-entity-panel");
-        for (let version_info of $(entity)
-                .find(".caosdb-f-entity-version-info")) {
-            $(version_info).find(".caosdb-f-entity-version-export-history-btn")
-                .click(() => {
-                    const html_table = $(version_info).find("table")[0];
-                    const history_tsv = this.get_history_tsv(html_table);
-                    version_history._download_tsv(history_tsv);
-                });
-        }
-    }
-
-    /**
-     * Initialize the restore old version buttons of `entity`.
-     *
-     * The buttons are only visible when the user is allowed to update the
-     * entity.
-     *
-     * The causes a retrieve of the specified version of the entity and then an
-     * update that restores that version.
-     *
-     * @param {HTMLElement} [entity] - if undefined, the export buttons of all
-     *     page entities are initialized.
-     */
-    this.init_restore_version_buttons = function (entity) {
-        var entities = [entity] || $(".caosdb-entity-panel");
-
-        for (let _entity of entities) {
-            // initialize buttons only if the user is allowed to update the entity
-            if (hasEntityPermission(_entity, "UPDATE:*") || hasEntityPermission(_entity, "UPDATE:DESCRIPTION")) {
-                for (let version_info of
-                        $(_entity).find(".caosdb-f-entity-version-info")) {
-                    // find the restore button
-                    $(version_info).find(".caosdb-f-entity-version-restore-btn")
-                        .toggleClass("d-none", false) // show button
-                        .click(async (eve) => {
-                            // the version id is stored in the restore button's
-                            // data-version-id attribute
-                            const versionid = eve.delegateTarget.getAttribute("data-version-id")
-                            const reload = () => {
-                                window.location.reload();
-                            }
-                            const _alert = form_elements.make_alert({
-                                title: "Warning",
-                                message: "You are going to restore this version of the entity.",
-                                proceed_callback: async () => {
-                                    try {
-                                        await restore_old_version(versionid);
-                                        $(_alert).remove();
-                                        // reload after sucessful update
-                                        $(version_info).find(".modal-body").prepend(
-                                            $(`<div class="alert alert-success" role="alert">Restore successful! <p>You are being forwarded to the latest version of this entity or you can click <a href="#" onclick="window.location.reload()">here</a>.</p></div>`));
-                                        setTimeout(reload, 5000);
-                                    } catch (e) {
-                                        logger.error(e);
-                                        // print errors in an alert div
-                                        $(version_info).find(".modal-body").prepend(
-                                            $(`<div class="alert alert-danger alert-dismissible " role="alert"> <button class="btn-close" data-bs-dismiss="alert" aria-label="close"></button> Restore failed! <p>${e.message}</p></div>`));
-
-                                    }
-                                },
-                                cancel_callback: () => {
-                                    // do nothing
-                                    $(_alert).remove();
-                                    $(version_info).find("table").show();
-                                },
-                                proceed_text: "Yes, restore!",
-                                remember_my_decision_id: "restore_entity",
-                            });
-
-                            $(version_info).find("table").after(_alert).hide();
-                            $(_alert).addClass("text-end");
-                        });
-                }
-            }
-        }
-    }
-
-    this._download_tsv = function (tsv_link) {
-        window.location.href = tsv_link;
-    }
-
-
-    this.init = function () {
-        this.init_load_history_buttons();
-        this.init_export_history_buttons();
-        this.init_restore_version_buttons();
-
-        // check for the version_history fragment and open the modal if present.
-        if (this._has_version_fragment()) {
-            const first_entity = $(".caosdb-entity-panel")[0];
-            if (first_entity && hasEntityPermission(first_entity, "RETRIEVE:HISTORY")) {
-                logger.debug("Showing full version modal for first entity");
-                const version_button = $(first_entity).find(".caosdb-f-entity-version-button");
-                version_button.click();
-                const full_version_history_button = $(first_entity).find(".caosdb-f-entity-version-load-history-btn");
-                full_version_history_button.click();
-            }
-        }
-    }
-}
-
 var paging = new function () {
 
     this.defaultPageLen = 10;
@@ -1373,20 +1200,30 @@ var paging = new function () {
     }
 };
 
-var queryForm = new function () {
-    var logger = log.getLogger("queryForm");
-    this.logger = logger;
+/**
+ * Extend the functionality of the pure html query panel.
+ *
+ * @module queryForm
+ * @global
+ */
+var queryForm = function () {
 
-    this.init = function (form) {
-        this.restoreLastQuery(form, () => window.sessionStorage.lastQuery);
-        this.bindOnClick(form, (set) => {
+    const init = function (form) {
+        queryForm.restoreLastQuery(form, () => window.sessionStorage.lastQuery);
+        queryForm.bindOnClick(form, (set) => {
             window.sessionStorage.lastQuery = set;
         });
-        const BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS = "PathObject, MapObject";
-        this.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`);
+        const BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS = "";
+        queryForm.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`);
     };
 
-    this.role_name_facet_select = undefined;
+    const logger = log.getLogger("queryForm");
+    var role_name_facet_select = undefined;
+
+    const _isCql = function (query) {
+       query = query.toUpperCase().trim();
+       return (query.startsWith("FIND") || query.startsWith("COUNT") || query.startsWith("SELECT"));
+    }
 
     /**
      * Initialize the free search to generate search queries which search only
@@ -1404,13 +1241,14 @@ var queryForm = new function () {
      * "FILE 'numpy array', 'Plant Experiemnt', 'quotes_not_necessary'".
      * Otherwise, the server will throw a syntax error.
      *
+     * @function initFreeSearch
      * @param {HTMLElement} form - the form which will be initialized for the
      *     free search.
      * @param {string} options - comma-separated list of options.
      * @return {boolean} - true if the initialization was successful, false
      *     otherwise.
      */
-    this.initFreeSearch = function (form, options) {
+    const initFreeSearch = function (form, options) {
         const textArea = $(form).find(".caosdb-f-query-textarea");
         logger.trace("initFreeSearch", form, textArea, options);
         if (textArea.length > 0 && options && options != "") {
@@ -1422,13 +1260,13 @@ var queryForm = new function () {
             const selected = window.localStorage["role_name_facet_option"];
             const select = $(`<select class="btn btn-secondary"/>`);
             for (let option of options.split(",")) {
-              select.append(`<option ${selected === option ? "selected" : ""}>${option}</option>`);
+              select.append(`<option ${selected === option.trim() ? "selected" : ""}>${option}</option>`);
             }
             $(form).find(".input-group").prepend(select);
-            this.role_name_facet_select = select[0];
+            role_name_facet_select = select[0];
 
             const switchFreeSearch = (text_area) => {
-                if(this._isCql(text_area.value)) {
+                if(_isCql(text_area.value)) {
                     select.hide();
                     $(text_area).css({"border-top-left-radius": "0.375rem", "border-bottom-left-radius": "0.375rem"});
                 } else {
@@ -1444,11 +1282,14 @@ var queryForm = new function () {
             });
             return true;
         }
-        this.role_name_facet_select = undefined;
+        role_name_facet_select = undefined;
         return false;
     }
 
-    this.restoreLastQuery = function (form, getter) {
+    /**
+     * @function restoreLastQuery
+     */
+    const restoreLastQuery = function (form, getter) {
         if (form == null) {
             throw new Error("form was null");
         }
@@ -1458,10 +1299,11 @@ var queryForm = new function () {
     };
 
     /**
-     * @value {string} query - the query string.
+     * @function redirect
+     * @param {string} query - the query string.
      * @param {string} paging - the paging string, e.g. 0L10.
      */
-    this.redirect = function (query, paging) {
+    const redirect = function (query, paging) {
         var pagingparam = ""
         if (paging && paging.length > 0) {
             pagingparam = "P=" + paging + "&";
@@ -1472,10 +1314,11 @@ var queryForm = new function () {
     /**
      * Read out the selector for the role/name facet of the query. Return
      * "RECORD" if the selector is disabled.
+     * @function getRoleNameFacet
      */
-    this.getRoleNameFacet = function () {
-        if (this.role_name_facet_select) {
-            const result = this.role_name_facet_select.value;
+    const getRoleNameFacet = function () {
+        if (role_name_facet_select) {
+            const result = role_name_facet_select.value;
             window.localStorage["role_name_facet_option"] = result;
             return result;
         }
@@ -1492,20 +1335,30 @@ var queryForm = new function () {
      * enclosing quotation marks are being stripped. Currently no support for
      * escape sequences for quotation marks.
      *
+     * @function splitSearchTerms
      * @param {string} query - complete query string.
      * @return {string[]} array of the search terms.
      */
-    this.splitSearchTerms = function (query) {
+    const splitSearchTerms = function (query) {
         // add empty space at the end, so every matching group ends with it -> easier regex. Also, undefined is filtered out
         return Array.from((query + " ").matchAll(_splitSearchTermsPattern), (m) => m[1] || m[2] || m[3]).filter((word) => word);
     }
 
-    this._isCql = function (query) {
-       query = query.toUpperCase().trim();
-       return (query.startsWith("FIND") || query.startsWith("COUNT") || query.startsWith("SELECT"));
+    /**
+     * Is the query a SELECT field,... FROM entity query?
+     *
+     * @function isSelectQuery
+     * @param {HTMLElement} query, the query to be tested.
+     * @return {Boolean}
+     */
+    const isSelectQuery = function (query) {
+        return query.toUpperCase().startsWith("SELECT");
     }
 
-    this.bindOnClick = function (form, setter) {
+    /**
+     * @function bindOnClick
+     */
+    const bindOnClick = function (form, setter) {
         if (setter == null || typeof (setter) !== 'function' || setter.length !== 1) {
             throw new Error("setter must be a function with one param");
         }
@@ -1522,13 +1375,13 @@ var queryForm = new function () {
             if (typeof value == "undefined" || value.length == 0) {
                 return;
             }
-            if (!queryForm._isCql(value)) {
+            if (!_isCql(value)) {
                 // split words in query field at space and create query fragments
-                var words = queryForm.splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`);
+                var words = splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`);
                 if (!words.length) {
                     return false;
                 }
-                const e = queryForm.getRoleNameFacet();
+                const e = getRoleNameFacet();
                 const query_string = `FIND ${e} WHICH HAS ` + words.join(" AND ");
                 queryField.value = query_string;
 
@@ -1538,7 +1391,7 @@ var queryForm = new function () {
             setter(queryField.value);
 
             var paging = "";
-            if (form.P && !queryForm.isSelectQuery(queryField.value)) {
+            if (form.P && !isSelectQuery(queryField.value)) {
                 paging = form.P.value
             }
 
@@ -1567,31 +1420,28 @@ var queryForm = new function () {
     };
 
     /**
-     * Is the query a SELECT field,... FROM entity query?
-     *
-     * @param {HTMLElement} query, the query to be tested.
-     * @return {Boolean}
+     * @function getRoleNameFacetSelect
      */
-    this.isSelectQuery = function (query) {
-        return query.toUpperCase().startsWith("SELECT");
-    }
 
-    /**
-     * Remove the (hidden) paging input from the query form.
-     * The form is changed in-place without copying it.
-     *
-     * @param {HTMLElement} form, the query form.
-     * @return {HTMLElement} the form without the paging input.
-     */
-    this.removePagingField = function (form) {
-        $(form.P).remove();
-        return form;
+    return {
+        init: init,
+        initFreeSearch: initFreeSearch,
+        isSelectQuery: isSelectQuery,
+        restoreLastQuery: restoreLastQuery,
+        redirect: redirect,
+        bindOnClick: bindOnClick,
+        splitSearchTerms: splitSearchTerms,
+        getRoleNameFacet: getRoleNameFacet,
+        getRoleNameFacetSelect: () => role_name_facet_select,
     }
-};
+}();
 
 
-/**
+/*
  * Small module containing only a converter from markdown to html.
+ *
+ * @module markdown
+ * @global
  */
 this.markdown = new function () {
     this.dependencies = ["showdown", "caosdb_utils"];
@@ -1625,6 +1475,10 @@ this.markdown = new function () {
     });
 }
 
+/**
+ * @module hintMessages
+ * @global
+ */
 var hintMessages = new function () {
     this.init = function () {
         for (var entity of $('.caosdb-entity-panel')) {
@@ -2079,7 +1933,6 @@ function initOnDocumentReady() {
     }
     caosdb_modules.init();
     navbar.init();
-    version_history.init();
 
     if ("${BUILD_MODULE_USER_MANAGEMENT}" == "ENABLED") {
         caosdb_modules.register(user_management);
@@ -2102,6 +1955,7 @@ function initOnDocumentReady() {
  *
  * Singleton which is globally available under caosdb_modules.
  *
+ * @class _CaosDBModules
  * @property {boolean} auto_init - if modules are initialized automatically
  *   after beeing registered, or when `this.init_all()` is being called.
  */
diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js
index cfb86e459acf6f38e5672918df81a42b0eb1ea2a..f838e35f5a8e70e0e1032a89e09885444c3fb250 100644
--- a/test/core/js/modules/webcaosdb.js.js
+++ b/test/core/js/modules/webcaosdb.js.js
@@ -1115,16 +1115,6 @@ QUnit.module("webcaosdb.js - queryForm", {
     }
 });
 
-QUnit.test("removePagingField", function (assert) {
-    assert.ok(queryForm.removePagingField, "function available.");
-    assert.throws(() => queryForm.removePagingField(), "null param throws.");
-    let form = $('<form><input name="P"></form>')[0];
-    assert.ok(form.P, "before: paging available.");
-    queryForm.removePagingField(form);
-    assert.notOk(form.P, "after: paging removed.");
-
-});
-
 QUnit.test("isSelectQuery", function (assert) {
     assert.ok(queryForm.isSelectQuery, "function available.");
     assert.throws(() => queryForm.isSelectQuery(), "null param throws.");
@@ -1263,19 +1253,21 @@ QUnit.test("initFreeSearch", function (assert) {
 
     // without initialization
     assert.ok(queryForm.initFreeSearch, "available");
-    assert.notOk(queryForm.role_name_facet_select, "role_name_facet_select is undefined 1");
+    assert.notOk(queryForm.getRoleNameFacetSelect(), "role_name_facet_select is undefined 1");
     assert.notOk(queryForm.initFreeSearch(), "not initialized");
 
-    assert.notOk(queryForm.role_name_facet_select, "role_name_facet_select is undefined 2");
+    assert.notOk(queryForm.getRoleNameFacetSelect(), "role_name_facet_select is undefined 2");
 
     assert.equal(form.find("select").length, 0, "no select present");
     assert.equal(queryForm.getRoleNameFacet(), "RECORD");
 
 
-    // after initialization
+    window.localStorage["role_name_facet_select"] = "Sample";
     assert.ok(queryForm.initFreeSearch(form, "Person, Experiment, Sample"), "initialized");
-    assert.ok(queryForm.role_name_facet_select, "role_name_facet_select is defined");
-    assert.equal(queryForm.role_name_facet_select.tagName, "SELECT", "role_name_facet_select is SELECT");
+
+    // after initialization
+    assert.ok(queryForm.getRoleNameFacetSelect(), "role_name_facet_select is defined");
+    assert.equal(queryForm.getRoleNameFacetSelect().tagName, "SELECT", "role_name_facet_select is SELECT");
     assert.equal(form.find("select").length, 1, "select is present");
 
     assert.equal(queryForm.getRoleNameFacet(), "Person");