diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6fb68fdc81cf6f6a86971be70c321eaea3750f1f..c295a5fc637112645189759f3267859385514518 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   * `BUILD_FOOTER_SOURCES_HREF`
   * `BUILD_FOOTER_LICENCE_HREF`
   See `build.properties.d/00_default.properties` for more information
+* Start editing an entity/creating a new record directly by adding an `#edit` or
+  `#new_record` URI fragment, respectively.
+* Optional WYSIWYG editor with markdown output for text properties. Controled by
+  the `BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT` build variable which is set do
+  `DISABLED` by default.
 
 ### Changed (for changes in existing functionality)
 
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index c6043d09d049b91cfc0a03560970537feb8bbc06..f3e1d1706dd8dd9bdbdab16377c2c75ed0299f7f 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -24,5 +24,12 @@
 * proj4js-2.5.0
 * Proj4Leaflet-1.0.1
 
+## For CKEditor (WYSIWYG editor in edit mode)
+
+* we're using a custom-built ckeditor 31.0.0 from
+  https://ckeditor.com/ckeditor-5/online-builder/ with a customized set of
+  editor plugins. Please refer to the `package.json` within
+  `libs/ckeditor...zip` for a full list of said plugins.
+
 ## For testing
 * qunit-2.9.2
diff --git a/Makefile b/Makefile
index c820cfb159d9b63518dff445883615e67fbd9289..5cca686319c617006007e4884cfb269796ac7af4 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ LIBS_DIR = $(abspath libs)
 TEST_CORE_DIR = $(abspath test/core/)
 TEST_EXT_DIR = $(abspath test/ext)
 TEST_SSS_DIR =$(abspath test/server_side_scripting)
-LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.js js/qrcode.js
+LIBS = fonts css/fonts css/bootstrap.css css/bootstrap-icons.css js/state-machine.js js/jquery.js js/showdown.js js/dropzone.js css/dropzone.css js/loglevel.js js/leaflet.js css/leaflet.css css/images js/leaflet-latlng-graticule.js js/proj4.js js/proj4leaflet.js js/leaflet-coordinates.js css/leaflet-coordinates.css js/leaflet-graticule.js js/bootstrap-select.js css/bootstrap-select.css js/bootstrap-autocomplete.min.js js/plotly.js js/pako.js js/utif.js js/bootstrap.js js/qrcode.js js/ckeditor.js
 
 
 TEST_LIBS = $(LIBS) js/qunit.js css/qunit.css $(subst $(TEST_CORE_DIR)/,,$(shell find $(TEST_CORE_DIR)/))
@@ -305,6 +305,9 @@ $(LIBS_DIR)/js/utif.js: unzip $(LIBS_DIR)/js
 $(LIBS_DIR)/js/qrcode.js: unzip $(LIBS_DIR)/js
 	ln -s $(LIBS_DIR)/qrcode-1.4.4/qrcode.min.js $@
 
+$(LIBS_DIR)/js/ckeditor.js: unzip $(LIBS_DIR)/js
+	ln -s $(LIBS_DIR)/ckeditor5-31.0.0-k356w86hp13l/build/ckeditor.js $@
+
 
 $(addprefix $(LIBS_DIR)/, js css):
 	mkdir $@ || true
diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties
index db01885d1494ffd3f07eecfa2e6f2d26f56e3705..535a6c846a5c39c48937dee43f087501f43285ae 100644
--- a/build.properties.d/00_default.properties
+++ b/build.properties.d/00_default.properties
@@ -60,6 +60,8 @@ BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB
 BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED
 BUILD_EXT_REFERENCES_CUSTOM_RESOLVER=person_reference
 
+BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT=DISABLED
+
 ##############################################################################
 # Navbar properties
 ##############################################################################
@@ -173,4 +175,6 @@ MODULE_DEPENDENCIES=(
     qrcode.js
     ext_qrcode.js
     form_panel.js
+    ckeditor.js
+    ext_editmode_wysiwyg_text.js
 )
