diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85833209471f557697c840342ccf8127fd678117..5443484baefbfa6789ed586bff42aeda16e63c84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### 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)
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_panel.js b/src/core/js/form_panel.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1aa31507f8b79b22c1b46f8bea74aeeb37dea26
--- /dev/null
+++ b/src/core/js/form_panel.js
@@ -0,0 +1,93 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2019 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';
+
+/**
+ * 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..1c3ccd6872bee2dd7c824a23fed5656828566960 100644
--- a/src/doc/extension/module.md
+++ b/src/doc/extension/module.md
@@ -23,16 +23,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 +38,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 +50,19 @@ $(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>
-```
+## Place Module in Load Order
+Add the name of your module file to `build.properties.d/00_default.properties`, where the order is defined in which modules are loaded.
 
 ## Add to index.html in test
 If you have unittests (and you should), you need to add a line in :
 `test/core/index.html`.
 
+## Dependency order
+Add the module to `build.properties.d/00_default.properties` at the right 
+location in the list of modules. The list defines the order in which modules are
+loaded
+
 ## Update the changelog
 
-## Create a merge request
\ No newline at end of file
+## Create a merge request
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()
+});
+
+