diff --git a/gui/src/components/App.js b/gui/src/components/App.js
index 2d29fc2fe3c067cdf02ce53fa74c072b4b490dfd..5658c8388ce57cb82ec6ccc38092c647251f9d9c 100644
--- a/gui/src/components/App.js
+++ b/gui/src/components/App.js
@@ -35,6 +35,7 @@ import { capitalize } from '../utils'
 import { amber } from '@material-ui/core/colors'
 import KeepState from './KeepState'
 import {help as userdataHelp, default as UserdataPage} from './UserdataPage'
+import ResolveDOI from './dataset/ResolveDOI'
 
 export class VersionMismatch extends Error {
   constructor(msg) {
@@ -466,6 +467,18 @@ export default class App extends React.Component {
         }
       }
     },
+    'dataset_doi': {
+      path: '/dataset/doi/:doi*',
+      key: (props) => `dataset/doi/${props.match.params.doi}`,
+      render: props => {
+        const { match, ...rest } = props
+        if (match && match.params.doi) {
+          return (<ResolveDOI {...rest} doi={match.params.doi} />)
+        } else {
+          return ''
+        }
+      }
+    },
     'uploads': {
       exact: true,
       singleton: true,
diff --git a/gui/src/components/DatasetPage.js b/gui/src/components/DatasetPage.js
index 67546f5dfba6a2a933bf561f7a1267fcbb69451a..7f788ebd87f15680972a1738256e94381984bb3a 100644
--- a/gui/src/components/DatasetPage.js
+++ b/gui/src/components/DatasetPage.js
@@ -6,8 +6,8 @@ import { withErrors } from './errors'
 import { withApi, DoesNotExist } from './api'
 import Search from './search/Search'
 import SearchContext from './search/SearchContext'
-import { Typography, Link } from '@material-ui/core'
-import { DatasetActions } from './search/DatasetList'
+import { Typography } from '@material-ui/core'
+import { DatasetActions, DOI } from './search/DatasetList'
 import { withRouter } from 'react-router'
 
 export const help = `
@@ -97,7 +97,7 @@ class DatasetPage extends React.Component {
           <div className={classes.description}>
             <Typography variant="h4">{dataset.name || 'loading ...'}</Typography>
             <Typography>
-              dataset{dataset.doi ? <span>, with DOI <Link href={dataset.doi}>{dataset.doi}</Link></span> : ''}
+              dataset{dataset.doi ? <span>, with DOI <DOI doi={dataset.doi} /></span> : ''}
             </Typography>
           </div>
 
diff --git a/gui/src/components/EditUserMetadataDialog.js b/gui/src/components/EditUserMetadataDialog.js
index 60f629717267363cc051fd2d41e56579682e253d..6be7dab205a7e4f7932cbb3ac75af11560913f3a 100644
--- a/gui/src/components/EditUserMetadataDialog.js
+++ b/gui/src/components/EditUserMetadataDialog.js
@@ -320,7 +320,8 @@ class EditUserMetadataDialogUnstyled extends React.Component {
     raiseError: PropTypes.func.isRequired,
     user: PropTypes.object,
     onEditComplete: PropTypes.func,
-    disabled: PropTypes.bool
+    disabled: PropTypes.bool,
+    title: PropTypes.string
   }
 
   static styles = theme => ({
@@ -478,7 +479,7 @@ class EditUserMetadataDialogUnstyled extends React.Component {
   }
 
   render() {
-    const { classes, buttonProps, total, api, user, example, disabled } = this.props
+    const { classes, buttonProps, total, api, user, example, disabled, title } = this.props
     const { open, actions, verified, submitting } = this.state
 
     const dialogEnabled = user && example.uploader && example.uploader.user_id === user.sub && !disabled
@@ -511,7 +512,7 @@ class EditUserMetadataDialogUnstyled extends React.Component {
     return (
       <React.Fragment>
         <IconButton {...(buttonProps || {})} onClick={this.handleButtonClick} disabled={!dialogEnabled}>
-          <Tooltip title={`Edit user metadata${dialogEnabled ? '' : '. You can only edit your data.'}`}>
+          <Tooltip title={title || `Edit user metadata${dialogEnabled ? '' : '. You can only edit your data.'}`}>
             <EditIcon />
           </Tooltip>
         </IconButton>
diff --git a/gui/src/components/Quantity.js b/gui/src/components/Quantity.js
index 0ce94668cb081ced177353fe7c9cac7205254f18..992c31dc81e8f0f9f7052e2b20303ac7c547a74d 100644
--- a/gui/src/components/Quantity.js
+++ b/gui/src/components/Quantity.js
@@ -32,8 +32,7 @@ class Quantity extends React.Component {
     },
     valueAction: {},
     valueActionButton: {
-      padding: 2,
-      paddingButtom: 3
+      padding: 4
     },
     valueActionIcon: {
       fontSize: 16
diff --git a/gui/src/components/api.js b/gui/src/components/api.js
index 74aed766bf070804ee0ca34b50efdb0d5f9523a7..f7e68e42cb5b30e2e83b198e01dac8ce7fdf0cc4 100644
--- a/gui/src/components/api.js
+++ b/gui/src/components/api.js
@@ -333,13 +333,23 @@ class Api {
 
   async resolvePid(pid) {
     this.onStartLoading()
-    return this.swaggerPromise
+    return this.swagger()
       .then(client => client.apis.repo.resolve_pid({pid: pid}))
       .catch(handleApiError)
       .then(response => response.body)
       .finally(this.onFinishLoading)
   }
 
+  async resolveDoi(doi) {
+    this.onStartLoading()
+    console.log(doi)
+    return this.swagger()
+      .then(client => client.apis.datasets.resolve_doi({doi: doi}))
+      .catch(handleApiError)
+      .then(response => response.body)
+      .finally(this.onFinishLoading)
+  }
+
   async search(search) {
     this.onStartLoading()
     return this.swagger()
diff --git a/gui/src/components/dataset/ResolveDOI.js b/gui/src/components/dataset/ResolveDOI.js
new file mode 100644
index 0000000000000000000000000000000000000000..3df0b4f7763c41424ba9851e31e5f058c1689ec4
--- /dev/null
+++ b/gui/src/components/dataset/ResolveDOI.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { withStyles, Typography } from '@material-ui/core'
+import { compose } from 'recompose'
+import { withApi } from '../api'
+import { withRouter } from 'react-router'
+
+class ResolveDOI extends React.Component {
+  static styles = theme => ({
+    root: {
+      padding: theme.spacing.unit * 3
+    }
+  })
+
+  static propTypes = {
+    classes: PropTypes.object.isRequired,
+    doi: PropTypes.string.isRequired,
+    api: PropTypes.object.isRequired,
+    history: PropTypes.object.isRequired,
+    raiseError: PropTypes.func.isRequired
+  }
+
+  update() {
+    const { doi, api, history, raiseError } = this.props
+    api.resolveDoi(doi).then(dataset => {
+      history.push(`/dataset/id/${dataset.dataset_id}`)
+    }).catch(raiseError)
+  }
+
+  componentDidMount() {
+    this.update()
+  }
+
+  componentDidUpdate(prevProps) {
+    if (prevProps.doi !== this.props.doi || prevProps.api !== this.props.api) {
+      this.update()
+    }
+  }
+
+  render() {
+    const { classes } = this.props
+
+    return (
+      <Typography className={classes.root}>loading ...</Typography>
+    )
+  }
+}
+
+export default compose(withRouter, withApi(false), withStyles(ResolveDOI.styles))(ResolveDOI)
diff --git a/gui/src/components/entry/RepoEntryView.js b/gui/src/components/entry/RepoEntryView.js
index dd2d773c754c32b4b6012e2baa73d8df2ad87678..43e2eb2036edd081dd69c4a79a3170e184067afe 100644
--- a/gui/src/components/entry/RepoEntryView.js
+++ b/gui/src/components/entry/RepoEntryView.js
@@ -7,6 +7,7 @@ import ApiDialogButton from '../ApiDialogButton'
 import Quantity from '../Quantity'
 import { withDomain } from '../domains'
 import { Link as RouterLink } from 'react-router-dom'
+import { DOI } from '../search/DatasetList'
 
 class RepoEntryView extends React.Component {
   static styles = theme => ({
@@ -120,7 +121,7 @@ class RepoEntryView extends React.Component {
                         {(calcData.datasets || []).map(ds => (
                           <Typography key={ds.id}>
                             <Link component={RouterLink} to={`/dataset/id/${ds.id}`}>{ds.name}</Link>
-                            {ds.doi ? <span>&nbsp; (<Link href={ds.doi}>{ds.doi}</Link>)</span> : ''}
+                            {ds.doi ? <span>&nbsp; (<DOI doi={ds.doi}/>)</span> : ''}
                           </Typography>))}
                       </div>
                     </Quantity>
diff --git a/gui/src/components/search/DatasetList.js b/gui/src/components/search/DatasetList.js
index d8e82eb5280bf1caada37eb9e94025897c4b6e1b..80de2470912a75c68fafb6eb849ed718d3c6702b 100644
--- a/gui/src/components/search/DatasetList.js
+++ b/gui/src/components/search/DatasetList.js
@@ -1,6 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { withStyles, TableCell, Toolbar, IconButton, FormGroup, Tooltip } from '@material-ui/core'
+import { withStyles, TableCell, Toolbar, IconButton, FormGroup, Tooltip, Link } from '@material-ui/core'
 import { compose } from 'recompose'
 import { withRouter } from 'react-router'
 import { withDomain } from '../domains'
@@ -13,6 +13,42 @@ import DeleteIcon from '@material-ui/icons/Delete'
 import { withApi } from '../api'
 import EditUserMetadataDialog from '../EditUserMetadataDialog'
 import DownloadButton from '../DownloadButton'
+import ClipboardIcon from '@material-ui/icons/Assignment'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
+
+class DOIUnstyled extends React.Component {
+  static propTypes = {
+    doi: PropTypes.string.isRequired
+  }
+
+  static styles = theme => ({
+    root: {
+      display: 'inline-flex',
+      alignItems: 'center',
+      flexDirection: 'row',
+      flexWrap: 'nowrap'
+    }
+  })
+
+  render() {
+    const {classes, doi} = this.props
+    const url = `https://dx.doi.org/${doi}`
+    return <span className={classes.root}>
+      <Link href={url}>{doi}</Link>
+      <CopyToClipboard
+        text={url} onCopy={() => null}
+      >
+        <Tooltip title={`Copy DOI to clipboard`}>
+          <IconButton style={{margin: 3, marginRight: 0, padding: 4}}>
+            <ClipboardIcon style={{fontSize: 16}} />
+          </IconButton>
+        </Tooltip>
+      </CopyToClipboard>
+    </span>
+  }
+}
+
+export const DOI = withStyles(DOIUnstyled.styles)(DOIUnstyled)
 
 class DatasetActionsUnstyled extends React.Component {
   static propTypes = {
@@ -102,6 +138,7 @@ class DatasetActionsUnstyled extends React.Component {
         </IconButton>
       </Tooltip>}
       {editable && <EditUserMetadataDialog
+        title="Edit metadata of all dataset entries"
         example={dataset.example} query={query}
         total={dataset.total} onEditComplete={this.handleEdit}
       />}
@@ -160,7 +197,7 @@ class DatasetListUnstyled extends React.Component {
     },
     DOI: {
       label: 'Dataset DOI',
-      render: (dataset) => dataset.doi
+      render: (dataset) => dataset.doi && <DOI doi={dataset.doi} />
     },
     entries: {
       label: 'Entries',
diff --git a/nomad/app/api/dataset.py b/nomad/app/api/dataset.py
index 90d130efc2a7f5bd1850c96f5aa3ba47184309da..87b959a6f62e84e601365a4e3975d262e61e1af3 100644
--- a/nomad/app/api/dataset.py
+++ b/nomad/app/api/dataset.py
@@ -138,7 +138,11 @@ class DatasetResource(Resource):
 
         # set the DOI
         doi = DOI.create(title='NOMAD dataset: %s' % result.name, user=g.user)
+        doi.create_draft()
+        doi.make_findable()
+
         result.doi = doi.doi
+
         result.m_x('me').save()
         if doi.state != 'findable':
             logger.warning(
@@ -176,3 +180,17 @@ class DatasetResource(Resource):
         result.m_x('me').delete()
 
         return result
+
+
+@ns.route('/doi/<path:doi>')
+class RepoPidResource(Resource):
+    @api.doc('resolve_doi')
+    @api.response(404, 'Dataset with DOI does not exist')
+    @api.marshal_with(dataset_model, skip_none=True, code=200, description='DOI resolved')
+    @authenticate()
+    def get(self, doi: str):
+        dataset_me = Dataset.m_def.m_x('me').objects(doi=doi).first()
+        if dataset_me is None:
+            abort(404, 'Dataset with DOI %s does not exist' % doi)
+
+        return dataset_me, 200
diff --git a/nomad/app/api/upload.py b/nomad/app/api/upload.py
index a98a9cd3b281aad235a138e3c99359bda860555d..5f6ae42d92e3ec5d11dc16c734bef4726a093d28 100644
--- a/nomad/app/api/upload.py
+++ b/nomad/app/api/upload.py
@@ -320,7 +320,7 @@ class UploadListResource(Resource):
 Thanks for uploading your data to nomad.
 Go back to %s and press reload to see the progress on your upload and publish your data.
 
-''' % upload.gui_url,
+''' % config.gui_url(),
                 200, {'Content-Type': 'text/plain; charset=utf-8'})
 
         return upload, 200
diff --git a/nomad/config.py b/nomad/config.py
index 17e202260049d529106970699a959b4a852b9ef0..3a12ba95e30c2c3b7514a5443a7ef973363faeb0 100644
--- a/nomad/config.py
+++ b/nomad/config.py
@@ -158,13 +158,12 @@ def api_url(ssl: bool = True):
         services.api_base_path.strip('/'))
 
 
-migration_source_db = NomadConfig(
-    host='db-repository.nomad.esc',
-    port=5432,
-    dbname='nomad_prod',
-    user='nomadlab',
-    password='*'
-)
+def gui_url():
+    base = api_url(True)[:-3]
+    if base.endswith('/'):
+        base = base[:-1]
+    return '%s/gui' % base
+
 
 mail = NomadConfig(
     enabled=False,
diff --git a/nomad/doi.py b/nomad/doi.py
index c2e965addd66db8791649a289c3b27ea7ae168a1..ed0c12e5ddd195d114966526d3d99c9febd34614 100644
--- a/nomad/doi.py
+++ b/nomad/doi.py
@@ -76,6 +76,7 @@ class DOI(Document):
         doi.doi_url = '%s/doi/%s' % (config.datacite.mds_host, doi_str)
         doi.state = 'created'
         doi.create_time = create_time
+        doi.url = '%s/dataset/doi/%s' % (config.gui_url(), doi_str)
 
         affiliation = ''
         if user.affiliation is not None:
diff --git a/nomad/processing/data.py b/nomad/processing/data.py
index d03ed24f026ff2d5e115a67dc5aec5759e9587be..6c5e2b5b1428254347e98bb84dfe8964e445f40b 100644
--- a/nomad/processing/data.py
+++ b/nomad/processing/data.py
@@ -841,13 +841,6 @@ class Upload(Proc):
         self.joined = False
         super().reset()
 
-    @property
-    def gui_url(self):
-        base = config.api_url()[:-3]
-        if base.endswith('/'):
-            base = base[:-1]
-        return '%s/gui/uploads/' % base
-
     def _cleanup_after_processing(self):
         # send email about process finish
         user = self.uploader
@@ -857,7 +850,7 @@ class Upload(Proc):
             '',
             'your data %suploaded at %s has completed processing.' % (
                 '"%s" ' % self.name if self.name else '', self.upload_time.isoformat()),  # pylint: disable=no-member
-            'You can review your data on your upload page: %s' % self.gui_url,
+            'You can review your data on your upload page: %s' % config.gui_url(),
             '',
             'If you encouter any issues with your upload, please let us know and replay to this email.',
             '',
diff --git a/tests/app/test_api.py b/tests/app/test_api.py
index 63b94a1f0f7a27d1115fea21816e3e1cc213021c..f1a02b19998da5a228ff309ae7262b578db50b2d 100644
--- a/tests/app/test_api.py
+++ b/tests/app/test_api.py
@@ -1512,7 +1512,6 @@ class TestDataset:
         search.refresh()
 
     def test_delete_dataset(self, api, test_user_auth, example_dataset_with_entry):
-        # delete dataset
         rv = api.delete('/datasets/ds1', headers=test_user_auth)
         assert rv.status_code == 200
         data = json.loads(rv.data)
@@ -1525,9 +1524,14 @@ class TestDataset:
         assert rv.status_code == 400
 
     def test_assign_doi(self, api, test_user_auth, example_dataset_with_entry):
-        # assign doi
         rv = api.post('/datasets/ds1', headers=test_user_auth)
         assert rv.status_code == 200
         data = json.loads(rv.data)
         self.assert_dataset(data, name='ds1', doi=True)
         self.assert_dataset_entry(api, '1', True, True, headers=test_user_auth)
+
+    def test_resolve_doi(self, api, example_dataset_with_entry):
+        rv = api.get('/datasets/doi/test_doi')
+        assert rv.status_code == 200
+        data = json.loads(rv.data)
+        self.assert_dataset(data, name='ds2', doi=True)