diff --git a/frontend-js/package-lock.json b/frontend-js/package-lock.json
index 806db19df37adfc12420410e385f2c0ed8308c75..8ca24c0f2abdfdbdbe76cfbfb890cb063abab86e 100644
--- a/frontend-js/package-lock.json
+++ b/frontend-js/package-lock.json
@@ -1167,6 +1167,14 @@
         "cssom": "0.3.2"
       }
     },
+    "csv-stringify": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-4.3.1.tgz",
+      "integrity": "sha512-VRjPYIUzex5kfbsOY7LaJcNE2qMWGQQAanb3/Vv85WbOgA+dAfDNfwntRvv335icJgGYrnTX403WxJxRVpLDFA==",
+      "requires": {
+        "lodash.get": "4.4.2"
+      }
+    },
     "cycle": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
@@ -3210,6 +3218,11 @@
         "lodash._isiterateecall": "3.0.9"
       }
     },
+    "lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
+    },
     "lodash.isarguments": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
diff --git a/frontend-js/package.json b/frontend-js/package.json
index a2588cdc805c28bfaf19249ffce1299093e15b8d..784d067b99236b36e02d3a5b36b945b7e9b73ec6 100644
--- a/frontend-js/package.json
+++ b/frontend-js/package.json
@@ -39,6 +39,7 @@
     "uglifyjs": "^2.4.10"
   },
   "dependencies": {
+    "csv-stringify": "^4.0.0",
     "datatables.net-rowreorder": "^1.2.5",
     "dual-listbox": "1.0.7",
     "file-saver": "^1.3.8",
diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js
index 3fe2bccb564e98d46aae66166208362e35ef70d6..a280321ccfd922715e01fcadecc34a74ce26bcac 100644
--- a/frontend-js/src/main/js/ServerConnector.js
+++ b/frontend-js/src/main/js/ServerConnector.js
@@ -1604,7 +1604,7 @@ ServerConnector.getMaxSearchDistance = function () {
  *
  * @param {number} overlayId
  * @param {string} projectId
- * @returns {PromiseLike<DataOverlay>}
+ * @returns {PromiseLike<DataOverlay>|Promise<DataOverlay>}
  */
 ServerConnector.getOverlayById = function (overlayId, projectId) {
   var self = this;
diff --git a/frontend-js/src/main/js/gui/leftPanel/PublicationListDialog.js b/frontend-js/src/main/js/gui/leftPanel/PublicationListDialog.js
index 23a09b5aef797a253d6927957a824b491a931ab2..f98d955f51547ccfbf530460df515e8f99d330a3 100644
--- a/frontend-js/src/main/js/gui/leftPanel/PublicationListDialog.js
+++ b/frontend-js/src/main/js/gui/leftPanel/PublicationListDialog.js
@@ -11,8 +11,11 @@ var IdentifiedElement = require('../../map/data/IdentifiedElement');
 var Reaction = require('../../map/data/Reaction');
 
 var Functions = require('../../Functions');
+// noinspection JSUnusedLocalSymbols
 var logger = require('../../logger');
 
+var stringify = require('csv-stringify');
+
 /**
  *
  * @param {Object} params
@@ -40,14 +43,14 @@ PublicationListDialog.prototype.createPublicationListDialogGui = function () {
   var self = this;
   var head = Functions.createElement({
     type: "thead",
-    content: "<tr>" + "<th>Pubmed ID</th>" + 
-    "<th>Title</th>" + 
-    "<th>Authors</th>" + 
-    "<th>Journal</th>" + 
-    "<th>Year</th>" + 
-    "<th>Elements on map</th>" + 
-    "<th>Submaps</th>" + 
-    "</tr>"
+    content: "<tr>" + "<th>Pubmed ID</th>" +
+      "<th>Title</th>" +
+      "<th>Authors</th>" +
+      "<th>Journal</th>" +
+      "<th>Year</th>" +
+      "<th>Elements on map</th>" +
+      "<th>Submaps</th>" +
+      "</tr>"
   });
   var body = Functions.createElement({
     type: "tbody"
@@ -74,7 +77,7 @@ PublicationListDialog.prototype.createPublicationListDialogGui = function () {
  */
 PublicationListDialog.prototype._dataTableAjaxCall = function (data, callback) {
   var self = this;
-  return ServerConnector.getPublications({
+  return self.getServerConnector().getPublications({
     start: data.start,
     length: data.length,
     sortColumn: self.getColumnsDefinition()[data.order[0].column].name,
@@ -89,7 +92,6 @@ PublicationListDialog.prototype._dataTableAjaxCall = function (data, callback) {
 
       var row = [];
       var submaps = {};
-      submaps[self.getMap().getId()] = true;
       row[0] = "<a href='" + publication.link + "'>" + publication.id + "</a>";
       row[1] = publication.title;
       row[2] = publication.authors.join();
@@ -174,6 +176,35 @@ PublicationListDialog.prototype.show = function () {
         ajax: function (data, callback, settings) {
           resolve(self._dataTableAjaxCall(data, callback, settings));
         },
+        dom: 'Bfrtip',
+        buttons: [
+          {
+            text: 'CSV',
+            action: function (e, data) {
+              GuiConnector.showProcessing();
+              return self.getServerConnector().getPublications({
+                start: 0,
+                length: data.page.info().recordsDisplay,
+                sortColumn: self.getColumnsDefinition()[data.order()[0][0]].name,
+                sortOrder: data.order()[0][1],
+                search: data.search()
+              }).then(function (publicationList) {
+                return self.publicationListAsCsvString(publicationList);
+              }).then(function (result) {
+                var blob = new Blob([result], {
+                  type: "text/plain;charset=utf-8"
+                });
+                var FileSaver = require("file-saver");
+                return FileSaver.saveAs(blob, "publications.csv");
+              }).catch(function(error){
+                GuiConnector.alert(error);
+              }).finally(function(){
+                GuiConnector.hideProcessing();
+              });
+
+            }
+          }
+        ],
         columns: self.getColumnsDefinition()
       });
     });
@@ -223,4 +254,69 @@ PublicationListDialog.prototype.destroy = function () {
   }
 };
 
+/**
+ *
+ * @returns {Promise}
+ */
+PublicationListDialog.prototype.publicationListToArray = function (publicationList) {
+  var self = this;
+  var result = [];
+
+  var elementsToFetch = [];
+
+  publicationList.data.map(function (entry) {
+    var publication = entry.publication.article;
+    var elements = entry.elements;
+    for (var j = 0; j < elements.length; j++) {
+      elementsToFetch.push(new IdentifiedElement(elements[j]));
+    }
+  });
+  return self.getProject().getBioEntitiesByIdentifiedElements(elementsToFetch).then(function(){
+    return Promise.all(publicationList.data.map(function (entry) {
+      var publication = entry.publication.article;
+      var elements = entry.elements;
+      var row = [];
+      row[0] = publication.id;
+      row[1] = publication.title;
+      row[2] = publication.authors.join();
+      row[3] = publication.journal;
+      row[4] = publication.year;
+      row[5] = "";
+      row[6] = "";
+
+      var submaps = [];
+      for (var j = 0; j < elements.length; j++) {
+        var modelId = elements[j].modelId;
+        if (submaps[modelId] === undefined) {
+          row[6] += self.getMap().getSubmapById(modelId).getModel().getName() + ", ";
+          submaps[elements[j].modelId] = true;
+        }
+      }
+      return Promise.all(elements.map(function (element) {
+        var model = self.getMap().getSubmapById(element.modelId).getModel();
+        return model.getByIdentifiedElement(new IdentifiedElement(element)).then(function (reaction) {
+          row[5] += reaction.getElementId() + ",";
+        });
+      })).then(function () {
+        result.push(row);
+      });
+    }));
+  }).then(function () {
+    return result;
+  });
+
+};
+
+PublicationListDialog.prototype.publicationListAsCsvString = function (publicationList) {
+  var self = this;
+
+  return self.publicationListToArray(publicationList).then(function(data){
+    return new Promise(function(resolve){
+      stringify(data,function(err, output){
+        resolve(output);
+      });
+    });
+  });
+};
+
 module.exports = PublicationListDialog;
diff --git a/frontend-js/src/main/js/map/data/Project.js b/frontend-js/src/main/js/map/data/Project.js
index 16884ab0d22979a3879a7e35a587e81094e2fa4e..eaf88c554d57672371201be2d271971168e68799 100644
--- a/frontend-js/src/main/js/map/data/Project.js
+++ b/frontend-js/src/main/js/map/data/Project.js
@@ -315,7 +315,7 @@ Project.prototype.getDisease = function () {
 
 /**
  *
- * @param {AnnotationOptions} disease
+ * @param {AnnotationOptions|null} disease
  */
 Project.prototype.setDisease = function (disease) {
   if (disease !== undefined && disease !== null) {
@@ -335,7 +335,7 @@ Project.prototype.getOrganism = function () {
 
 /**
  *
- * @param {AnnotationOptions} organism
+ * @param {AnnotationOptions|null} organism
  */
 Project.prototype.setOrganism = function (organism) {
   if (organism !== undefined && organism !== null) {
@@ -553,5 +553,34 @@ Project.prototype.getElementsPointingToSubmap = function (modelId) {
   });
 };
 
+/**
+ *
+ * @param {IdentifiedElement[]} elements
+ * @param {boolean} complete
+ * @returns {Promise}
+ */
+Project.prototype.getBioEntitiesByIdentifiedElements = function (elements, complete) {
+  var self = this;
+  var elementsByModelId = [];
+  var i;
+  for (i = 0; i < elements.length; i++) {
+    var element = elements[i];
+    var modelId = element.getModelId();
+    if (elementsByModelId[modelId] === undefined) {
+      elementsByModelId[modelId] = [];
+    }
+    elementsByModelId[modelId].push(element);
+  }
+
+  var models = self.getModels();
+  var promises = [];
+  for (i = 0; i < models.length; i++) {
+    if (elementsByModelId[models[i].getId()] !== undefined) {
+      promises.push(models[i].getByIdentifiedElements(elementsByModelId[models[i].getId()], complete));
+    }
+  }
+  return Promise.all(promises);
+};
+
 
 module.exports = Project;
diff --git a/frontend-js/src/main/js/map/data/Reaction.js b/frontend-js/src/main/js/map/data/Reaction.js
index e9f9daa2033185f4ca9fac51aed7a5395cb073d2..73085d47022d09cdec387ac0bcc8702f8f82d68c 100644
--- a/frontend-js/src/main/js/map/data/Reaction.js
+++ b/frontend-js/src/main/js/map/data/Reaction.js
@@ -197,6 +197,14 @@ Reaction.prototype.getReactionId = function () {
   return this._reactionId;
 };
 
+/**
+ *
+ * @returns {string}
+ */
+Reaction.prototype.getElementId = function () {
+  return this.getReactionId();
+};
+
 /**
  *
  * @param {string} reactionId
diff --git a/frontend-js/src/test/js/gui/leftPanel/PublicationListDialog-test.js b/frontend-js/src/test/js/gui/leftPanel/PublicationListDialog-test.js
index a300fbc33475f89d75a81ef2fff497e8091ef37a..f7f989c35cbbe5060f0164bedd0fe6913f1b2a01 100644
--- a/frontend-js/src/test/js/gui/leftPanel/PublicationListDialog-test.js
+++ b/frontend-js/src/test/js/gui/leftPanel/PublicationListDialog-test.js
@@ -4,6 +4,7 @@ require('../../mocha-config.js');
 
 var PublicationListDialog = require('../../../../main/js/gui/leftPanel/PublicationListDialog');
 var ServerConnector = require('../../ServerConnector-mock');
+var Functions = require('../../../../main/js/Functions');
 
 var chai = require('chai');
 var assert = chai.assert;
@@ -54,4 +55,42 @@ describe('PublicationListDialog', function () {
     });
   });
 
+  it('publicationListAsCsvString', function () {
+
+    var dialog;
+    return ServerConnector.getProject().then(function (project) {
+      dialog = createPublicationListDialog(helper.createCustomMap(project));
+      // noinspection JSAccessibilityCheck
+      return ServerConnector.getPublications({
+        start: 0,
+        length: 10
+      });
+    }).then(function (publicationList) {
+      return dialog.publicationListAsCsvString(publicationList);
+    }).then(function (result) {
+      assert.ok(Functions.isString(result));
+      assert.ok(result.indexOf("re21") >= 0);
+    }).finally(function () {
+      dialog.destroy();
+    });
+  });
+
+  it('publicationListToArray', function () {
+    var dialog;
+    return ServerConnector.getProject().then(function (project) {
+      dialog = createPublicationListDialog(helper.createCustomMap(project));
+      // noinspection JSAccessibilityCheck
+      return ServerConnector.getPublications({
+        start: 0,
+        length: 10
+      });
+    }).then(function (publicationList) {
+      return dialog.publicationListToArray(publicationList);
+    }).then(function (result) {
+      assert.equal(10, result.length);
+    }).finally(function () {
+      dialog.destroy();
+    });
+  });
+
 });
diff --git a/web/src/main/webapp/index.xhtml b/web/src/main/webapp/index.xhtml
index 385a0661cc00cc1b9b47dab145170db49ddc30e8..8fbeb0d72246a8043df301a3f24fa11fffef74fa 100644
--- a/web/src/main/webapp/index.xhtml
+++ b/web/src/main/webapp/index.xhtml
@@ -17,11 +17,13 @@
   <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
   <script src="https://cdn.datatables.net/1.10.13/js/jquery.dataTables.min.js"></script>
   <script src="https://cdn.datatables.net/rowreorder/1.2.3/js/dataTables.rowReorder.min.js"></script>
-
+  <script src="https://cdn.datatables.net/buttons/1.5.2/js/dataTables.buttons.min.js"></script>
 
 	<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.13/css/jquery.dataTables.min.css"/>	
 	<link rel="stylesheet" type="text/css" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"/>
 	<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/rowreorder/1.2.3/css/rowReorder.dataTables.min.css"/>
+	<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/1.5.2/css/buttons.dataTables.min.css"/>
+	
 
 	<link rel="shortcut icon" href="./resources/images/favicon.png" type="image/png" />