diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index 9b8f8095befc01f3f9fba610ee61efbaa8c24fc5..f188535c2e33448dc46297f2333050d2b480f96f 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -54,6 +54,7 @@ BUILD_MODULE_EXT_ANNOTATION=ENABLED
 BUILD_MODULE_EXT_COSMETICS_LINKIFY=DISABLED
 BUILD_MODULE_EXT_QRCODE=ENABLED
 BUILD_MODULE_SHOW_ID_IN_LABEL=DISABLED
+BUILD_MODULE_LEGACY_QUERY_FORM=DISABLED
 
 BUILD_MODULE_USER_MANAGEMENT=ENABLED
 BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB
@@ -168,6 +169,7 @@ MODULE_DEPENDENCIES=(
     loglevel.js
     plotly.js
     webcaosdb.js
+    query_form.js
     pako.js
     utif.js
     ext_version_history.js
diff --git a/src/core/js/query_form.js b/src/core/js/query_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..89056c13dd9f82f8fa9781d5a1676e9616b7714c
--- /dev/null
+++ b/src/core/js/query_form.js
@@ -0,0 +1,245 @@
+/**
+ * Extend the functionality of the pure html query panel.
+ *
+ * @module queryForm
+ * @global
+ */
+var queryForm = function () {
+
+    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 = "";
+        queryForm.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`);
+    };
+
+    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
+     * 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.
+     *
+     * @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.
+     */
+    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 != "") {
+            const lastQuery = window.localStorage["freeTextQuery:" + textArea[0].value];
+            if (lastQuery) {
+                textArea[0].value = lastQuery;
+            }
+
+            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.trim() ? "selected" : ""}>${option}</option>`);
+            }
+            $(form).find(".input-group").prepend(select);
+            role_name_facet_select = select[0];
+
+            const switchFreeSearch = (text_area) => {
+                if(_isCql(text_area.value)) {
+                    select.hide();
+                    $(text_area).css({"border-top-left-radius": "0.375rem", "border-bottom-left-radius": "0.375rem"});
+                } else {
+                    select.show();
+                    $(text_area).css({});
+                }
+            }
+
+            switchFreeSearch(textArea[0]);
+
+            textArea.on("keydown", (e) => {
+                switchFreeSearch(e.target);
+            });
+            return true;
+        }
+        role_name_facet_select = undefined;
+        return false;
+    }
+
+    /**
+     * @function restoreLastQuery
+     */
+    const restoreLastQuery = function (form, getter) {
+        if (form == null) {
+            throw new Error("form was null");
+        }
+        if (getter()) {
+            form.query.value = getter();
+        }
+    };
+
+    /**
+     * @function redirect
+     * @param {string} query - the query string.
+     * @param {string} paging - the paging string, e.g. 0L10.
+     */
+    const redirect = function (query, paging) {
+        var pagingparam = ""
+        if (paging && paging.length > 0) {
+            pagingparam = "P=" + paging + "&";
+        }
+        location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query;
+    }
+
+    /**
+     * Read out the selector for the role/name facet of the query. Return
+     * "RECORD" if the selector is disabled.
+     * @function getRoleNameFacet
+     */
+    const getRoleNameFacet = function () {
+        if (role_name_facet_select) {
+            const result = role_name_facet_select.value;
+            window.localStorage["role_name_facet_option"] = result;
+            return result;
+        }
+        return "RECORD";
+    }
+
+    const _splitSearchTermsPattern = /"(?<dq>[^"]*)" |'(?<sq>[^']*)' |(?<nq>[^ ]+)/g;
+
+    /**
+     * Split a query string into single terms.
+     *
+     * Terms are separated by white spaces. Terms which contain white spaces
+     * which are to be preserved must be enclosed in " or ' quotes. The
+     * 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.
+     */
+    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);
+    }
+
+    /**
+     * 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");
+    }
+
+    /**
+     * @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");
+        }
+
+        /*
+          Here a submit handler is created that is attached to both the form submit handler
+          and the click handler of the button.
+          See https://developer.mozilla.org/en-US/docs/Web/Events/submit why this is necessary.
+          */
+        var submithandler = function () {
+            // store current query
+            var queryField = form.query;
+            var value = queryField.value;
+            if (typeof value == "undefined" || value.length == 0) {
+                return;
+            }
+            if (!_isCql(value)) {
+                // split words in query field at space and create query fragments
+                var words = splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`);
+                if (!words.length) {
+                    return false;
+                }
+                const e = getRoleNameFacet();
+                const query_string = `FIND ${e} WHICH HAS ` + words.join(" AND ");
+                queryField.value = query_string;
+
+                // store original value of the text field
+                window.localStorage["freeTextQuery:" + query_string] = value;
+            }
+            setter(queryField.value);
+
+            var paging = "";
+            if (form.P && !isSelectQuery(queryField.value)) {
+                paging = form.P.value
+            }
+
+            queryForm.redirect(queryField.value.trim(), paging);
+
+            var btn =  $(form).find(".caosdb-search-btn");
+            btn.html(`<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>`);
+            btn.prop("disabled", true);
+
+            var textField =  $(form).find(".caosdb-f-query-textarea");
+            textField.blur();
+            textField.prop("disabled", true);
+        };
+
+        // handler for the form
+        form.onsubmit = function (e) {
+            e.preventDefault();
+            submithandler();
+            return false;
+        };
+
+        // same handler for the button
+        form.getElementsByClassName("caosdb-search-btn")[0].onclick = function () {
+            submithandler();
+        };
+    };
+
+    /**
+     * @function getRoleNameFacetSelect
+     */
+
+    return {
+        init: init,
+        initFreeSearch: initFreeSearch,
+        isSelectQuery: isSelectQuery,
+        restoreLastQuery: restoreLastQuery,
+        redirect: redirect,
+        bindOnClick: bindOnClick,
+        splitSearchTerms: splitSearchTerms,
+        getRoleNameFacet: getRoleNameFacet,
+        getRoleNameFacetSelect: () => role_name_facet_select,
+    }
+}();
+
+
+$(document).ready(function () {
+		if (`${BUILD_MODULE_LEGACY_QUERY_FORM}` == "ENABLED") {
+			var form = document.getElementById("caosdb-query-form");
+			if (form != null) {
+					queryForm.init(form);
+			}
+		}
+});
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index bfe0eb35c8e72e6493ad273ae0ed5a727019c628..18387779cfe8a9ee2ac34a1b3e69fa3f1127689f 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -1200,242 +1200,6 @@ var paging = new function () {
     }
 };
 
-/**
- * Extend the functionality of the pure html query panel.
- *
- * @module queryForm
- * @global
- */
-var queryForm = function () {
-
-    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 = "";
-        queryForm.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`);
-    };
-
-    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
-     * 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.
-     *
-     * @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.
-     */
-    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 != "") {
-            const lastQuery = window.localStorage["freeTextQuery:" + textArea[0].value];
-            if (lastQuery) {
-                textArea[0].value = lastQuery;
-            }
-
-            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.trim() ? "selected" : ""}>${option}</option>`);
-            }
-            $(form).find(".input-group").prepend(select);
-            role_name_facet_select = select[0];
-
-            const switchFreeSearch = (text_area) => {
-                if(_isCql(text_area.value)) {
-                    select.hide();
-                    $(text_area).css({"border-top-left-radius": "0.375rem", "border-bottom-left-radius": "0.375rem"});
-                } else {
-                    select.show();
-                    $(text_area).css({});
-                }
-            }
-
-            switchFreeSearch(textArea[0]);
-
-            textArea.on("keydown", (e) => {
-                switchFreeSearch(e.target);
-            });
-            return true;
-        }
-        role_name_facet_select = undefined;
-        return false;
-    }
-
-    /**
-     * @function restoreLastQuery
-     */
-    const restoreLastQuery = function (form, getter) {
-        if (form == null) {
-            throw new Error("form was null");
-        }
-        if (getter()) {
-            form.query.value = getter();
-        }
-    };
-
-    /**
-     * @function redirect
-     * @param {string} query - the query string.
-     * @param {string} paging - the paging string, e.g. 0L10.
-     */
-    const redirect = function (query, paging) {
-        var pagingparam = ""
-        if (paging && paging.length > 0) {
-            pagingparam = "P=" + paging + "&";
-        }
-        location.href = connection.getBasePath() + "Entity/?" + pagingparam + "query=" + query;
-    }
-
-    /**
-     * Read out the selector for the role/name facet of the query. Return
-     * "RECORD" if the selector is disabled.
-     * @function getRoleNameFacet
-     */
-    const getRoleNameFacet = function () {
-        if (role_name_facet_select) {
-            const result = role_name_facet_select.value;
-            window.localStorage["role_name_facet_option"] = result;
-            return result;
-        }
-        return "RECORD";
-    }
-
-    const _splitSearchTermsPattern = /"(?<dq>[^"]*)" |'(?<sq>[^']*)' |(?<nq>[^ ]+)/g;
-
-    /**
-     * Split a query string into single terms.
-     *
-     * Terms are separated by white spaces. Terms which contain white spaces
-     * which are to be preserved must be enclosed in " or ' quotes. The
-     * 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.
-     */
-    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);
-    }
-
-    /**
-     * 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");
-    }
-
-    /**
-     * @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");
-        }
-
-        /*
-          Here a submit handler is created that is attached to both the form submit handler
-          and the click handler of the button.
-          See https://developer.mozilla.org/en-US/docs/Web/Events/submit why this is necessary.
-          */
-        var submithandler = function () {
-            // store current query
-            var queryField = form.query;
-            var value = queryField.value;
-            if (typeof value == "undefined" || value.length == 0) {
-                return;
-            }
-            if (!_isCql(value)) {
-                // split words in query field at space and create query fragments
-                var words = splitSearchTerms(queryField.value).map(word => `A PROPERTY LIKE '*${word.replaceAll("'", `\\'`)}*'`);
-                if (!words.length) {
-                    return false;
-                }
-                const e = getRoleNameFacet();
-                const query_string = `FIND ${e} WHICH HAS ` + words.join(" AND ");
-                queryField.value = query_string;
-
-                // store original value of the text field
-                window.localStorage["freeTextQuery:" + query_string] = value;
-            }
-            setter(queryField.value);
-
-            var paging = "";
-            if (form.P && !isSelectQuery(queryField.value)) {
-                paging = form.P.value
-            }
-
-            queryForm.redirect(queryField.value.trim(), paging);
-
-            var btn =  $(form).find(".caosdb-search-btn");
-            btn.html(`<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>`);
-            btn.prop("disabled", true);
-
-            var textField =  $(form).find(".caosdb-f-query-textarea");
-            textField.blur();
-            textField.prop("disabled", true);
-        };
-
-        // handler for the form
-        form.onsubmit = function (e) {
-            e.preventDefault();
-            submithandler();
-            return false;
-        };
-
-        // same handler for the button
-        form.getElementsByClassName("caosdb-search-btn")[0].onclick = function () {
-            submithandler();
-        };
-    };
-
-    /**
-     * @function getRoleNameFacetSelect
-     */
-
-    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.
@@ -1912,12 +1676,6 @@ function initOnDocumentReady() {
     paging.init();
     hintMessages.init();
 
-    // init query form
-    var form = document.getElementById("caosdb-query-form");
-    if (form != null) {
-        queryForm.init(form);
-    }
-
     // show image 100% width
     $(".entity-image-preview").click(function () {
         $(this).css('max-width', '100%');