diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78608dd944b4c5b79762548e1a55f32e754d248a..ae5c058e15f82a98df92e6822efe174fe54edece 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,24 @@ 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.4.1] - 2021-11-04
+
+### Added (for new features, dependecies etc.)
+
+* `form_panel` module for conveniently creating a panel for web forms.
+
+### Changed (for changes in existing functionality)
+
+### Deprecated (for soon-to-be removed features)
+
+### Removed (for now removed features)
+
+### Fixed (for any bug fixes)
+
+### Security (in case of vulnerabilities)
+
+### Documentation (for notable additions or changes of the documentation)
+
 ## [0.4.0] - 2021-10-28
 
 ### Added (for new features, dependecies etc.)
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index 65a2b9bc42f9e0f22dc0cd6ca6baac094e94eee7..df68f1012cd71b49538b073a4c9d17e85b2214e4 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -156,4 +156,5 @@ MODULE_DEPENDENCIES=(
     ext_cosmetics.js
     qrcode.js
     ext_qrcode.js
+    form_panel.js
 )
diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js
index d01b45ee9148febc592d615a8fa947d0d53656d3..193235a2f8a799c07ccc893742d5df9a7d0fa7d1 100644
--- a/src/core/js/form_elements.js
+++ b/src/core/js/form_elements.js
@@ -100,14 +100,41 @@ var form_elements = new function () {
     this.version = "0.1";
     this.dependencies = ["log", "caosdb_utils", "markdown", "bootstrap"];
     this.logger = log.getLogger("form_elements");
+    /**
+     * Event. On form cancel.
+     */
     this.cancel_form_event = new Event("caosdb.form.cancel");
+    /**
+     * Event. On form submit.
+     */
     this.submit_form_event = new Event("caosdb.form.submit");
+    /**
+     * Event. On field change.
+     */
     this.field_changed_event = new Event("caosdb.field.changed");
+    /**
+     * Event. On field enabled.
+     */
     this.field_enabled_event = new Event("caosdb.field.enabled");
+    /**
+     * Event. On field disabled.
+     */
     this.field_disabled_event = new Event("caosdb.field.disabled");
+    /**
+     * Event. On field ready (e.g. for reference drop downs)
+     */
     this.field_ready_event = new Event("caosdb.field.ready");
+    /**
+     * Event. On field error (e.g. for reference drop downs)
+     */
     this.field_error_event = new Event("caosdb.field.error");
+    /**
+     * Event. Form submitted successfully.
+     */
     this.form_success_event = new Event("caosdb.form.success");
+    /**
+     * Event. Error after form was submitted.
+     */
     this.form_error_event = new Event("caosdb.form.error");
 
 
@@ -1266,7 +1293,10 @@ var form_elements = new function () {
         }, config.to);
 
         const from_input = this.make_form_field(from_config);
+        $(from_input).toggleClass("form-control", false);
+
         const to_input = this.make_form_field(to_config);