diff --git a/libs/ckeditor5-31.0.0-k356w86hp13l.zip b/libs/ckeditor5-31.0.0-k356w86hp13l.zip
new file mode 100644
index 0000000000000000000000000000000000000000..71523482a2bdfa7c0a9776eeed7aa7be010e053b
Binary files /dev/null and b/libs/ckeditor5-31.0.0-k356w86hp13l.zip differ
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index 359bfac8481f5edfa4a51b7b1c22d31240b1312e..284aaef65e4f6fe89d0a3e9ee4b6690b8ba38ce9 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -76,11 +76,52 @@ var edit_mode = new function () {
         }
     }
 
+    this.has_edit_fragment = function () {
+        const fragment = window.location.hash.substr(1);
+        return fragment === "edit";
+    }
+
+    this.has_new_record_fragment = function () {
+        const fragment = window.location.hash.substr(1);
+        return fragment === "new_record";
+    }
+
     this._init = function () {
         var target = $("#top-navbar").find("ul").first();
         this.add_edit_mode_button(target, edit_mode.toggle_edit_mode);
+
+        var after_setup_callback = () => {} // do nothing
+        if (this.has_edit_fragment()) {
+            // find first entity
+            const first_entity = $(".caosdb-entity-panel")[0];
+            if (first_entity) {
+                window.localStorage["edit_mode"] = true;
+                after_setup_callback = () => {
+                    logger.debug("Edit this entity after #edit in the uri", first_entity);
+                    edit_mode.edit(first_entity);
+                }
+            }
+        } else if (this.has_new_record_fragment()) {
+            for (let entity of $(".caosdb-entity-panel")) {
+                // find first record type
+                if (getEntityRole(entity) === "RecordType") {
+                    window.localStorage["edit_mode"] = true;
+                    const new_record = edit_mode.create_new_record(getEntityID(entity));
+                    after_setup_callback = () => {
+                        logger.debug("Create a new record after #new_record in the uri", entity);
+                        new_record.then((new_record) => {
+                            edit_mode.app.newEntity(new_record);
+                        }, edit_mode.handle_error);
+                    }
+                    break;
+                }
+            }
+        }
+
+        // intialize the edit mode panel and add all necessary buttons if the edit mode is 
         if (this.is_edit_mode()) {
-            edit_mode.enter_edit_mode();
+
+            edit_mode.enter_edit_mode().then(after_setup_callback);
             edit_mode.toggle_edit_panel();
             // This is for the very specific case of reloading the
             // page while the edit mode is active on small screens
@@ -88,6 +129,9 @@ var edit_mode = new function () {
             $(".caosdb-edit-min-width-warning").addClass("d-block");
         }
         $('.caosdb-f-edit').css("transition", "top 1s");
+
+        // add drag-n-drop listener (needed for the edit_mode toolbox).
+        edit_mode.init_dragable();
     }
 
 
@@ -444,9 +488,6 @@ var edit_mode = new function () {
             file_path = getEntityPath(entity_form);
             file_checksum = getEntityChecksum(entity_form);
             file_size = getEntitySize(entity_form);
-            console.log(file_path);
-            console.log(file_checksum);
-            console.log(file_size);
         }
         return createEntityXML(
             entityRole,
@@ -580,7 +621,6 @@ var edit_mode = new function () {
             $(".caosdb-f-btn-toggle-edit-mode").text("Leave Edit Mode");
 
             edit_mode.init_tool_box();
-            edit_mode.init_dragable();
 
             var nextEditApp = editApp;
             if (typeof nextEditApp == "undefined") {
@@ -639,6 +679,7 @@ var edit_mode = new function () {
      * the model given by model.
      */
     this.init_tool_box = async function () {
+
         // remove previously added model
         $(".caosdb-f-edit-mode-existing").remove()
 
@@ -649,6 +690,12 @@ var edit_mode = new function () {
 
         removeAllWaitingNotifications(editPanel[0]);
         editPanel.children()[0].appendChild(model);
+
+        if (edit_mode.app && edit_mode.app.entity && edit_mode.app.entity.parentElement && (edit_mode.app.state === "changed")) {
+            // an entity is being editted
+            $(".caosdb-f-edit-mode-existing").toggleClass("d-none", false);
+            $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", true);
+        }
     }
 
 
@@ -1485,6 +1532,7 @@ var edit_mode = new function () {
             init_drag_n_drop();
         }
         app.onEnterInitial = async function (e) {
+
             $(".caosdb-f-edit-mode-existing").toggleClass("d-none", true);
             $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", false);
             app.old = undefined;
@@ -1559,6 +1607,7 @@ var edit_mode = new function () {
             }, edit_mode.handle_error);
         };
         app.onEnterChanged = function (e) {
+
             // show existing entities in toolbox
             $(".caosdb-f-edit-mode-existing").toggleClass("d-none", false);
             $(".caosdb-f-edit-mode-create-buttons").toggleClass("d-none", true);
diff --git a/src/core/js/ext_editmode_wysiwyg_text.js b/src/core/js/ext_editmode_wysiwyg_text.js
new file mode 100644
index 0000000000000000000000000000000000000000..380d8b1ec298ed0fa5c68af50b8b0e9dad333b89
--- /dev/null
+++ b/src/core/js/ext_editmode_wysiwyg_text.js
@@ -0,0 +1,118 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ * Copyright (C) 2021 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";
+
+/**
+ * Replaces textareas in the edit mode by a wysiwyg editor
+ *
+ * @module ext_editmode_wysiwyg_text
+ * @version 0.1
+ *
+ * @param jQuery - well-known library.
+ * @param log - singleton from loglevel library or javascript console.
+ * @param {class} ClassicEditor - ClassicEditor class from ckEditor
+ * @param {module} edit_mode - caosdb's edit-mode module
+ * @param {function} getPropertyElements - caosdb's function to extract the
+ *     property HTML elements from an entity HTML element
+ * @param {function} getPropertyDatatype - caosdb's function to extract the
+ *     data type from a property HTML element
+ * @param {function} getPropertyName - caosdb's function to extract the
+ *     name from a property HTML element
+ */
+var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, getPropertyElements, getPropertyDatatype, getPropertyName) {
+
+    var insertEditorInProperty = async function (prop) {
+        if (!(getPropertyDatatype(prop) === 'TEXT')) {
+            // Ignore anything that isn't a list property, even LIST<TEXT>
+            return;
+        }
+
+        try{
+            const editor = await ClassicEditor
+                .create(prop.querySelector('textarea'), {
+                    // use all plugins since we built the editor dependency to
+                    // contain only those we need.
+                    plugins: ClassicEditor.builtinPlugins,
+                    // Markdown needs a header row so enforce this
+                    table: {
+                        defaultHeadings: {
+                            rows: 1,
+                            columns: 0
+                        },
+                    },
+                })
+            logger.debug('Initialized editor for ' + getPropertyName(prop));
+            // Manually implement saving the data since edit mode is not
+            // a form to be submitted.
+            editor.model.document.on("change:data", (e) => {
+                editor.updateSourceElement();
+            });
+        } catch(error) {
+            logger.error(error.stack);
+        }
+    }
+
+    var replaceTextAreas = function (entity) {
+        const properties = getPropertyElements(entity);
+        for (let prop of properties) {
+            // TODO(fspreck): This will be replaced by a whitelist of properties
+            // in the future.
+            if (getPropertyName(prop)) {
+                insertEditorInProperty(prop);
+            }
+        }
+    }
+
+    var init = function () {
+
+        // Insert an editor into all TEXT properties of the entity which is
+        // being edited.
+        document.body.addEventListener(edit_mode.start_edit.type, (e) => {
+            logger.debug('Replacing text areas ...');
+            ext_editmode_wysiwyg_text.replaceTextAreas(e.target);
+        }, true);
+
+        // Listen to added properties and replace the textarea if necessary
+        document.body.addEventListener(edit_mode.property_added.type, (e) => {
+            logger.debug('Replacing textarea in ' + getPropertyName(e.target));
+            ext_editmode_wysiwyg_text.insertEditorInProperty(e.target);
+        }, true)
+
+        // Listen to properties, the data type of which has changed. Mainly
+        // because of change from list to scalar and vice versa.
+        document.body.addEventListener(edit_mode.property_data_type_changed.type, (e) => {
+            logger.debug('Re-rendering ' + getPropertyName(e.target));
+            ext_editmode_wysiwyg_text.insertEditorInProperty(e.target);
+        }, true);
+    };
+
+    return {
+        init: init,
+        replaceTextAreas: replaceTextAreas,
+        insertEditorInProperty: insertEditorInProperty,
+    };
+}($, log.getLogger("ext_editmode_wysiwyg_text"), ClassicEditor, edit_mode, getPropertyElements, getPropertyDatatype, getPropertyName);
+
+$(document).ready(() => {
+    if ("${BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT}" == "ENABLED") {
+        caosdb_modules.register(ext_editmode_wysiwyg_text);
+    }
+});
diff --git a/test/core/js/modules/ext_cosmetics.js.js b/test/core/js/modules/ext_cosmetics.js.js
index d5d4df7f10a2859bcd7318680d4f6720aedc6127..969c8297b8b5cf85d0a668d7c30e8b0f45e34d4d 100644
--- a/test/core/js/modules/ext_cosmetics.js.js
+++ b/test/core/js/modules/ext_cosmetics.js.js
@@ -46,13 +46,16 @@ QUnit.test("linkify - https", function (assert) {
         ["this is other text https://link", 1],
         ["this is other text https://link and here comes another link https://link and more text", 2],
     ];
+
     for (let test_case of test_cases) {
-        var text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
-        $(document.body).append(text_value);
-        assert.equal($(text_value).find("a[href='https://link']").length, 0, "no link present");
+        const container = $('<div></div>');
+        $(document.body).append(container);
+        const text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
+        container.append(text_value);
+        assert.equal($(container).find("a[href='https://link']").length, 0, "no link present");
         cosmetics.linkify();
-        assert.equal($(text_value).find("a[href='https://link']").length, test_case[1], "link is present");
-        text_value.remove();
+        assert.equal($(container).find("a[href='https://link']").length, test_case[1], "link is present");
+        container.remove();
     }
 });
 
@@ -66,22 +69,26 @@ QUnit.test("linkify - http", function (assert) {
         ["this is other text http://link and here comes another link http://link and more text", 2],
     ];
     for (let test_case of test_cases) {
-        var text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
-        $(document.body).append(text_value);
-        assert.equal($(text_value).find("a[href='http://link']").length, 0, "no link present");
+        const container = $('<div></div>');
+        $(document.body).append(container);
+        const text_value = $(`<div class="caosdb-f-property-text-value">${test_case[0]}</div>`);
+        $(container).append(text_value);
+        assert.equal($(container).find("a[href='http://link']").length, 0, "no link present");
         cosmetics.linkify();
-        assert.equal($(text_value).find("a[href='http://link']").length, test_case[1], "link is present");
-        text_value.remove();
+        assert.equal($(container).find("a[href='http://link']").length, test_case[1], "link is present");
+        container.remove();
     }
 });
 
 QUnit.test("linkify cut-off (40)", function (assert) {
-    var test_case = "here is some text https://this.is.a.link/with/more/than/40/characters/ this is more text";
-    var text_value = $(`<div class="caosdb-f-property-text-value">${test_case}</div>`);
-    $(document.body).append(text_value);
-    assert.equal($(text_value).find("a").length, 0, "no link present");
+    const container = $('<div></div>');
+    $(document.body).append(container);
+    const test_case = "here is some text https://this.is.a.link/with/more/than/40/characters/ this is more text";
+    const text_value = $(`<div class="caosdb-f-property-text-value">${test_case}</div>`);
+    $(container).append(text_value);
+    assert.equal($(container).find("a").length, 0, "no link present");
     cosmetics.linkify();
-    assert.equal($(text_value).find("a").length, 1, "link is present");
-    assert.equal($(text_value).find("a").text(), "https://this.is.a.link/with/more/th[...] ", "link text has been cut off");
-    text_value.remove();
-});
\ No newline at end of file
+    assert.equal($(container).find("a").length, 1, "link is present");
+    assert.equal($(container).find("a").text(), "https://this.is.a.link/with/more/th[...] ", "link text has been cut off");
+    container.remove();
+});