diff --git a/nomad/cli/admin/__init__.py b/nomad/cli/admin/__init__.py
index 32109390a4dbbaa3d7ba0a7381111cde70df010d..48157c3e7d39c2442201949378d9ea5077e09312 100644
--- a/nomad/cli/admin/__init__.py
+++ b/nomad/cli/admin/__init__.py
@@ -13,4 +13,4 @@
 # limitations under the License.
 
 
-from . import admin, uploads, run, clean, users
+from . import admin, uploads, entries, run, clean, users
diff --git a/nomad/cli/admin/entries.py b/nomad/cli/admin/entries.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ca0d7b1798caca727826aea9781423c96555c22
--- /dev/null
+++ b/nomad/cli/admin/entries.py
@@ -0,0 +1,39 @@
+# Copyright 2018 Markus Scheidgen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an"AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import click
+
+from nomad import processing as proc, infrastructure, search
+from .admin import admin
+
+
+@admin.group(help='Entry related commands')
+def entries():
+    infrastructure.setup_mongo()
+    infrastructure.setup_elastic()
+
+
+@entries.command(help='Delete selected entries from mongo and elastic')
+@click.argument('ENTRIES', nargs=-1)
+@click.option('--skip-es', help='Keep the elastic index version of the data.', is_flag=True)
+@click.option('--skip-mongo', help='Keep uploads and calcs in mongo.', is_flag=True)
+def rm(entries, skip_es, skip_mongo):
+    print('%d entries selected, deleting ...' % len(entries))
+
+    if not skip_es:
+        for entry in entries:
+            search.delete_entry(calc_id=entry)
+
+    if not skip_mongo:
+        proc.Calc.objects(calc_id__in=entries).delete()
diff --git a/nomad/search.py b/nomad/search.py
index 492f13c806ec21a27d0e9ad46e29277fdce1876f..1bbff8ce6385c124ac5f27171e4b17146b0d55ac 100644
--- a/nomad/search.py
+++ b/nomad/search.py
@@ -167,6 +167,12 @@ def delete_upload(upload_id):
     Search(index=index).query('match', upload_id=upload_id).delete()
 
 
+def delete_entry(calc_id):
+    """ Delete the entry with the given ``calc_id`` from the index. """
+    index = Entry._default_index()
+    Search(index=index).query('match', calc_id=calc_id).delete()
+
+
 def publish(calcs: Iterable[datamodel.CalcWithMetadata]) -> None:
     """ Update all given calcs with their metadata and set ``publish = True``. """
     def elastic_updates():
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 18dd6f1b06346f5dab7dba0f2c51086bd0fec719..ee34730b298bd9b32cbdc952ba8843205d5e32ab 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -79,6 +79,19 @@ class TestAdmin:
 
         assert search.SearchRequest().search_parameter('comment', 'specific').execute()['total'] == 1
 
+    def test_delete_entry(self, published):
+        upload_id = published.upload_id
+        calc = Calc.objects(upload_id=upload_id).first()
+
+        result = click.testing.CliRunner().invoke(
+            cli, ['admin', 'entries', 'rm', calc.calc_id], catch_exceptions=False, obj=utils.POPO())
+
+        print(result.output)
+        assert result.exit_code == 0
+        assert 'deleting' in result.stdout
+        assert Upload.objects(upload_id=upload_id).first() is not None
+        assert Calc.objects(calc_id=calc.calc_id).first() is None
+
 
 @pytest.mark.usefixtures('reset_config', 'no_warn')
 class TestAdminUploads: