diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 79f7cbfeb42794625970722f3d4c52b274969092..b9a09010bb3818f2f551e3bcd1a0b985ce08bcb4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -95,8 +95,7 @@ pages_prepare: &pages_prepare
     refs:
       - /^release-.*$/i
   script:
-    - npm install jsdoc
-    - npm install jsdoc-sphinx
+    - npm install jsdoc jsdoc-sphinx
     - echo "Deploying"
     - make doc
     - rm -r public || true ; cp -r build/doc/html public
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c5eb8d6970bf588fb5542e4aeb622b43e25de95..02295dbb7ee91a930a15b7ce318a167358d47383 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.10.0] - 2022-12-19
+(Florian Spreckelsen)
+
+### Added
+
+* [#191](https://gitlab.com/caosdb/caosdb-webui/-/issues/191) - "Configure the
+  RecordType which is searched by the simple search."
+  A list of entity roles and names can be specified via the newly added
+  `BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS` build variable. See the docstring
+  of `queryForm.initFreeSearch` for more information.
+* [#188](https://gitlab.com/caosdb/caosdb-webui/-/issues/188) Properties can be
+  hidden/shown only for certain users/roles.
+* [#189](https://gitlab.com/caosdb/caosdb-webui/-/issues/189) The order in which
+  the properties of (Records of) a RecordType are displayed can be configured.
+
+### Changed (for changes in existing functionality)
+
+* Version bump of caosdb_map module (0.5.0):
+  * Added configurable entityLayers
+  * Changed name of the icon option to icon_options, because that name better
+    distiguished the options from the result icon object.
+* New behavior of the "Enter" key in the query input text field: When pressed
+  when the autocompletion drop-down is open, the enter key selects an option
+  from the autocompletion (this is as it was before). But when the "Enter" key
+  is pressed, when the autocompletion drop-down is not open, the query is being
+  submitted.
+
 ## [0.9.0] - 2022-11-02
 (Florian Spreckelsen)
 
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 9c6f7e4c7880c488ec116662c2ed49e2bd85d8a1..49d5f792e8cd17bf6ccf4c9402686e0c557919ce 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -1,4 +1,4 @@
-* CaosDB Server 0.8.0
+* CaosDB Server 0.8.1
 * Make 4.2.0
 
 # Java Script Libraries (included in this repository)
diff --git a/README.md b/README.md
index dca645c4244573ae31c7e0072208b53a47cec1d2..d6c53f492b2f6080d423aaf6fbee25e5504ca794 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ when creating the merge request. This allows our team to work with you on your r
 - If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-webui/),
 the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
 However, you can also create an issue for it.
-- You can also contact us at **info (AT) caosdb.de**  and join the
+- You can also contact us at **info (AT) caosdb.org**  and join the
   CaosDB community on
   [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org).
 
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index cd57b632e2ea2b9ab005c7185a309a9594f123ff..9b8f8095befc01f3f9fba610ee61efbaa8c24fc5 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -62,6 +62,7 @@ BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED
 BUILD_EXT_REFERENCES_CUSTOM_RESOLVER=caosdb_default_person_reference
 
 BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT=DISABLED
+BUILD_MODULE_EXT_PROPERTY_DISPLAY=DISABLED
 
 ##############################################################################
 # Navbar properties
@@ -75,6 +76,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 +143,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 +151,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 +170,7 @@ MODULE_DEPENDENCIES=(
     webcaosdb.js
     pako.js
     utif.js
+    ext_version_history.js
     caosdb.js
     form_elements.js
     ext_autocomplete.js
@@ -179,4 +204,5 @@ MODULE_DEPENDENCIES=(
     ckeditor.js
     ext_editmode_wysiwyg_text.js
     reference_resolver/caosdb_default_person.js
+    ext_prop_display.js
 )
diff --git a/conf/core/json/ext_prop_display.json b/conf/core/json/ext_prop_display.json
new file mode 100644
index 0000000000000000000000000000000000000000..0967ef424bce6791893e9a57bb952f80fd536e93
--- /dev/null
+++ b/conf/core/json/ext_prop_display.json
@@ -0,0 +1 @@
+{}
diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css
index 1c8f50be049d308efcac25e41d705b08d96d21e6..248f3444962a839193016a42dc4e9f730ba7d448 100644
--- a/src/core/css/webcaosdb.css
+++ b/src/core/css/webcaosdb.css
@@ -381,6 +381,14 @@ h5 {
     margin-right: 0px;
 }
 
+body[data-hidden-properties="true"] .caosdb-v-hidden-property {
+    display: None;
+}
+
+body[data-hidden-properties="true"] .caosdb-v-entity-being-edited .caosdb-v-hidden-property {
+    display: unset;
+}
+
 .caosdb-v-edit-drag {
     padding: 5px;
 }
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index 63b28b08a6c07d4c5b00795f5e9f87d1965e4031..ba478dcd90cc9f6899e678f543d1c928129e1df7 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -1359,6 +1359,7 @@ var edit_mode = new function () {
         doc.firstElementChild.appendChild(newrecord.firstElementChild);
         // TODO I dunno whats wrong here: xml -> str -> xml ???
         var x = await transformation.transformEntities(str2xml(xml2str(doc)));
+        $(x[0]).addClass("caosdb-v-entity-being-edited");
         return x[0];
     }
 
@@ -1618,6 +1619,8 @@ var edit_mode = new function () {
             app.entity = $(entity).clone(true)[0];
             // remove preview stuff
             $(app.entity).find(".caosdb-preview-container").remove();
+            // add class for styling the entity that's being edited
+            $(app.entity).addClass("caosdb-v-entity-being-edited");
             edit_mode.smooth_replace(app.old, app.entity);
 
             edit_mode.add_save_button(app.entity, () => app.update(app.entity));
@@ -1897,7 +1900,9 @@ var edit_mode = new function () {
 
     this.create_new_entity = async function (role) {
         var empty_entity = str2xml('<Response><' + role + '/></Response>');
-        return (await transformation.transformEntities(empty_entity))[0];
+        var ent_element = await transformation.transformEntities(empty_entity);
+        $(ent_element).addClass("caosdb-v-entity-being-edited");
+        return ent_element[0];
     }
 
     this.remove_cancel_button = function (entity) {
@@ -2097,4 +2102,4 @@ var edit_mode = new function () {
  */
 $(document).ready(function () {
     edit_mode.init();
-});
\ No newline at end of file
+});
diff --git a/src/core/js/ext_autocomplete.js b/src/core/js/ext_autocomplete.js
index 932a9466a83f17594894ad389f6535bcd6caffa2..02ae1199494ae3634afa16ad417b886f19068ee0 100644
--- a/src/core/js/ext_autocomplete.js
+++ b/src/core/js/ext_autocomplete.js
@@ -179,6 +179,19 @@ var ext_autocomplete = new function () {
      */
     this.switch_on_completion = function () {
         var field = $("#caosdb-query-textarea");
+
+        // submit on "enter" when the drop-down menu is not visible.
+        field.on("keydown", (e) => {
+            if(e.originalEvent.keyCode == 13) { // Enter
+                if($(e.target).siblings(".bootstrap-autocomplete.show").length == 0) {
+                    $(".caosdb-search-btn").click();
+                } else {
+                    // don't submit - the user is just selecting something
+                    e.originalEvent.preventDefault();
+                }
+            }
+        });
+
         field.attr("autocomplete", "off");
         field.toggleClass("basicAutoComplete", true);
         field.autoComplete({
diff --git a/src/core/js/ext_map.js b/src/core/js/ext_map.js
index bac14e71f68561df6982f94fca9c5e9f02e8c90e..6dd9b963d4caa2887f4e680285e7b61a47e97b20 100644
--- a/src/core/js/ext_map.js
+++ b/src/core/js/ext_map.js
@@ -25,7 +25,7 @@
 
 /**
  * @module caosdb_map
- * @version 0.4.1
+ * @version 0.5.0
  *
  * For displaying a geographical map which shows entities at their associated
  * geolocation.
@@ -43,7 +43,7 @@
 var caosdb_map = new function () {
 
     var logger = log.getLogger("caosdb_map");
-    this.version = "0.4.1";
+    this.version = "0.5.0";
     this.dependencies = ["log", {
         "L": ["latlngGraticule", "Proj"]
     }, "navbar", "caosdb_utils"];
@@ -76,6 +76,8 @@ var caosdb_map = new function () {
      * @property {DataModelConfig} datamodel - the data model for the
      *     display of entities in the map (also used by the query generator).
      * @property {SelectConfig} select - config for the query generator.
+     * @property {Object.<string, EntityLayerConfig>} entityLayers -
+     *     configuration for the entity layer which are to be shown on the map.
      */
 
     /**
@@ -382,14 +384,23 @@ var caosdb_map = new function () {
      */
 
     /**
+     * Configuration of the entity layers which are to be shown on the map.
+     *
      * @typedef {object} EntityLayerConfig
-     * @property {string} id
-     * @property {string} name
-     * @property {string} description
-     * @property {DivIcon_options} icon_options
+     * @property {string} id - the id of the entity layer which is used
+     *     internally.
+     * @property {string} name - a short name which is shown in the entity
+     *     layers menu.
+     * @property {string} description - a short description which is shown as
+     *     hover-over text in the entity layers menu.
+     * @property {DivIcon_options} icon_tions - leaflet options for the icon
+     *     (aka the marker) which is shown on the map. These options are
+     *     espcially useful to style the icons (color etc).
      * @property {number} zIndexOffset
-     * @property {mapEntityGetter} get_entities
-     * @property {mapEntityPopupGenerator} make_popup
+     * @property {mapEntityGetter} get_entities - returns the entities which
+     *     are to be shown on the map.
+     * @property {mapEntityPopupGenerator} make_popup - returns the popup which
+     *     is to be shown when a user clicks on the map marker.
      */
 
     /**
@@ -647,13 +658,13 @@ var caosdb_map = new function () {
      * "all_map_entities" which shows all entities in the database with
      * coordinates.
      *
-     * @type {EntityLayerConfig[]}
+     * @type {Object.<string, EntityLayerConfig>}
      */
     this._default_entity_layer_config = {
         "current_page_entities": {
             "name": "Entities on the current page.",
             "description": "Show all entities on the current page.",
-            "icon": {
+            "icon_options": {
                 html: '<i class="bi-geo-alt-fill"  style="font-size: 20px; color: #00F;"></i>',
                 iconAnchor: [10, 19],
                 className: "",
@@ -675,7 +686,7 @@ var caosdb_map = new function () {
         "all_map_entities": {
             "name": "All entities",
             "description": "Show all entities with coordinates.",
-            "icon": {
+            "icon_options": {
                 html: '<i class="bi-geo-alt-fill"  style="font-size: 20px; color: #F00;"></i>',
                 iconAnchor: [10, 19],
                 className: "",
@@ -973,9 +984,10 @@ var caosdb_map = new function () {
         this._reload_layers = function () {
             caosdb_map._show_load_info()
             const promises = []
+            const entity_layer_config = $.extend(true, {}, caosdb_map._default_entity_layer_config, caosdb_map.config["entityLayers"]);
             for (const layer of caosdb_map.layers) {
                 promises.push(caosdb_map._fill_layer(layer.layer_group,
-                    caosdb_map._default_entity_layer_config[layer.id]));
+                    entity_layer_config[layer.id]));
             }
             Promise.all(promises).then((val) => {
                 caosdb_map._hide_load_info()
@@ -1067,14 +1079,16 @@ var caosdb_map = new function () {
                         view_config);
 
                     // init entity layers
-                    this.layers = this.init_entity_layers(this._default_entity_layer_config);
+                    const entity_layer_config = $.extend(true, {}, this._default_entity_layer_config, config["entityLayers"]);
+                    caosdb_map.entityLayers = entity_layer_config;
+                    this.layers = this.init_entity_layers(entity_layer_config);
                     var layerControl = L.control.layers();
 
                     const promises = []
                     for (const layer of this.layers) {
 
                         promises.push(caosdb_map._fill_layer(layer.layer_group,
-                            this._default_entity_layer_config[layer.id]));
+                            entity_layer_config[layer.id]));
                         layerControl.addOverlay(layer.layer_group, layer.chooser_html.outerHTML);
                         layer.layer_group.addTo(this._map);
                     }
@@ -1139,7 +1153,7 @@ var caosdb_map = new function () {
          */
 
         /**
-         * @param {EntityLayerConfig[]} configs
+         * @param {Object.<string, EntityLayerConfig>} config
          * @returns {_EntityLayer[]}
          */
         this.init_entity_layers = function (configs) {
@@ -1165,7 +1179,7 @@ var caosdb_map = new function () {
             layer_group.entities = entities;
             var markers = caosdb_map.create_entity_markers(
                 entities, config.datamodel, config.make_popup,
-                config.zIndexOffset, config.icon);
+                config.zIndexOffset, config.icon_options);
 
             for (const marker of markers) {
                 layer_group.addLayer(marker);
@@ -1202,7 +1216,7 @@ var caosdb_map = new function () {
         this.make_layer_chooser_html = function (config) {
             return $('<span/>')
                 .attr("title", config.description)
-                .append(config.icon.html)
+                .append(config.icon_options.html)
                 .append(config.name)[0];
         }
 
diff --git a/src/core/js/ext_prop_display.js b/src/core/js/ext_prop_display.js
new file mode 100644
index 0000000000000000000000000000000000000000..f74d700078b2b8ab69174ab05c9e95c6df33b109
--- /dev/null
+++ b/src/core/js/ext_prop_display.js
@@ -0,0 +1,237 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@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/>.
+ *
+ */
+'use strict';
+
+/**
+ * @requires jQuery (library)
+ * @requires log (singleton from loglevel library)
+ * @requires load_config (function from webcaosdb.js)
+ */
+var prop_display = new function ($, edit_mode, getEntityName, getEntityRole, getPropertyElements, getPropertyName, getUserName, getUserRoles, logger, load_config, query) {
+
+    /**
+     * Return the property-display config file; `ext_prop_display.json` by
+     * default.
+     *
+     * @param {string} resource - file name of the config file
+     */
+    this.load_config = async function (resource) {
+
+        var conf = {};
+        try {
+            resource = resource || "ext_prop_display.json";
+            conf = await load_config(resource);
+        } catch (err) {
+            logger.error(err);
+        }
+
+        return conf;
+    }
+
+    this.getEntitiesInView = function () {
+        // Use all entities, both in entity panel and in preview.
+        return $(".caosdb-entity-panel,.caosdb-entity-preview");
+    }
+
+    this.displayProperties = function (entities, conf, allTypes, userName, userRoles) {
+
+        for (let ent of entities) {
+            let parents = getParents(ent).map(par => par.name);
+            let properties = getPropertyElements(ent);
+            // either the entity has matching parents OR it is the actual
+            // RecordType for which a rule is written.
+            if (parents.some(par => allTypes.allTypesOrChildren.includes(par)) ||
+                (getEntityRole(ent) == "RecordType" && allTypes.allTypesOrChildren.includes(getEntityName(ent)))) {
+                // we know that there is at least one rule for this type (it is
+                // in `allTypes.allTypesOrChildren`), but we don't know which tp
+                // apply yet.
+                for (let typeName of Object.keys(conf)) {
+                    let typeConf = conf[typeName];
+                    let allNames = allTypes.typesWithChildren[typeName];
+                    // only change the display something if there is a match in
+                    // at least one parent type
+                    if (parents.some(par => allNames.includes(par)) ||
+                        (getEntityRole(ent) == "RecordType" && allNames.includes(getEntityName(ent)))) {
+                        // first sort the properties
+                        this._sortProperties(ent, properties, typeConf);
+                        properties.forEach((prop, index) => {
+                            if (this._hide_property(getPropertyName(prop), userName, userRoles, typeConf)) {
+                                // Should be hidden by default but better safe than sorry
+                                $(prop).addClass("caosdb-v-hidden-property").removeClass("caosdb-v-show-property");
+                            } else {
+                                // show this property
+                                $(prop).addClass("caosdb-v-show-property").removeClass("caosdb-v-hidden-property");
+                            }
+                        });
+                    }
+                }
+            } else {
+                // no rules for this RecordType, so show all properties
+                properties.forEach((prop, index) => $(prop).addClass("caosdb-v-show-property").removeClass("caosdb-v-hidden-property"));
+            }
+        }
+    }
+
+    this._sortProperties = function (entity, properties, conf) {
+        if (conf.order == undefined || conf.order.length == 0) {
+            return;
+        }
+        properties.sort(function (a, b) {
+            let confIndexA = conf.order.indexOf(getPropertyName(a));
+            let confIndexB = conf.order.indexOf(getPropertyName(b));
+            if (confIndexA < 0 && confIndexB < 0) {
+                // both are not part of order list
+                return 0;
+            }
+            if (confIndexA < 0) {
+                // only b is part of order list , so it is placed before a
+                return 1;
+            }
+            if (confIndexB < 0) {
+                // only a is part of order list, so it is placed before b
+                return -1;
+            }
+            // From here, we can assume that both are in the order list:
+            return confIndexA - confIndexB;
+        });
+        $(entity).find(".caosdb-properties").append(properties);
+    }
+
+    this._hide_property = function (propname, userName, userRoles, conf) {
+
+        // is this property only shown for certain users/groups?
+        if ((conf.show != undefined) && conf.show.length > 0) {
+            for (let def of conf.show) {
+                if (propname.toLowerCase() == def.name.toLowerCase()) {
+                    if (!(def.users.includes(userName)) && !(userRoles.some(role => def.roles.includes(role)))) {
+                        return true
+                    }
+                }
+            }
+        }
+
+        // is this property hidden for certain users/groups?
+        if ((conf.hide != undefined) && conf.hide.length > 0) {
+            for (let def of conf.hide) {
+                if (propname.toLowerCase() == def.name.toLowerCase()) {
+                    if (def.users.includes(userName) || userRoles.some(role => def.roles.includes(role))) {
+                        return true
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    this._getRecordTypes = async function (conf) {
+
+        const parentTypes = Object.keys(conf);
+
+        var typesWithChildren = {};
+        var allTypesOrChildren = [];
+
+        for (let parentName of parentTypes) {
+            const children = await query(`FIND RECORDTYPE "${parentName}"`);
+            const names = children.map(ent => getEntityName(ent));
+            typesWithChildren[parentName] = names;
+            allTypesOrChildren = allTypesOrChildren.concat(names);
+        }
+
+        return {
+            "typesWithChildren": typesWithChildren,
+            "allTypesOrChildren": allTypesOrChildren
+        };
+    }
+
+    this.unhideAllProperties = function () {
+        // Just show all initially hidden properties
+        $(".caosdb-v-hidden-property").removeClass("caosdb-v-hidden-property");
+    }
+
+    this._unhideAllPropertiesWrapper = function (original) {
+        // construct a function that wirst does the original work, then unhides
+        // all properties, then returns the original return values.
+        const result = function (entity) {
+            var original_return = undefined;
+            if (typeof original === "function") {
+                original_return = original(entity);
+            }
+            prop_display.unhideAllProperties();
+            return original_return;
+        }
+
+        return result;
+    }
+
+    this._displayPropertiesWrapper = function (original, conf, allTypes) {
+        // Same as above, but for when there actually may be something to hide
+        // (i.e., build variable is set and config is non-empty).
+        const result = function (entitiy) {
+            var original_return = undefined;
+            if (typeof original === "function") {
+                original_return = original(entitiy);
+            }
+            var entities = prop_display.getEntitiesInView();
+            const userName = getUserName();
+            const userRoles = getUserRoles();
+            prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+            return original_return;
+        }
+
+        return result;
+    }
+
+    this.init = async function () {
+        const conf = await this.load_config();
+        if (Object.keys(conf).length > 0) {
+            const allTypes = await this._getRecordTypes(conf);
+            var entities = this.getEntitiesInView();
+            const userName = getUserName();
+            const userRoles = getUserRoles();
+            this.displayProperties(entities, conf, allTypes, userName, userRoles);
+            // If we are in the edit mode, (un)hide properties after ending
+            // the editing of an entity
+            document.body.addEventListener(edit_mode.start_edit.type, (e) => {
+                edit_mode.app.onAfterShowResults = this._displayPropertiesWrapper(edit_mode.app.onAfterShowResults, conf, allTypes);
+            }, true);
+
+        } else {
+            // There are no properties to be hidden, so make this clear in HTML body
+            $("body").attr("data-hidden-properties", "false")
+            this.unhideAllProperties();
+            document.body.addEventListener(edit_mode.start_edit.type, (e) => {
+                // also unhide properties when leaving the edit mode
+                // TODO(fspreck): We're lacking a proper state/event here in the
+                // edit mode, so do this on "init", since this is the state to which
+                // the state machine returns after either successfully saving an
+                // entity or canceling the edit.
+                edit_mode.app.onAfterShowResults = this._unhideAllPropertiesWrapper(edit_mode.app.onAfterShowResults);
+            }, true);
+        }
+    }
+}($, edit_mode, getEntityName, getEntityRole, getPropertyElements, getPropertyName, getUserName, getUserRoles, log.getLogger("ext_prop_display"), load_config, query);
+
+$(document).ready(() => {
+    if ("${BUILD_MODULE_EXT_PROPERTY_DISPLAY}" == "ENABLED") {
+        caosdb_modules.register(prop_display);
+    }
+});
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 03e372919377e537017a0f2917c0d66db9ed580e..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,16 +1200,96 @@ var paging = new function () {
     }
 };
 
-var queryForm = new function () {
-    this.init = function (form) {
-        this.restoreLastQuery(form, () => window.sessionStorage.lastQuery);
-        this.bindOnClick(form, (set) => {
+/**
+ * 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;
-            return null;
         });
+        const BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS = "";
+        queryForm.initFreeSearch(form, `${BUILD_FREE_SEARCH_ROLE_NAME_FACET_OPTIONS}`);
     };
 
-    this.restoreLastQuery = function (form, getter) {
+    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");
         }
@@ -1392,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 + "&";
@@ -1403,6 +1311,20 @@ var queryForm = new function () {
         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;
 
     /**
@@ -1413,15 +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.bindOnClick = function (form, setter) {
+    /**
+     * 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");
         }
@@ -1434,37 +1371,40 @@ var queryForm = new function () {
         var submithandler = function () {
             // store current query
             var queryField = form.query;
-            var value = queryField.value.toUpperCase().trim();
+            var value = queryField.value;
             if (typeof value == "undefined" || value.length == 0) {
                 return;
             }
-            if (!(value.startsWith("FIND") || value.startsWith("COUNT") || value.startsWith("SELECT"))) {
+            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;
                 }
-                var query_string = "FIND ENTITY WHICH HAS ";
-                // send a query that combines all fragments with an AND 
-                queryField.value = query_string + words.join(" AND ");
+                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 && !queryForm.isSelectQuery(queryField.value)) {
+            if (form.P && !isSelectQuery(queryField.value)) {
                 paging = form.P.value
             }
 
             queryForm.redirect(queryField.value.trim(), paging);
-        };
 
-        $("#caosdb-query-textarea").on("keydown", (e) => {
-            // prevent submit on enter
-            if (e.originalEvent.which == 13) {
-                e.originalEvent.preventDefault();
-            }
-        })
+            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) {
@@ -1480,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"];
@@ -1538,6 +1475,10 @@ this.markdown = new function () {
     });
 }
 
+/**
+ * @module hintMessages
+ * @global
+ */
 var hintMessages = new function () {
     this.init = function () {
         for (var entity of $('.caosdb-entity-panel')) {
@@ -1992,7 +1933,6 @@ function initOnDocumentReady() {
     }
     caosdb_modules.init();
     navbar.init();
-    version_history.init();
 
     if ("${BUILD_MODULE_USER_MANAGEMENT}" == "ENABLED") {
         caosdb_modules.register(user_management);
@@ -2015,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/src/core/webcaosdb.xsl b/src/core/webcaosdb.xsl
index 9a0d6769f1901e860c7a2318fce28957d6461496..38b6461e8793d9063f1d8f0047df9232c138da54 100644
--- a/src/core/webcaosdb.xsl
+++ b/src/core/webcaosdb.xsl
@@ -66,6 +66,14 @@
         <xsl:call-template name="caosdb-head-js" />
       </head>
       <body>
+        <xsl:choose>
+          <xsl:when test="'${BUILD_MODULE_EXT_PROPERTY_DISPLAY}'='ENABLED'">
+            <xsl:attribute name="data-hidden-properties">true</xsl:attribute>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:attribute name="data-hidden-properties">false</xsl:attribute>
+          </xsl:otherwise>
+        </xsl:choose>
         <xsl:attribute name="data-response-count">
           <xsl:value-of select="/Response/@count"/>
         </xsl:attribute>
diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl
index 82de9e416c2e28f21cd3f386cc6e04419284dc5f..264136f09828dce4e2ba7d324ff09fc14db5842c 100644
--- a/src/core/xsl/entity.xsl
+++ b/src/core/xsl/entity.xsl
@@ -232,7 +232,15 @@
   </xsl:template>
   <!-- PROPERTIES -->
   <xsl:template match="Property" mode="entity-body">
-    <li class="list-group-item caosdb-v-property-row caosdb-f-entity-property">
+    <li>
+      <xsl:choose>
+        <xsl:when test="'${BUILD_MODULE_EXT_PROPERTY_DISPLAY}'='ENABLED'">
+          <xsl:attribute name="class">list-group-item caosdb-v-property-row caosdb-f-entity-property caosdb-v-hidden-property</xsl:attribute>
+        </xsl:when>
+        <xsl:otherwise>
+          <xsl:attribute name="class">list-group-item caosdb-v-property-row caosdb-f-entity-property</xsl:attribute>
+        </xsl:otherwise>
+      </xsl:choose>
       <xsl:attribute name="id">
         <xsl:value-of select="generate-id()"/>
       </xsl:attribute>
diff --git a/src/core/xsl/query.xsl b/src/core/xsl/query.xsl
index 702a390f28ada96e140f40f10218b1740ab10700..6497cc9c64f7921c2b26b4ccd6535d181ddfeb4b 100644
--- a/src/core/xsl/query.xsl
+++ b/src/core/xsl/query.xsl
@@ -359,7 +359,7 @@
         </xsl:attribute>
         <input id="caosdb-query-paging-input" name="P" type="hidden" value="0L10"/>
         <div class="input-group">
-          <input class="form-control" id="caosdb-query-textarea" name="query" placeholder="E.g. 'FIND Experiment'" rows="1" style="resize: vertical;" type="text"></input>
+          <input class="form-control caosdb-f-query-textarea" id="caosdb-query-textarea" name="query" placeholder="E.g. 'FIND Experiment'" rows="1" style="resize: vertical;" type="text"></input>
             <a class="btn btn-secondary caosdb-search-btn" href="#" title="Click to execute the query.">
               <i class="bi-search"></i>
             </a>
diff --git a/src/doc/conf.py b/src/doc/conf.py
index 48707c8b6481453457a2712658d8bf2085703be3..9c8fb8b0efdeb765156b1b6ab92b9cafea6669a1 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -26,9 +26,9 @@ copyright = '2022, IndiScale GmbH'
 author = 'Daniel Hornung'
 
 # The short X.Y version
-version = '0.9.0'
+version = '0.10.0'
 # The full version, including alpha/beta/rc tags
-release = '0.9.0'
+release = '0.10.0'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/src/doc/extension/display_of_properties.rst b/src/doc/extension/display_of_properties.rst
new file mode 100644
index 0000000000000000000000000000000000000000..591ce9b2a07f84eb20fe12a2128f47858103e266
--- /dev/null
+++ b/src/doc/extension/display_of_properties.rst
@@ -0,0 +1,142 @@
+Tweaking the display of properties
+==================================
+
+Hide or show properties for specific roles and users
+****************************************************
+
+.. note::
+
+   This feature is part of CaosDB WebUI 0.10 and is not available for 0.9.X or
+   older.
+
+.. warning::
+
+   Hiding properties is purely cosmetics and should **never** be considered a
+   security feature. The hidden properties are still part of the server
+   response.
+
+Sometimes it is desirable to hide certain properties for specific users or
+roles, e.g., when they might be irrelevant or confusing. For example, an
+internal id might only be of interest to curators or administrators, whereas it
+is entirely meaningless to most other users.
+
+To configure the hiding of properties, you first need to enable the build
+variable ``BUILD_MODULE_EXT_PROPERTY_DISPLAY``. Then, the display of the
+properties is configured in ``conf/ext/json/ext_prop_display.json``. In there,
+properties of a specific RecordType can be hidden by specifying the name of the
+property, and the names of the roles and/or users from whom it should be hidden.
+
+.. code-block:: json
+
+   {
+     "RecordTypeName": {
+       "hide": [
+         {
+           "name": "property name",
+           "roles": ["list", "of", "roles"],
+           "users": ["list", "of", "users"]
+         },
+         ...
+       ]
+     },
+     ...
+   }
+
+In the same way but using the ``show`` keyword, properties can be hidden for
+everyone **but** the specified users/roles:
+
+.. code-block:: json
+
+   {
+     "RecordTypeName": {
+       "show": [
+         {
+           "name": "property name",
+           "roles": ["list", "of", "roles"],
+           "users": ["list", "of", "users"]
+         },
+         ...
+       ]
+     },
+     ...
+   }
+ 
+
+For example, using the data from demo.indiscale.com, the following would hide
+the ``price`` of all ``MusicalInstruments`` from ``anonymous`` and their
+``Manufacturer`` from the ``admin`` user.
+
+.. code-block:: json
+
+   {
+     "MusicalInstrument": {
+       "hide": [
+         {"name": "price", "roles": ["anonymous"], "users": []},
+         {"name": "Manufacturer", "roles": [], "users": ["admin"]}
+       ]
+     }
+   }
+
+Defining the order of properties
+********************************
+
+Similar to above, the order in which properties are displayed can also be
+specified. Again, the build variable ``BUILD_MODULE_EXT_PROPERTY_DISPLAY`` has
+to be enabled. Then, the order in which the properties are displayed can be
+configured in the same configuration file as above, i.e., in
+``conf/ext/json/ext_prop_display.json``:
+
+.. code-block:: json
+
+   {
+     "RecordTypeName": {
+       "order": ["ordered", "list", "of", "properties"]
+     },
+     ...
+   }
+
+This ensures that the properties of all entities with the declared types are
+displayed in the defined order if present. Additional properties, that are not
+part of this list are appended in their original order as returned from the CaosDB
+server.
+
+Using again the data of demo.indiscale.com for an example, a configuration might
+look the following:
+
+.. code-block:: json
+
+   {
+     "MusicalInstrument": {
+       "order": ["price", "Manufacturer"]
+     }
+   }
+
+In all ``MusicalInstrument`` entities, the ``price`` would then be shown first,
+then the ``Manufacturer``, and then all remaining properties. If it doesn't have
+a ``price`` property, ``Manufacturer`` is shown on top.
+
+Of course, this feature can be combined with the hiding of properties:
+
+.. code-block:: json
+
+   {
+     "MusicalInstrument": {
+       "hide": [
+         {"name": "price", "roles": ["anonymous"], "users": []},
+         {"name": "Manufacturer", "roles": [], "users": ["admin"]}
+       ],
+       "order": ["price", "Manufacturer"]
+     }
+   }
+
+
+In this example, ``price`` would still be displayed on top of the list of
+property in every ``MusicalInstrument`` entity, but it is hidden for the
+``anonymous`` role.
+
+Future
+******
+
+In the future, this feature will be extended to allow to `toggle
+<https://gitlab.com/caosdb/caosdb-webui/-/issues/190>`__ properties of
+predifined RecordTypes.
diff --git a/src/doc/extension/query_templates.rst b/src/doc/extension/query_templates.rst
index 015d26a21fe20bc09dc97db9c7a3e7a1ca58a0b3..4e511ac9f91b26ffcda2674362b00070948c1bd2 100644
--- a/src/doc/extension/query_templates.rst
+++ b/src/doc/extension/query_templates.rst
@@ -192,7 +192,7 @@ The following example for the file global_query_shortcuts.json would create two
         {
             "description": "Show a table of Experiments for year: {year}",
             "query": "SELECT date, project, identifier FROM Record Experiment with date in {year}"
-        },
+        }
     ]
 
 Data Model for User Query Templates
diff --git a/test/core/js/modules/ext_map.js.js b/test/core/js/modules/ext_map.js.js
index 3e55506b9abc8742ae59bf958febd9f63f968ba9..54c8d003029632f13277c05c332b0c4993c35c63 100644
--- a/test/core/js/modules/ext_map.js.js
+++ b/test/core/js/modules/ext_map.js.js
@@ -76,7 +76,7 @@ QUnit.module("ext_map.js", {
 });
 
 QUnit.test("availability", function (assert) {
-    assert.equal(caosdb_map.version, "0.4.1", "test version");
+    assert.equal(caosdb_map.version, "0.5.0", "test version");
     assert.ok(caosdb_map.init, "init available");
 });
 
@@ -272,7 +272,7 @@ QUnit.test("make_layer_chooser_html", function (assert) {
         "id": "test_id",
         "name": "test name",
         "description": "test description",
-        "icon": {
+        "icon_options": {
             "html": "<span>ICON</span>",
         },
     };
@@ -287,7 +287,7 @@ QUnit.test("_init_single_entity_layer", function (assert) {
         "id": "test_id",
         "name": "test name",
         "description": "test description",
-        "icon": {
+        "icon_options": {
             "html": "<span>ICON</span>",
         },
     }
diff --git a/test/core/js/modules/ext_prop_display.js.js b/test/core/js/modules/ext_prop_display.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c2c0264b44810786ed64fc556d21fb21dbe9b1f
--- /dev/null
+++ b/test/core/js/modules/ext_prop_display.js.js
@@ -0,0 +1,324 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@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/>.
+ */
+
+'use strict';
+
+QUnit.module("ext_prop_display.js", {
+    before: function (assert) {
+        // setup before module
+    },
+    beforeEach: function (assert) {
+        // setup before each test
+        // entity list, one entity with three properties (ids 1,2,3 for
+        // testing), all of them hidden by default.
+        $(document.body).append('<div class="caosdb-f-main-entities prop-display-test-entities"><div id=115 class="caosdb-entity-panel"><div class="caosdb-entity-panel-heading"><span class="caosdb-f-parent-list"><span class="caosdb-parent-item"><a class="caosdb-parent-name" href="https://demo.indiscale.com/Entity/110">Guitar</a></span></span></div><div class="caosdb-entity-panel-body"><ul class="list-group caosdb-properties"><li id=1 class="caosdb-v-property-row caosdb-f-entity-property caosdb-v-hidden-property"><div class="row"><div class="caosdb-v-property-left-col"><span class="caosdb-property-name">first prop</span></div><div class="caosdb-f-property-value"><span class="caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value">48.0</span><span class="caosdb-unit">€</span></div></div></li><li id=2 class="caosdb-v-property-row caosdb-f-entity-property caosdb-v-hidden-property"><div class="row"><div class="caosdb-v-property-left-col"><span class="caosdb-property-name">second prop</span></div><div class="caosdb-f-property-value"><span class="caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value">48.0</span><span class="caosdb-unit">€</span></div></div></li><li id=3 class="caosdb-v-property-row caosdb-f-entity-property caosdb-v-hidden-property"><div class="row"><div class="caosdb-v-property-left-col"><span class="caosdb-property-name">third prop</span></div><div class="caosdb-f-property-value"><span class="caosdb-f-property-single-raw-value caosdb-property-text-value caosdb-f-property-text-value caosdb-v-property-text-value">48.0</span><span class="caosdb-unit">€</span></div></div></li></ul></div></div></div>');
+    },
+    afterEach: function (assert) {
+        // teardown after each test
+        $(".prop-display-test-entities").remove();
+    },
+    after: function (assert) {
+        // teardown after module
+    }
+});
+
+QUnit.test("unhide all properties", function (assert) {
+    assert.ok(prop_display.unhideAllProperties, "unhideAllProperties available");
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all properties hidden initially");
+    prop_display.unhideAllProperties();
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 0, "no hidden properties after unhiding");
+});
+
+QUnit.test("hide properties garbage type", function (assert) {
+    assert.ok(prop_display.getEntitiesInView, "getEntitiesInView available");
+    assert.ok(prop_display.displayProperties, "displayProperties available");
+    const conf = {
+        "DoesntExist": {
+            "hide": [{
+                "name": "first prop",
+                "roles": ["some_role"],
+                "users": ["someone"]
+            }]
+        }
+    };
+    // only one garbage type
+    const allTypes = {
+        "typesWithChildren": {
+            "DoesntExist": ["DoesntExist"]
+        },
+        "allTypesOrChildren": ["DoesntExist"]
+    };
+    const userName = "someone";
+    const userRoles = ["some_role", "some_other_role"];
+    const entities = prop_display.getEntitiesInView();
+    assert.equal(entities.length, 1, "only one entity in test data");
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all properties hidden initially");
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 0, "no garbage-type entity, so no hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "all properties are being shown");
+});
+
+QUnit.test("hide properties garbage property", function (assert) {
+    assert.ok(prop_display.getEntitiesInView, "getEntitiesInView available");
+    assert.ok(prop_display.displayProperties, "displayProperties available");
+    const conf = {
+        "MusicalInstrument": {
+            "hide": [{
+                "name": "prop does not exist",
+                "roles": ["some_role"],
+                "users": ["someone"]
+            }]
+        }
+    };
+    const allTypes = {
+        "typesWithChildren": {
+            "MusicalInstrument": ["MusicalInstrument", "Guitar"]
+        },
+        "allTypesOrChildren": ["MusicalInstrument", "Guitar"]
+    };
+    const userName = "someone";
+    const userRoles = ["some_role", "some_other_role"];
+    const entities = prop_display.getEntitiesInView();
+    assert.equal(entities.length, 1, "only one entity in test data");
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all properties hidden initially");
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 0, "no garbage property, so no hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "all properties are being shown");
+});
+
+
+QUnit.test("hide properties", function (assert) {
+    assert.ok(prop_display.getEntitiesInView, "getEntitiesInView available");
+    assert.ok(prop_display.displayProperties, "displayProperties available");
+    const conf = {
+        "MusicalInstrument": {
+            "hide": [{
+                    "name": "first prop",
+                    "roles": ["some_role"],
+                    "users": ["someone"]
+                },
+                {
+                    "name": "second prop",
+                    "roles": [],
+                    "users": ["someone else"]
+                },
+                {
+                    "name": "third prop",
+                    "roles": ["some_other_role"],
+                    "users": ["someone else"]
+                }
+            ]
+        }
+    };
+    const allTypes = {
+        "typesWithChildren": {
+            "MusicalInstrument": ["MusicalInstrument", "Guitar"]
+        },
+        "allTypesOrChildren": ["MusicalInstrument", "Guitar"]
+    };
+    var userName = "someone";
+    var userRoles = ["some_role"];
+    const entities = prop_display.getEntitiesInView();
+    assert.equal(entities.length, 1, "only one entity in test data");
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all properties hidden initially");
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 1, "exactly one hidden property");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 2, "the remaining two are shown");
+    assert.equal($("#1").hasClass("caosdb-v-hidden-property"), true, "first prop hidden");
+    assert.equal($("#2").hasClass("caosdb-v-show-property"), true, "second prop shown");
+    assert.equal($("#3").hasClass("caosdb-v-show-property"), true, "third prop shown");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "all shown after reset");
+
+    userRoles = ["some_other_role"];
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 2, "two hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 1, "the remaining one is shown");
+    assert.equal($("#1").hasClass("caosdb-v-hidden-property"), true, "first prop hidden");
+    assert.equal($("#2").hasClass("caosdb-v-show-property"), true, "second prop shown");
+    assert.equal($("#3").hasClass("caosdb-v-hidden-property"), true, "third prop hidden");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "all shown after reset");
+
+    userName = "someone else";
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 2, "two hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 1, "the remaining one is shown");
+    assert.equal($("#1").hasClass("caosdb-v-show-property"), true, "first prop shown");
+    assert.equal($("#2").hasClass("caosdb-v-hidden-property"), true, "second prop hidden");
+    assert.equal($("#3").hasClass("caosdb-v-hidden-property"), true, "third prop hidden");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "all shown after reset");
+
+    userRoles = ["some_role", "some_other_role"]
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "two hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 0, "None is shown");
+    assert.equal($("#1").hasClass("caosdb-v-hidden-property"), true, "first prop hidden");
+    assert.equal($("#2").hasClass("caosdb-v-hidden-property"), true, "second prop hidden");
+    assert.equal($("#3").hasClass("caosdb-v-hidden-property"), true, "third prop hidden");
+
+});
+
+QUnit.test("show properties", function (assert) {
+    assert.ok(prop_display.getEntitiesInView, "getEntitiesInView available");
+    assert.ok(prop_display.displayProperties, "displayProperties available");
+    const conf = {
+        "MusicalInstrument": {
+            "show": [{
+                    "name": "first prop",
+                    "roles": ["some_role"],
+                    "users": ["someone"]
+                },
+                {
+                    "name": "second prop",
+                    "roles": [],
+                    "users": ["someone else"]
+                },
+                {
+                    "name": "third prop",
+                    "roles": ["some_other_role"],
+                    "users": ["someone else"]
+                }
+            ]
+        }
+    };
+    const allTypes = {
+        "typesWithChildren": {
+            "MusicalInstrument": ["MusicalInstrument", "Guitar"]
+        },
+        "allTypesOrChildren": ["MusicalInstrument", "Guitar"]
+    };
+    var userName = "someone";
+    var userRoles = ["some_role"];
+    const entities = prop_display.getEntitiesInView();
+    assert.equal(entities.length, 1, "only one entity in test data");
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all properties hidden initially");
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 2, "two hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 1, "the remaining one shown");
+    assert.equal($("#1").hasClass("caosdb-v-show-property"), true, "first prop shown");
+    assert.equal($("#2").hasClass("caosdb-v-hidden-property"), true, "second prop hidden");
+    assert.equal($("#3").hasClass("caosdb-v-hidden-property"), true, "third prop hidden");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all hidden after reset");
+
+    userRoles = ["some_other_role"];
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 1, "one hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 2, "the remaining two are shown");
+    assert.equal($("#1").hasClass("caosdb-v-show-property"), true, "first prop shown");
+    assert.equal($("#2").hasClass("caosdb-v-hidden-property"), true, "second prop hidden");
+    assert.equal($("#3").hasClass("caosdb-v-show-property"), true, "third prop shown");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all hidden after reset");
+
+    userName = "someone else";
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 1, "one hidden property");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 2, "the remaining ones are shown");
+    assert.equal($("#1").hasClass("caosdb-v-hidden-property"), true, "first prop hidden");
+    assert.equal($("#2").hasClass("caosdb-v-show-property"), true, "second prop shown");
+    assert.equal($("#3").hasClass("caosdb-v-show-property"), true, "third prop shown");
+
+    // reset
+    prop_display.displayProperties(entities, conf, allTypes, "", []);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 3, "all hidden after reset");
+
+    userRoles = ["some_role", "some_other_role"]
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    assert.equal($(document).find(".caosdb-v-hidden-property").length, 0, "no hidden properties");
+    assert.equal($(document).find(".caosdb-v-show-property").length, 3, "All are shown");
+    assert.equal($("#1").hasClass("caosdb-v-show-property"), true, "first prop shown");
+    assert.equal($("#2").hasClass("caosdb-v-show-property"), true, "second prop shown");
+    assert.equal($("#3").hasClass("caosdb-v-show-property"), true, "third prop shown");
+
+});
+
+QUnit.test("Sort properties", function (assert) {
+    assert.ok(prop_display.getEntitiesInView, "getEntitiesInView available");
+    assert.ok(prop_display.displayProperties, "displayProperties available");
+
+    var conf = {
+        "MusicalInstrument": {
+            "order": ["third prop", "first prop", "second prop"]
+        }
+    };
+    const allTypes = {
+        "typesWithChildren": {
+            "MusicalInstrument": ["MusicalInstrument", "Guitar"]
+        },
+        "allTypesOrChildren": ["MusicalInstrument", "Guitar"]
+    };
+    // username and roles don't matter for sorting
+    const userName = "";
+    const userRoles = [];
+    // initial order
+    var properties = $(document).find(".caosdb-v-property-row");
+    assert.equal(properties.index($("#1")), 0, "first prop at first position");
+    assert.equal(properties.index($("#2")), 1, "second prop at second position");
+    assert.equal(properties.index($("#3")), 2, "third prop at third position");
+
+    var entities = prop_display.getEntitiesInView();
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    properties = $(document).find(".caosdb-v-property-row");
+    assert.equal(properties.index($("#1")), 1, "first prop at second position");
+    assert.equal(properties.index($("#2")), 2, "second prop at third position");
+    assert.equal(properties.index($("#3")), 0, "third prop at first position");
+
+    // only specify first prop, the rest is appended in the previous order.
+    conf = {
+        "MusicalInstrument": {
+            "order": ["first prop"]
+        }
+    };
+    entities = prop_display.getEntitiesInView();
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    properties = $(document).find(".caosdb-v-property-row");
+    assert.equal(properties.index($("#1")), 0, "first prop at first position");
+    assert.equal(properties.index($("#2")), 2, "second prop at third position");
+    assert.equal(properties.index($("#3")), 1, "third prop at second position");
+
+    // two specified, the remaining prop is appended
+    conf = {
+        "MusicalInstrument": {
+            "order": ["second prop", "first prop"]
+        }
+    };
+    entities = prop_display.getEntitiesInView();
+    prop_display.displayProperties(entities, conf, allTypes, userName, userRoles);
+    properties = $(document).find(".caosdb-v-property-row");
+    assert.equal(properties.index($("#1")), 1, "first prop at second position");
+    assert.equal(properties.index($("#2")), 0, "second prop at first position");
+    assert.equal(properties.index($("#3")), 2, "third prop at third position");
+
+
+
+});
diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js
index 328a057f19af953399e08867085f6e04b5e785c5..534dc86c485ccfac184bb05bac17d595addaa3d6 100644
--- a/test/core/js/modules/webcaosdb.js.js
+++ b/test/core/js/modules/webcaosdb.js.js
@@ -1111,19 +1111,10 @@ QUnit.test("initEntity", function (assert) {
 QUnit.module("webcaosdb.js - queryForm", {
     before: function (assert) {
         assert.ok(queryForm, "queryForm is defined");
+        assert.notOk(queryForm.initFreeSearch(), "free search reset");
     }
 });
 
-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.");
@@ -1196,14 +1187,14 @@ QUnit.test("bindOnClick", function (assert) {
     assert.equal(storage(), undefined, "before2: storage empty.");
     form.getElementsByClassName("caosdb-search-btn")[0].onclick();
 
-    assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext*'", "after2: storage not empty.");
+    assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*freetext*'", "after2: storage not empty.");
 
     // test the form submit handler analogously
     form.query.value = "freetext2";
     $("body").append(form);
     $(form).append(submitButton);
     submitButton.click();
-    assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext2*'", "after3: storage not empty.");
+    assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*freetext2*'", "after3: storage not empty.");
 
     $(form).remove();
 
@@ -1212,7 +1203,7 @@ QUnit.test("bindOnClick", function (assert) {
     $("body").append(form);
     $(form).append(submitButton);
     submitButton.click();
-    assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty.");
+    assert.equal(storage(), "FIND RECORD WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty.");
 
     $(form).remove();
 
@@ -1221,7 +1212,7 @@ QUnit.test("bindOnClick", function (assert) {
     $("body").append(form);
     $(form).append(submitButton);
     submitButton.click();
-  assert.equal(storage(), `FIND ENTITY WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*"\\'bla*'`, "after5: stuff with quotation marks");
+  assert.equal(storage(), `FIND RECORD WHICH HAS A PROPERTY LIKE '*with double*' AND A PROPERTY LIKE '*and single*' AND A PROPERTY LIKE '*what\\'s wrong?*' AND A PROPERTY LIKE '*\\'*' AND A PROPERTY LIKE '*nothin\\'*' AND A PROPERTY LIKE '*"\\'bla*'`, "after5: stuff with quotation marks");
 
     // ... then with empty quotation marks. this will not trigger the query execution at all
     storage("not triggered");
@@ -1251,6 +1242,42 @@ QUnit.test("splitSearchTerms", function (assert) {
     }
 });
 
+QUnit.test("initFreeSearch", function (assert) {
+    const form = $('<form></form>');
+    const inputGroup = $('<div class="input-group"></div>');
+    const textArea = $( '<textarea class="caosdb-f-query-textarea" name="query"></textarea>');
+    const submitButton = $('<input class="caosdb-search-btn" type="submit">');
+    inputGroup.append(textArea);
+    form.append(inputGroup).append(submitButton);
+    $("body").append(form);
+
+    // without initialization
+    assert.ok(queryForm.initFreeSearch, "available");
+    assert.notOk(queryForm.getRoleNameFacetSelect(), "role_name_facet_select is undefined 1");
+    assert.notOk(queryForm.initFreeSearch(), "not initialized");
+
+    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");
+
+
+    window.localStorage["role_name_facet_option"] = "Sample";
+    assert.ok(queryForm.initFreeSearch(form, "Person, Experiment, Sample"), "initialized");
+
+    // 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(), "Sample", "previously selected option is selected");
+    form.find("select")[0].value = "Experiment";
+    assert.equal(queryForm.getRoleNameFacet(), "Experiment");
+
+    // clean up
+    form.remove();
+})
+
 /* MODULE paging */
 QUnit.module("webcaosdb.js - paging", {
     before: function (assert) {}