diff --git a/editor/editor.html b/editor/editor.html
index d45958ec1204a133442fedfbc3e2127ff5154ef0..527abd44ef3fc12f8ba68f1d8b699bd7f4fa04e9 100644
--- a/editor/editor.html
+++ b/editor/editor.html
@@ -29,6 +29,7 @@
     <script src="%WWW%editor/js/tools/selecttool.js"></script>
     <script src="%WWW%editor/js/tools/collecttool.js"></script>
     <script src="%WWW%editor/js/tools/deletetool.js"></script>
+    <script src="%WWW%editor/js/tools/addnodetool.js"></script>
     <script src="%WWW%editor/js/display.js"></script>
     <script src="%WWW%editor/js/state.js"></script>
     <script src="%WWW%editor/js/editor.js"></script>
diff --git a/editor/images/tools/addnode.png b/editor/images/tools/addnode.png
new file mode 100644
index 0000000000000000000000000000000000000000..a899e0a587dd2538612438fbac1f40e060ece5b0
Binary files /dev/null and b/editor/images/tools/addnode.png differ
diff --git a/editor/js/editor.js b/editor/js/editor.js
index c9b0694ee2a652b8070fcacbcfcba8cc49a1f79d..be00eeaa97d648f69c25a889797f75e267be6b66 100644
--- a/editor/js/editor.js
+++ b/editor/js/editor.js
@@ -35,7 +35,7 @@ function downloadJson() {
 
 function extractPositions(event) {
     return {
-        graph: { x: event.layerX, y: event.layerY },
+        graph: graphObj.screen2GraphCoords(event.layerX, event.layerY),
         window: { x: event.clientX, y: event.clientY },
     };
 }
@@ -57,8 +57,14 @@ function load() {
         .linkDirectionalParticleWidth((link) =>
             state.linkDirectionalParticleWidth(link)
         )
-        .onBackgroundClick((event) => state.onBackgroundClick(event, extractPositions(event)))
+        .onBackgroundClick((event) =>
+            state.onBackgroundClick(event, extractPositions(event))
+        )
         .nodeCanvasObjectMode((node) => state.nodeCanvasObjectMode(node))
         .nodeCanvasObject((node, ctx) => state.nodeCanvasObject(node, ctx))
         .onLinkClick((link) => state.onLinkClick(link));
+
+    graph.externUpdate.push(() => {
+        graphObj.graphData(graph.data);
+    });
 }
diff --git a/editor/js/graph.js b/editor/js/graph.js
index 52fffdc693061eeefdfa1362a3c78a6235c39fd1..a8d3d4095dbc985562d40dc69f2ccba09fdc204b 100644
--- a/editor/js/graph.js
+++ b/editor/js/graph.js
@@ -13,7 +13,7 @@ const GRAPH_NODES = "nodes";
 const GRAPH_LINKS = "links";
 
 const IMAGE_SIZE = 12;
-const IMAGE_SRC = PLUGIN_PATH + "datasets/images/"
+const IMAGE_SRC = PLUGIN_PATH + "datasets/images/";
 
 const LINK_PARAMS = [LINK_TYPE];
 const NODE_PARAMS = [NODE_ID, NODE_LABEL, NODE_IMAGE, NODE_DESCRIPTION];
@@ -22,10 +22,13 @@ const JSON_CONFIG = PLUGIN_PATH + "datasets/aud1.json";
 
 const STOP_PHYSICS_DELAY = 5000; // ms
 
-
-
 const graph = {
     data: undefined,
+    externUpdate: [],
+
+    update() {
+        graph.externUpdate.forEach((fn) => fn());
+    },
 
     deleteNode(nodeId) {
         // Delete node from nodes
@@ -159,4 +162,60 @@ const graph = {
 
         return cleanLink;
     },
+
+    existsNodeId(nodeId) {
+        var nodes = graph.data[GRAPH_NODES];
+        for (var i = 0; i < nodes.length; i++) {
+            if (nodes[i][NODE_ID] === nodeId) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    getUnusedNodeId() {
+        var id;
+        do {
+            id = graph.getRandomString();
+        } while (graph.existsNodeId(id));
+        return id;
+    },
+
+    getRandomString(length = 8) {
+        // Based on: https://stackoverflow.com/a/1349426/7376120
+        var characters =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        var charactersLength = characters.length;
+
+        var result = "";
+        for (var i = 0; i < length; i++) {
+            result += characters.charAt(
+                Math.floor(Math.random() * charactersLength)
+            );
+        }
+        return result;
+    },
+
+    addNode(nodeDetails) {
+        // Copy params
+        var newNode = nodeDetails;
+
+        // Make sure the ID is set and unique
+        if (newNode[NODE_ID] === undefined) {
+            newNode[NODE_ID] = graph.getUnusedNodeId();
+        } else if (graph.existsNodeId(newNode[NODE_ID])) {
+            return;
+        }
+
+        // Basic node properties
+        newNode.node = true;
+        newNode.link = false;
+        newNode.index = graph.data[GRAPH_NODES].length;
+
+        // Add node
+        graph.data[GRAPH_NODES].push(newNode);
+        graph.update();
+
+        return newNode;
+    },
 };
diff --git a/editor/js/state.js b/editor/js/state.js
index 9370f8d140743a7e1d34ff2b16548d1048383cf3..a58d25332e55150220ad2ed0b38eb6fd925fb6dc 100644
--- a/editor/js/state.js
+++ b/editor/js/state.js
@@ -2,6 +2,7 @@ const TOOLS = {
     select: new SelectTool("select"),
     collect: new CollectTool("collect"),
     delete: new DeleteTool("delete"),
+    addnode: new AddNodeTool("addnode"),
 };
 
 const CONTEXT = {
@@ -184,7 +185,6 @@ class State extends Tool {
     }
 
     onBackgroundClick(event, positions) {
-        console.log(event);
         this.tool.onBackgroundClick(event, positions);
     }
 
diff --git a/editor/js/tools/addnodetool.js b/editor/js/tools/addnodetool.js
new file mode 100644
index 0000000000000000000000000000000000000000..c72c189d8ac795d2ab22be6553476e07dc359a76
--- /dev/null
+++ b/editor/js/tools/addnodetool.js
@@ -0,0 +1,22 @@
+class AddNodeTool extends Tool {
+    constructor(key) {
+        super("Add node", "addnode", key);
+    }
+
+    onBackgroundClick(event, positions) {
+        var node = {};
+
+        // Set position
+        node.fx = positions.graph.x;
+        node.fy = positions.graph.y;
+
+        var node = graph.addNode(node);
+
+        if (node === undefined) {
+            console.error("Couldn't add new node");
+            return;
+        }
+
+        state.setSelectedItem(node);
+    }
+}
\ No newline at end of file