diff --git a/src/core/js/query_form.js b/src/core/js/query_form.js
new file mode 100644
index 0000000000000000000000000000000000000000..6775fee677660b32387dcd82346753eb98c94da0
--- /dev/null
+++ b/src/core/js/query_form.js
@@ -0,0 +1,270 @@
+/**
+ * Extend the functionality of the pure html query panel.
+ *
+ * Deprecated. This is to be replaced by the query form of caosdb-webui-core-components.
+ *
+ * @deprecated
+ * @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=" + encodeURIComponent(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);
+    }
+  }
+});