diff --git a/misc/revision_test_data.py b/misc/revision_test_data.py
new file mode 100755
index 0000000000000000000000000000000000000000..0f41fa9c1b748be0cbc6c5b917dc6739d3c21d89
--- /dev/null
+++ b/misc/revision_test_data.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Copyright 2020 IndiScale GmbH <info@indiscale.com>
+Copyright 2020 Timm Fitschen <t.fitschen@indiscale.com>
+"""
+
+import caosdb
+import random
+import os
+
+# data model
+c = caosdb.execute_query("FIND Test*")
+if len(c) > 0:
+    print(c)
+    delete = input("Delete these entities?\nType `yes`:")
+    if delete == "yes":
+        c.delete();
+    else:
+        print("You typed `{}`".format(delete))
+        print("[Canceled]")
+        exit(0)
+
+
+print("inserting test data")
+
+upload_file = open("test.dat", "w")
+upload_file.write("hello world\n")
+upload_file.close()
+
+testdata = caosdb.Container()
+testdata.extend([
+    caosdb.File("TestFile",
+        path="test.dat",
+        file="test.dat"),
+    caosdb.Property("TestRevisionOf", datatype="TestObsolete"),
+    caosdb.RecordType("TestObsolete"),
+    caosdb.RecordType("TestRecordType"),
+    caosdb.Property("TestProperty", datatype=caosdb.TEXT),
+    caosdb.Record("TestRecord"
+                 ).add_parent("TestRecordType"
+                 ).add_property("TestProperty", "this is a test"),
+])
+
+testdata.insert()
+os.remove("test.dat")
diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js
index 98d4feae395f47a0eaecb420ed99f231f4fbf70b..648e8249a258a9f723325ec34c230e07a5900514 100644
--- a/src/core/js/caosdb.js
+++ b/src/core/js/caosdb.js
@@ -183,10 +183,12 @@ function getEntityDatatype(element) {
  * @return {string} the data type
  */
 function getPropertyDatatype(element) {
-    var dt_elem = $(element).find(".caosdb-property-datatype");
+    var dt_elem = findElementByConditions(element,
+        x => x.classList.contains("caosdb-property-datatype"),
+        x => x.classList.contains("caosdb-preview-container"));
 
     if(dt_elem.length == 1){
-        return dt_elem.text();
+        return $(dt_elem[0]).text();
     } else if (dt_elem.length > 1){
         throw new Error("The datatype of this property could not uniquely be determined.");
     }
diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js
index 96ff91d2d9a2a6e153b56e8f6b66b6669f93a70b..b68d8ca4d9d5d309f3374c3717fe001806241173 100644
--- a/src/core/js/edit_mode.js
+++ b/src/core/js/edit_mode.js
@@ -151,8 +151,13 @@ var edit_mode = new function() {
         }
     }
 
-    this.property_drop_listener = function(e) { edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_property); }
-    this.parent_drop_listener = function(e) { edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_parent); }
+    this.property_drop_listener = function(e) {
+        edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_property); 
+    }
+
+    this.parent_drop_listener = function(e) {
+        edit_mode._drop_listener.call(this, e, edit_mode.add_dropped_parent);
+    }
 
     this._drop_listener = function(e, add_cb) {
         e.preventDefault();
@@ -308,11 +313,6 @@ var edit_mode = new function() {
     }
 
 
-    this.update_entity_by_id = async function(ent_id) {
-        var ent_element = $("#" + ent_id)[0];
-        return this.update_entity(ent_element);
-    }
-
     /**
      * Insert entities.
      *
@@ -426,9 +426,11 @@ var edit_mode = new function() {
             getEntityDescription(ent_element),
             getEntityUnit(ent_element),
         );
-        return await update(xml);
+        return await edit_mode.update(xml);
     }
 
+    this.update = update;
+
     this.add_edit_mode_button = function(target, toggle_function) {
         var edit_mode_li = $('<li><button class="navbar-btn btn btn-link caosdb-f-btn-toggle-edit-mode">Edit Mode</button></li>');
         $(target).append(edit_mode_li);
@@ -1239,6 +1241,8 @@ var edit_mode = new function() {
             edit_mode.unhighlight();
             app.old = entity;
             app.entity = $(entity).clone(true)[0];
+            // remove preview stuff
+            $(app.entity).find(".caosdb-preview-container").remove();
             edit_mode.smooth_replace(app.old, app.entity);
 
             edit_mode.add_save_button(app.entity, () => app.update(app.entity));
diff --git a/src/core/js/ext_revisions.js b/src/core/js/ext_revisions.js
new file mode 100644
index 0000000000000000000000000000000000000000..cf0da0c78a83b87c38ecfdce9e3d576328e2b04c
--- /dev/null
+++ b/src/core/js/ext_revisions.js
@@ -0,0 +1,227 @@
+/*
+ * ** header v3.0
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2019 IndiScale GmbH (info@indiscale.com)
+ * Copyright (C) 2019 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';
+
+/**
+ * The ext_revisions module extends the edit_mode update functionality.
+ *
+ * The edit_mode.update_entity function is overridden by this module with a
+ * proxy pattern. That means, that the original function is still called, but a
+ * proxy (or wrapper) function adds further functionality.
+ *
+ * The extended update function creates a back-up version of the updated entity
+ * and and adds a revisionOf property to the updated entity which references
+ * the back-up. The back-up entity loses all of its original parents and gets
+ * an "Obsolete" as only parent instead.
+ *
+ * Per default, the module assumes two Entities to be present in the
+ * database. A RecordType named "Obsolete" and a Property named
+ * "revisionOf". The initialization is aborted if these entities cannot be
+ * found and the module remains inactive.
+ *
+ * @module ext_revisions
+ * @version 0.1
+ *
+ * @requires jQuery
+ * @requires log
+ * @requires edit_mode
+ * @requires getEntityID
+ * @requires transaction
+ * @requires _createDocument
+ */
+var ext_revisions = function ($, logger, edit_mode, getEntityID, transaction, _createDocument) {
+
+
+    /**
+     * Default names for the two entities which are required by this module.
+     *
+     * TODO: rename
+     */
+    var _datamodel = { obsolete: "Obsolete", revisionOf: "revisionOf" };
+
+    /**
+     * Generate and insert the back-up entity which stores the old state of the
+     * entity which is to be updated.
+     *
+     * The obsolete entity has only one parent named "Obsolete".
+     * Apart from that, the obsolete entity has all the properties, name,
+     * description and so on from the original entity before the update.
+     *
+     * @param {string} id - the id of the entity which is to be updated.
+     * @returns {string} the id of the newly created obsolete entity.
+     */
+    var _insert_obsolete = async function (id) {
+        logger.debug("insert obsolete", id);
+
+        // create new obsolete entity from the original
+        var obsolete = await transaction.retrieveEntityById(id);
+        $(obsolete).attr("id", "-1");
+        $(obsolete).find("Permissions").remove();
+        $(obsolete).find("Parent").remove();
+        $(obsolete).append(`<Parent name="${_datamodel.obsolete}"/>`);
+
+        var doc = _createDocument("Request");
+        doc.firstElementChild.appendChild(obsolete);
+        var result = await transaction.insertEntitiesXml(doc);
+        return $(result.firstElementChild).find("[id]").first().attr("id");
+    };
+
+    /**
+     * Generate a HTML string which represents a new "revisionOf" property
+     * which references the newly created obsolete entity. The property is
+     * meant to be appended to the property section of the entity which is to
+     * be updated.
+     *
+     * @param {string} obsolete_id - the id of the newly created obsolete
+     *     entity.
+     * @returns {string} A HTML represesentation of an entity property.
+     */
+    var _make_revision_of_property = async function (obsolete_id) {
+        logger.debug("_make_revision_of_property", obsolete_id);
+        var ret = (await transformation.transformProperty(str2xml(`<Response><Property id="${_datamodel._revisionOfId}" name="${_datamodel.revisionOf}" datatype="${_datamodel.obsolete}"></Property></Response>`))).firstElementChild;
+
+        $(ret).append(`<div class="caosdb-property-edit-value"><select><option value="${obsolete_id}" selected="selected"></option></select></div>`);
+
+        return ret;
+    }
+
+    /**
+     * Remove all properties from ent_element with the id of the "revisionOf"
+     * property.
+     *
+     * @param {HTMLElement} ent_element - entity in HTML representation.
+     */
+    var _remove_old_revision_of_property = function (ent_element) {
+        $(ent_element)
+            .find(".caosdb-f-entity-property")
+            .filter(function(index, property) {
+                if(_datamodel._revisionOfId === $(property)
+                    .find(".caosdb-property-id").text()) {
+                    return true;
+                }
+                return false;
+        }).remove();
+    }
+
+    /**
+     * Main functionality of this module is in here.
+     *
+     * This method is called by the (overridden) edit_mode.update_entity
+     * function before the actual update. It inserts a new obsolete entity
+     * which represesents the old state of the entity, deletes any revisionOf
+     * properties of the entity (if present) and adds a new revisionOf property
+     * which references the (newly inserted) obsolete entity.
+     *
+     * @param {HTMLElement} ent_element - The entity form which has been
+     * generated by the edit_mode with the changes by the user.
+     */
+    var _create_revision = async function (ent_element) {
+        logger.debug("create revision", ent_element);
+        var id = getEntityID(ent_element);
+
+        var obsolete_id = await _insert_obsolete(id);
+
+        // remove old revision of and add new one
+        _remove_old_revision_of_property(ent_element);
+        var revision_of_property = await _make_revision_of_property(obsolete_id);
+        var properties_section = ent_element.getElementsByClassName("caosdb-properties")[0];
+        properties_section.appendChild(revision_of_property);
+    };
+
+    /**
+     * Test whether the necessary entities exist ("revisionOf" and "Obsolete").
+     */
+    var _check_datamodel = async function() {
+        var results = Promise.all([
+            query(`FIND RecordType ${_datamodel.obsolete}`),
+            query(`FIND Property ${_datamodel.revisionOf}`)
+        ]);
+
+        for (let result of (await results)) {
+            if (result.length !== 1) {
+                throw new Error("Invalid datamodel");
+            }
+
+            var name = getEntityName(result[0]);
+            if (name && name.toLowerCase() === _datamodel.revisionOf.toLowerCase()) {
+                _datamodel._revisionOfId = getEntityID(result[0]);
+                _datamodel.revisionOf = name;
+            } else if (name && name.toLowerCase() === _datamodel.obsolete.toLowerCase()) {
+                _datamodel.obsolete = name;
+            }
+        }
+    };
+
+    /**
+     * Initialize the ext_revisions module.
+     *
+     * Per default, the module assumes two Entities to be present in the
+     * database. A RecordType named "Obsolete" and a Property named
+     * "revisionOf". The initialization is aborted if these entities cannot be
+     * found and the module remains inactive. For testing purposes the names of
+     * these entities can be set to different values via the respective
+     * parameters.
+     *
+     * @param {string} [obsolete] - The name of the obsolete RecordType.
+     * @param {string} [revisionOf] - The name of the revisionOf Property.
+     */
+    var init = async function (obsolete, revisionOf) {
+        if (typeof obsolete === "string") {
+            _datamodel.obsolete = obsolete;
+        }
+        if (typeof revisionOf === "string") {
+            _datamodel.revisionOf = revisionOf;
+        }
+
+        try {
+            await _check_datamodel();
+        } catch (err) {
+            logger.error("could not init ext_revisions", err);
+            return;
+        }
+
+        (function(proxied) {
+            edit_mode.update_entity = async function(ent_element) {
+                await _create_revision(ent_element);
+                return await proxied.apply(this, arguments)
+            };
+        })(edit_mode.update_entity);
+    }
+
+    return {
+        // public members, part of the API
+        init: init,
+        // private members, exposed for testing
+        _make_revision_of_property: _make_revision_of_property,
+        _datamodel: _datamodel,
+    }
+}($, log.getLogger("ext_revisions"), edit_mode, getEntityID, transaction, _createDocument);
+
+
+// this will be replaced by require.js in the future.
+$(document).ready(function () {
+    if ("${BUILD_MODULE_EXT_REVISIONS}" == "ENABLED") {
+        caosdb_modules.register(ext_revisions);
+    }
+});
diff --git a/src/core/xsl/main.xsl b/src/core/xsl/main.xsl
index dc4cc0e69add1cff3210677e53a4f513dca710c1..f4a128f63f52488657b32e8ff03043c276d4aaa3 100644
--- a/src/core/xsl/main.xsl
+++ b/src/core/xsl/main.xsl
@@ -235,6 +235,11 @@
         <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_bottom_line.js')"/>
       </xsl:attribute>
     </xsl:element>
+    <xsl:element name="script">
+      <xsl:attribute name="src">
+        <xsl:value-of select="concat($basepath,'webinterface/${BUILD_NUMBER}/js/ext_revisions.js')"/>
+      </xsl:attribute>
+    </xsl:element>
     <!--JS_EXTENSIONS-->
   </xsl:template>
   <xsl:template name="caosdb-data-container">
diff --git a/test/core/index.html b/test/core/index.html
index b2ec4fceac7f4a78c621046647e55225c1cfb11b..0d97a415334fd441319eec7e4db262c34d64ef07 100644
--- a/test/core/index.html
+++ b/test/core/index.html
@@ -65,6 +65,7 @@
   <script src="js/proj4leaflet.js"></script>
   <script src="js/ext_map.js"></script>
   <script src="js/ext_bottom_line.js"></script>
+  <script src="js/ext_revisions.js"></script>
   <script src="js/autocomplete.js"></script>
   <!--EXTENSIONS-->
   <script src="js/modules/webcaosdb.js.js"></script>
@@ -82,6 +83,7 @@
   <script src="js/modules/ext_references.js.js"></script>
   <script src="js/modules/ext_map.js.js"></script>
   <script src="js/modules/ext_bottom_line.js.js"></script>
+  <script src="js/modules/ext_revisions.js.js"></script>
   <script src="js/modules/autocomplete.js.js"></script>
 </body>
 </html>
diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js
index 61058a4301cc43c6f6da56999bcfccd320d92424..c6326e4400750f75ef0b939990a941c2f706da2e 100644
--- a/test/core/js/modules/edit_mode.js.js
+++ b/test/core/js/modules/edit_mode.js.js
@@ -169,10 +169,6 @@ QUnit.test("add_property_trash_button", function(assert){
     assert.ok(edit_mode.add_property_trash_button);
 });
 
-QUnit.test("update_entity_by_id", function(assert){
-    assert.ok(edit_mode.update_entity_by_id);
-});
-
 QUnit.test("insert_entity", function(assert){
     assert.ok(edit_mode.insert_entity);
 });
diff --git a/test/core/js/modules/ext_revisions.js.js b/test/core/js/modules/ext_revisions.js.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd9787afec3039984e3b067638c9ae6a9a3fd422
--- /dev/null
+++ b/test/core/js/modules/ext_revisions.js.js
@@ -0,0 +1,118 @@
+/*
+ * ** 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';
+
+var ext_revisions_test_suite = function ($, ext_revisions, QUnit, edit_mode) {
+
+    var datamodel = ext_revisions._datamodel;
+
+    QUnit.module("ext_revisions.js", {
+        before: function (assert) {
+            // setup before module
+            this.original_update_entity = edit_mode.update_entity;
+            this.original_insert = transaction.insertEntitiesXml;
+            this.original_retrieve = transaction.retrieveEntityById;
+            this.original_query = query;
+        },
+        beforeEach: function (assert) {
+            // setup before each test
+            datamodel.obsolete = "UNITTESTObsolete";
+            datamodel.revisionOf = "UNITTESTRevisionOf";
+        },
+        afterEach: function (assert) {
+            // teardown after each test
+            query = this.original_query;
+            edit_mode.update_entity = this.original_update_entity;
+            transaction.insertEntitiesXml = this.original_insert;
+            transaction.retrieveEntityById = this.original_retrieve;
+        },
+        after: function (assert) {
+            // teardown after module
+        }
+    });
+
+    QUnit.test("_make_revision_of_property", async function(assert) {
+        var p = await ext_revisions._make_revision_of_property("1234");
+        var editfield = $(p).find(".caosdb-property-edit-value");
+        var value = $(editfield).find("select").first()[0].selectedOptions[0].value;
+        assert.ok($(p).hasClass("caosdb-f-entity-property"), "is property");
+        assert.equal(value, "1234", "has value 1234");
+        assert.equal(getPropertyName(p), datamodel.revisionOf, "has revisionOf name");
+        assert.equal(getPropertyDatatype(p), datamodel.obsolete, "has Obsolete datatype");
+    });
+
+    /**
+     * This is a rather complete test, not a unit test.
+     */
+    QUnit.test("update calls update_entity through proxy", async function (assert) {
+        var done = assert.async(3);
+        var done_query = assert.async(2);
+        var ent_element = $('<div data-entity-id="15"><div class="caosdb-properties"/></div>')[0];
+
+        // mock server responses to several requests...
+        var retrieve_fun = async function(id) {
+            assert.equal(id, "15", "retrieve id 15");
+            done();
+            return $(`<Record id="15"><Parent name="ORIG_PARENT"/></Record>`)[0];
+        }
+        var insert_fun = async function(xml) {
+            var rec = xml.firstElementChild.firstElementChild;
+            assert.equal(rec.id, "-1", "insert with tmp id");
+            assert.equal($(rec).find("Parent").attr("name"), datamodel.obsolete, "Obsolete Parent");
+            xml.firstElementChild.firstElementChild.id = "2345";
+            done();
+            return xml;
+        };
+        var update_fun = async function(ent_element) {
+            var prop = edit_mode.getProperties(ent_element)[0];
+            assert.equal(prop.name, datamodel.revisionOf, "has revisionOf");
+            assert.equal(prop.value, "2345", "revisionOf 2345");
+            done();
+        };
+        var query_fun = async function(query) {
+            assert.ok(query.startsWith("FIND") && ( query.endsWith(datamodel.obsolete) || query.endsWith(datamodel.revisionOf)), query);
+            done_query(); // called twice
+            return [$(`<div data-entity-name="${datamodel.revisionOf}" data-caosdb-id="3456"/>`)[0]];
+        }
+
+        // injecting the server mock-up responses.
+        transaction.retrieveEntityById = retrieve_fun;
+        transaction.insertEntitiesXml = insert_fun;
+        edit_mode.update_entity = update_fun;
+        query = query_fun;
+
+
+        // actual tests
+        assert.equal(update_fun, edit_mode.update_entity, "before init, the edit_mode.update_entity function has not been overridden.");
+
+        // call init which checks the datamodel and overwrites the
+        // edit_mode.update_entity function.
+        await ext_revisions.init();
+        assert.notEqual(update_fun, edit_mode.update_entity, "after init, the edit_mode.update_entity hab been overriden with a proxy calling the update_fun and the original function.");
+
+        // call edit_mode.update_entity which calls the insert_fun and the
+        // update_fun
+        await edit_mode.update_entity(ent_element);
+    });
+
+}($, ext_revisions, QUnit, edit_mode);