+        $(to_input).toggleClass("form-control", false);
 
         const ret = $(this._make_field_wrapper(config.name));
         if (config.label) {
@@ -1277,12 +1307,8 @@ var form_elements = new function () {
         ret.append(to_input);
 
         // styling
-        $(from_input).toggleClass("form-control", false);
-        $(from_input).find(".col-sm-3").toggleClass("col-sm-3", false).toggleClass("col-sm-1");
-        $(from_input).find(".col-sm-9").toggleClass("col-sm-9", false).toggleClass("col-sm-3");
-        $(to_input).toggleClass("form-control", false);
-        $(to_input).find(".col-sm-3").toggleClass("col-sm-3", false).toggleClass("col-sm-1").toggleClass("col-sm-offset-1");
-        $(to_input).find(".col-sm-9").toggleClass("col-sm-9", false).toggleClass("col-sm-3");
+        $(from_input).toggleClass("col-sm-4", true);
+        $(to_input).toggleClass("col-sm-4", true);
 
         return ret[0];
     }
diff --git a/src/core/js/form_panel.js b/src/core/js/form_panel.js
new file mode 100644
index 0000000000000000000000000000000000000000..dabb28c9d9dad79a3768fde7552d483bdd3bf570
--- /dev/null
+++ b/src/core/js/form_panel.js
@@ -0,0 +1,94 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@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
+ */
+
+'use strict';
+
+/**
+ * form_panel module for creating a panel below the navbar where forms can be
+ * placed.
+ */
+var form_panel = new function () {
+    const logger = log.getLogger("form_panel");
+    this.version = "0.1";
+    this.dependencies = ["log", "caosdb_utils", "markdown", "bootstrap"];
+
+    /**
+     * Return a the panel which shall contain the form.
+     *
+     * Side-effects:
+     * 1. Creates the form panel if it does not exist.
+     * 2. Removes the welcome panel if present.
+     */
+    this.get_form_panel = function (panel_id, title) {
+        // remove welcome
+        $(".caosdb-f-welcome-panel").remove();
+        $(".caosdb-v-welcome-panel").remove();
+
+        var existing = $("#" + panel_id);
+        if (existing.length > 0) {
+            return existing[0];
+        }
+        const panel = $('<div id="' + panel_id + '" class="caosdb-f-form-panel bg-light container"/>');
+        const header = $('<h2 class="text-center">' + title + '</h2>');
+        panel.append(header);
+
+        // add to main panel
+        $('nav').after(panel);
+
+        return panel[0];
+    };
+
+    /**
+     * Remove the form panel from the DOM tree.
+     */
+    this.destroy_form_panel = function (panel) {
+        $(panel).remove();
+    };
+
+    /**
+     * Creates a callback function that toggles the form panel which
+     */
+    this.create_show_form_callback = function (panel_id, title, form_config) {
+        return (e) => {
+            logger.trace("enter show_form_panel", e);
+
+            const panel = $(form_panel.get_form_panel(panel_id, title));
+            if (panel.find("form").length === 0) {
+                const form = form_elements.make_form(form_config);
+                panel.append(form);
+                $(form).find(".selectpicker").selectpicker();
+
+                form.addEventListener("caosdb.form.cancel",
+                    (e) => form_panel.destroy_form_panel(panel),
+                    true
+                );
+            }
+        }
+    };
+
+    this.init = function () {
+    }
+}
+
+$(document).ready(function () {
+    caosdb_modules.register(form_panel);
+});
diff --git a/src/doc/extension/forms.rst b/src/doc/extension/forms.rst
index 1bced612b5f9517c5ec149871cbf53321b4671d4..e1891b8d7e571c4a273cec98fcc39e50398936f0 100644
--- a/src/doc/extension/forms.rst
+++ b/src/doc/extension/forms.rst
@@ -45,6 +45,36 @@ On submission, the function ``my_special_submit_handler`` is being called with t
 
 As the generated form is a plain HTML form, the javascript form API can be used. However, there are special methods in the ``form_elements`` module e.g. :doc:`get_fields <../api/module-form_elements>` which are especially designed to interact with the forms generated by the ``make_form`` factory.
 
+
+Placing the form in a panel below the navbar
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+There are functions in the `form_panel` module to make it easy to place forms at the typical location: 
+below the navbar. The following shows how the config (see above) is passed to 
+`init_show_form_panel_button`  a direct call to `make_form` is no longer necessary.
+
+.. code-block:: javascript
+
+    const title = "Upload CSV File"; // title of the form and text in the toolbox
+    const panel_id = "csv_upload_form_panel";
+
+    /**
+     * Add a button to the navbar, saying "Upload CSV File" which opens a
+     * form for file upload.
+     */
+    const init_show_form_panel_button = function () {
+        navbar.add_tool(title, tool_box, {
+            callback: form_panel.create_show_form_callback(
+                panel_id,
+                title,
+                csv_form_config)
+        });
+    };
+
+    const init = function () {
+        init_show_form_panel_button();
+    }
+
 Calling a server-side script
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/src/doc/extension/module.md b/src/doc/extension/module.md
index d4f9b56db508d7c3be9dc0a52953de5c82bf310d..207d3fff9d013cda657ef8c01f3cca5988622f4a 100644
--- a/src/doc/extension/module.md
+++ b/src/doc/extension/module.md
@@ -4,7 +4,14 @@ The CaosDB WebUI is organized in modules which can easily be added and on a modu
 There are a few steps necessary to create a new module.
 
 ## Create the module file
-Create a new file in `src/core/js` starting with `ext_`. E.g. `ext_flight_preview.js`. This file should define one function that wraps every thing and which is enabled at the bottom of the file:
+
+Create a new file for each new module. We have the convention, that extensions
+which are optional and should stay that way and also custom extensions for
+special purposes to name the file starting with `ext_`. E.g.
+`ext_flight_preview.js`.
+
+This file should define one function that wraps every thing and which is
+enabled at the bottom of the file:
 
 ```js
 /*
@@ -23,16 +30,14 @@ Create a new file in `src/core/js` starting with `ext_`. E.g. `ext_flight_previe
  * @requires somelibrary
  * (pass the dependencies as arguments)
  */
-var ext_flight_preview = function (somelibrary) {
+const ext_flight_preview = function (libA, libB) {
 
-    var init = function (toolbox) {
+    const init = function () {
         /* initialization of the module */
     }
 
-    /**
-     * doc string
-     */
-    var some_function = function (arg1, arg2) {
+    /* doc string */
+    const some_function = function (arg1, arg2) {
     }
 
     /* the main function must return the initialization of the module */
@@ -40,7 +45,7 @@ var ext_flight_preview = function (somelibrary) {
         init: init,
     };
 //pass the dependencies as arguments here as well
-}(somelibrary);
+}(libA, libB);
 
 // this will be replaced by require.js in the future.
 $(document).ready(function() {
@@ -52,21 +57,36 @@ $(document).ready(function() {
     }
 });
 ```
-## Update xml
-Add a section to `src/core/xsl/main.xsl` to include your new file.
-
-```xsl
-<xsl:element name="script">       
-   <xsl:attribute name="src">         
-     <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_sss_markdown.js')"/>             
-   </xsl:attribute>     
-</xsl:element>
-```
 
-## Add to index.html in test
-If you have unittests (and you should), you need to add a line in :
-`test/core/index.html`.
+## Install the module
+
+The new new file should be placed in `src/core/js` if it is intended to be merged into the main repository eventually. For development purposes and for custom extensions which are not to be published you may place it in `src/ext/js`.
+
+Everything inside `src/core/js` and `src/ext/js` will eventually being loaded.
+So, if there are no other modules which depend on this particular new module,
+you are done.
+
+Otherwise, when we need to configure the order in which the
+module is being loaded.
+
+
+### Dependency order
+
+#### For Upstream Code
+
+For modules which are about to be merged into the main or dev branch of this
+repository, add the module's file to `build.properties.d/00_default.properties`
+at the right location in the list of module files (Array
+`MODULE_DEPENDENCIES`). The list defines the order in which module files are
+being loaded.
+
+#### For Custom Extensions
+
+For modules which will not be published and merged with the main repository you
+may append all your module files in the desired order to the
+`MODULE_DEPENDENCIES` array in a new `*.properties` file (e.g.
+`build.properties.d/99_local_stuff`):
 
-## Update the changelog
+    MODULE_DEPENDENCIES+=(libA.js libB.js ext_flight_preview.js)
 
-## Create a merge request
\ No newline at end of file
+In this example, `libA.js`, `libB.js` and `ext_flight_preview.js` are custom modules developed for this particular CaosDB webui instance.
diff --git a/test/core/js/modules/form_panel.js.js b/test/core/js/modules/form_panel.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..bc8343d4a65233e025039e7476861fb998c2abbc
--- /dev/null
+++ b/test/core/js/modules/form_panel.js.js
@@ -0,0 +1,63 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH
+ *
+ * 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
+ */
+
+'use strict';
+
+QUnit.module("form_panel.js", {
+    before: function (assert) {
+
+    },
+    after: function (assert) {
+    }
+});
+
+QUnit.test("availability", function (assert) {
+    assert.ok(form_panel.init, "init available");
+    assert.ok(form_panel.create_show_form_callback , "version available");
+});
+
+QUnit.test("create_show_form_callback ", function (assert) {
+    const title = "Upload CSV File"; // title of the form and text in the toolbox
+    const panel_id = "csv_upload_form_panel";
+    const server_side_script = "csv_script.py";
+    const tool_box = "Tools"; // Name of the drop-down menu where the button is added in the navbar
+    const help_text = "something";
+    const accepted_files_formats = [ ".csv", "text/tsv", ] // Mime types and file endings.
+
+    const csv_form_config = {
+        script: server_side_script,
+        fields: [{
+            type: "file",
+            name: "csv_file",
+            label: "CSV File", // label of the file selector in the form
+            required: true,
+            cached: false,
+            accept: accepted_files_formats.join(","),
+            help: help_text,
+        }, ],
+    };
+    cb = form_panel.create_show_form_callback( panel_id, title, csv_form_config);
+    assert.equal(typeof cb, "function", "function created");
+    cb()
+});
+
+