diff --git a/build.sbt b/build.sbt
index e5eb90f2..5538ba5a 100644
--- a/build.sbt
+++ b/build.sbt
@@ -616,9 +616,10 @@ def compileElm(
 
 def createWebjarSource(wj: Seq[ModuleID], out: File): Seq[File] = {
   val target = out / "Webjars.scala"
+  val badChars = "-.".toSet
   val fields = wj
     .map(m =>
-      s"""val ${m.name.toLowerCase.filter(_ != '-')} = "/${m.name}/${m.revision}" """
+      s"""val ${m.name.toLowerCase.filter(c => !badChars.contains(c))} = "/${m.name}/${m.revision}" """
     )
     .mkString("\n\n")
   val content = s"""package docspell.restserver.webapp
diff --git a/elm.json b/elm.json
index 50606567..149303a7 100644
--- a/elm.json
+++ b/elm.json
@@ -21,15 +21,22 @@
             "elm-explorations/markdown": "1.0.0",
             "justinmimbs/date": "3.1.2",
             "norpan/elm-html5-drag-drop": "3.1.4",
+            "pablohirafuji/elm-qrcode": "3.3.1",
             "ryannhg/date-format": "2.3.0",
             "truqu/elm-base64": "2.0.4",
             "ursi/elm-throttle": "1.0.1"
         },
         "indirect": {
+            "avh4/elm-color": "1.0.0",
+            "danfishgold/base64-bytes": "1.0.3",
             "elm/bytes": "1.0.8",
             "elm/parser": "1.1.0",
             "elm/regex": "1.0.0",
-            "elm/virtual-dom": "1.0.2"
+            "elm/svg": "1.0.1",
+            "elm/virtual-dom": "1.0.2",
+            "elm-community/list-extra": "8.2.4",
+            "folkertdev/elm-flate": "2.0.4",
+            "justgook/elm-image": "4.0.0"
         }
     },
     "test-dependencies": {
diff --git a/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
index ba46c2e5..b59c7875 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/TemplateRoutes.scala
@@ -126,6 +126,7 @@ object TemplateRoutes {
         Seq(
           "/app/assets" + Webjars.jquery + "/jquery.min.js",
           "/app/assets" + Webjars.semanticui + "/semantic.min.js",
+          "/app/assets" + Webjars.clipboardjs + "/clipboard.min.js",
           s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell-app.js"
         ),
         s"/app/assets/docspell-webapp/${BuildInfo.version}/favicon",
diff --git a/modules/webapp/src/main/elm/Comp/SourceForm.elm b/modules/webapp/src/main/elm/Comp/SourceForm.elm
index a5202ef4..6d156ce8 100644
--- a/modules/webapp/src/main/elm/Comp/SourceForm.elm
+++ b/modules/webapp/src/main/elm/Comp/SourceForm.elm
@@ -97,6 +97,10 @@ type Msg
     | FolderDropdownMsg (Comp.Dropdown.Msg IdName)
 
 
+
+--- Update
+
+
 update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
 update flags msg model =
     case msg of
@@ -223,6 +227,10 @@ update flags msg model =
             ( model_, Cmd.map FolderDropdownMsg c2 )
 
 
+
+--- View
+
+
 view : Flags -> UiSettings -> Model -> Html Msg
 view flags settings model =
     let
@@ -293,43 +301,6 @@ disappear then.
                       """
                 ]
             ]
-        , urlInfoMessage flags model
-        ]
-
-
-urlInfoMessage : Flags -> Model -> Html Msg
-urlInfoMessage flags model =
-    div
-        [ classList
-            [ ( "ui info icon message", True )
-            , ( "hidden", not model.enabled || model.source.id == "" )
-            ]
-        ]
-        [ i [ class "info icon" ] []
-        , div [ class "content" ]
-            [ div [ class "header" ]
-                [ text "Public Uploads"
-                ]
-            , p []
-                [ text "This source defines URLs that can be used by anyone to send files to "
-                , text "you. There is a web page that you can share or the API url can be used "
-                , text "with other clients."
-                ]
-            , dl [ class "ui list" ]
-                [ dt [] [ text "Public Upload Page" ]
-                , dd []
-                    [ let
-                        url =
-                            flags.config.baseUrl ++ "/app/upload/" ++ model.source.id
-                      in
-                      a [ href url, target "_blank" ] [ code [] [ text url ] ]
-                    ]
-                , dt [] [ text "Public API Upload URL" ]
-                , dd []
-                    [ code [] [ text (flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ model.source.id) ]
-                    ]
-                ]
-            ]
         ]
 
 
diff --git a/modules/webapp/src/main/elm/Comp/SourceManage.elm b/modules/webapp/src/main/elm/Comp/SourceManage.elm
index 11184e42..70da2a06 100644
--- a/modules/webapp/src/main/elm/Comp/SourceManage.elm
+++ b/modules/webapp/src/main/elm/Comp/SourceManage.elm
@@ -8,10 +8,10 @@ module Comp.SourceManage exposing
 
 import Api
 import Api.Model.BasicResult exposing (BasicResult)
-import Api.Model.Source
+import Api.Model.Source exposing (Source)
 import Api.Model.SourceList exposing (SourceList)
 import Comp.SourceForm
-import Comp.SourceTable
+import Comp.SourceTable exposing (SelectMode(..))
 import Comp.YesNoDimmer
 import Data.Flags exposing (Flags)
 import Data.UiSettings exposing (UiSettings)
@@ -19,53 +19,68 @@ import Html exposing (..)
 import Html.Attributes exposing (..)
 import Html.Events exposing (onClick, onSubmit)
 import Http
+import Ports
+import QRCode
 import Util.Http
 import Util.Maybe
 
 
 type alias Model =
-    { tableModel : Comp.SourceTable.Model
-    , formModel : Comp.SourceForm.Model
-    , viewMode : ViewMode
+    { formModel : Comp.SourceForm.Model
+    , viewMode : SelectMode
     , formError : Maybe String
     , loading : Bool
     , deleteConfirm : Comp.YesNoDimmer.Model
+    , sources : List Source
     }
 
 
-type ViewMode
-    = Table
-    | Form
-
-
 init : Flags -> ( Model, Cmd Msg )
 init flags =
     let
         ( fm, fc ) =
             Comp.SourceForm.init flags
     in
-    ( { tableModel = Comp.SourceTable.emptyModel
-      , formModel = fm
-      , viewMode = Table
+    ( { formModel = fm
+      , viewMode = None
       , formError = Nothing
       , loading = False
       , deleteConfirm = Comp.YesNoDimmer.emptyModel
+      , sources = []
       }
-    , Cmd.map FormMsg fc
+    , Cmd.batch
+        [ Cmd.map FormMsg fc
+        , Ports.initClipboard appClipboardData
+        , Ports.initClipboard apiClipboardData
+        ]
     )
 
 
+appClipboardData : ( String, String )
+appClipboardData =
+    ( "app-url", "#app-url-copy-to-clipboard-btn" )
+
+
+apiClipboardData : ( String, String )
+apiClipboardData =
+    ( "api-url", "#api-url-copy-to-clipboard-btn" )
+
+
 type Msg
     = TableMsg Comp.SourceTable.Msg
     | FormMsg Comp.SourceForm.Msg
     | LoadSources
     | SourceResp (Result Http.Error SourceList)
-    | SetViewMode ViewMode
     | InitNewSource
     | Submit
     | SubmitResp (Result Http.Error BasicResult)
     | YesNoMsg Comp.YesNoDimmer.Msg
     | RequestDelete
+    | SetTableView
+
+
+
+--- Update
 
 
 update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
@@ -73,29 +88,31 @@ update flags msg model =
     case msg of
         TableMsg m ->
             let
-                ( tm, tc ) =
-                    Comp.SourceTable.update flags m model.tableModel
+                ( tc, sel ) =
+                    Comp.SourceTable.update flags m
 
                 ( m2, c2 ) =
                     ( { model
-                        | tableModel = tm
-                        , viewMode = Maybe.map (\_ -> Form) tm.selected |> Maybe.withDefault Table
+                        | viewMode = sel
                         , formError =
-                            if Util.Maybe.nonEmpty tm.selected then
-                                Nothing
+                            if Comp.SourceTable.isEdit sel then
+                                model.formError
 
                             else
-                                model.formError
+                                Nothing
                       }
                     , Cmd.map TableMsg tc
                     )
 
                 ( m3, c3 ) =
-                    case tm.selected of
-                        Just source ->
+                    case sel of
+                        Edit source ->
                             update flags (FormMsg (Comp.SourceForm.SetSource source)) m2
 
-                        Nothing ->
+                        Display _ ->
+                            ( m2, Cmd.none )
+
+                        None ->
                             ( m2, Cmd.none )
             in
             ( m3, Cmd.batch [ c2, c3 ] )
@@ -111,34 +128,27 @@ update flags msg model =
             ( { model | loading = True }, Api.getSources flags SourceResp )
 
         SourceResp (Ok sources) ->
-            let
-                m2 =
-                    { model | viewMode = Table, loading = False }
-            in
-            update flags (TableMsg (Comp.SourceTable.SetSources sources.items)) m2
+            ( { model
+                | viewMode = None
+                , loading = False
+                , sources = sources.items
+              }
+            , Cmd.none
+            )
 
         SourceResp (Err _) ->
             ( { model | loading = False }, Cmd.none )
 
-        SetViewMode m ->
-            let
-                m2 =
-                    { model | viewMode = m }
-            in
-            case m of
-                Table ->
-                    update flags (TableMsg Comp.SourceTable.Deselect) m2
-
-                Form ->
-                    ( m2, Cmd.none )
+        SetTableView ->
+            ( { model | viewMode = None }, Cmd.none )
 
         InitNewSource ->
             let
-                nm =
-                    { model | viewMode = Form, formError = Nothing }
-
                 source =
                     Api.Model.Source.empty
+
+                nm =
+                    { model | viewMode = Edit source, formError = Nothing }
             in
             update flags (FormMsg (Comp.SourceForm.SetSource source)) nm
 
@@ -160,7 +170,7 @@ update flags msg model =
             if res.success then
                 let
                     ( m2, c2 ) =
-                        update flags (SetViewMode Table) model
+                        update flags SetTableView model
 
                     ( m3, c3 ) =
                         update flags LoadSources m2
@@ -194,13 +204,29 @@ update flags msg model =
             ( { model | deleteConfirm = cm }, cmd )
 
 
+
+--- View
+
+
+qrCodeView : String -> Html msg
+qrCodeView message =
+    QRCode.encode message
+        |> Result.map QRCode.toSvg
+        |> Result.withDefault
+            (Html.text "Error generating QR-Code")
+
+
 view : Flags -> UiSettings -> Model -> Html Msg
 view flags settings model =
-    if model.viewMode == Table then
-        viewTable model
+    case model.viewMode of
+        None ->
+            viewTable model
 
-    else
-        div [] (viewForm flags settings model)
+        Edit _ ->
+            div [] (viewForm flags settings model)
+
+        Display source ->
+            viewLinks flags settings source
 
 
 viewTable : Model -> Html Msg
@@ -210,7 +236,7 @@ viewTable model =
             [ i [ class "plus icon" ] []
             , text "Create new"
             ]
-        , Html.map TableMsg (Comp.SourceTable.view model.tableModel)
+        , Html.map TableMsg (Comp.SourceTable.view model.sources)
         , div
             [ classList
                 [ ( "ui dimmer", True )
@@ -222,6 +248,112 @@ viewTable model =
         ]
 
 
+viewLinks : Flags -> UiSettings -> Source -> Html Msg
+viewLinks flags _ source =
+    let
+        appUrl =
+            flags.config.baseUrl ++ "/app/upload/" ++ source.id
+
+        apiUrl =
+            flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ source.id
+    in
+    div
+        []
+        [ h3 [ class "ui dividing header" ]
+            [ text "Public Uploads: "
+            , text source.abbrev
+            , div [ class "sub header" ]
+                [ text source.id
+                ]
+            ]
+        , p []
+            [ text "This source defines URLs that can be used by anyone to send files to "
+            , text "you. There is a web page that you can share or the API url can be used "
+            , text "with other clients."
+            ]
+        , p []
+            [ text "There have been "
+            , String.fromInt source.counter |> text
+            , text " items created through this source."
+            ]
+        , h4 [ class "ui header" ]
+            [ text "Public Upload Page"
+            ]
+        , div [ class "ui attached message" ]
+            [ div [ class "ui fluid left action input" ]
+                [ a
+                    [ class "ui left icon button"
+                    , title "Copy to clipboard"
+                    , href "#"
+                    , Tuple.second appClipboardData
+                        |> String.dropLeft 1
+                        |> id
+                    , attribute "data-clipboard-target" "#app-url"
+                    ]
+                    [ i [ class "copy icon" ] []
+                    ]
+                , a
+                    [ class "ui icon button"
+                    , href appUrl
+                    , target "_blank"
+                    , title "Open in new tab/window"
+                    ]
+                    [ i [ class "link external icon" ] []
+                    ]
+                , input
+                    [ type_ "text"
+                    , id "app-url"
+                    , value appUrl
+                    , readonly True
+                    ]
+                    []
+                ]
+            ]
+        , div [ class "ui attached segment" ]
+            [ div [ class "qr-code" ]
+                [ qrCodeView appUrl
+                ]
+            ]
+        , h4 [ class "ui header" ]
+            [ text "Public API Upload URL"
+            ]
+        , div [ class "ui attached message" ]
+            [ div [ class "ui fluid left action input" ]
+                [ a
+                    [ class "ui left icon button"
+                    , title "Copy to clipboard"
+                    , href "#"
+                    , Tuple.second apiClipboardData
+                        |> String.dropLeft 1
+                        |> id
+                    , attribute "data-clipboard-target" "#api-url"
+                    ]
+                    [ i [ class "copy icon" ] []
+                    ]
+                , input
+                    [ type_ "text"
+                    , value apiUrl
+                    , readonly True
+                    , id "api-url"
+                    ]
+                    []
+                ]
+            ]
+        , div [ class "ui attached segment" ]
+            [ div [ class "qr-code" ]
+                [ qrCodeView apiUrl
+                ]
+            ]
+        , div [ class "ui divider" ] []
+        , button
+            [ class "ui button"
+            , onClick SetTableView
+            ]
+            [ text "Back"
+            ]
+        ]
+
+
 viewForm : Flags -> UiSettings -> Model -> List (Html Msg)
 viewForm flags settings model =
     let
@@ -256,7 +388,7 @@ viewForm flags settings model =
         , button [ class "ui primary button", type_ "submit" ]
             [ text "Submit"
             ]
-        , a [ class "ui secondary button", onClick (SetViewMode Table), href "" ]
+        , a [ class "ui secondary button", onClick SetTableView, href "" ]
             [ text "Cancel"
             ]
         , if not newSource then
diff --git a/modules/webapp/src/main/elm/Comp/SourceTable.elm b/modules/webapp/src/main/elm/Comp/SourceTable.elm
index c6ab18a6..5e9d0ec8 100644
--- a/modules/webapp/src/main/elm/Comp/SourceTable.elm
+++ b/modules/webapp/src/main/elm/Comp/SourceTable.elm
@@ -1,7 +1,7 @@
 module Comp.SourceTable exposing
-    ( Model
-    , Msg(..)
-    , emptyModel
+    ( Msg
+    , SelectMode(..)
+    , isEdit
     , update
     , view
     )
@@ -14,44 +14,47 @@ import Html.Attributes exposing (..)
 import Html.Events exposing (onClick)
 
 
-type alias Model =
-    { sources : List Source
-    , selected : Maybe Source
-    }
+type SelectMode
+    = Edit Source
+    | Display Source
+    | None
 
 
-emptyModel : Model
-emptyModel =
-    { sources = []
-    , selected = Nothing
-    }
+isEdit : SelectMode -> Bool
+isEdit m =
+    case m of
+        Edit _ ->
+            True
+
+        Display _ ->
+            False
+
+        None ->
+            False
 
 
 type Msg
-    = SetSources (List Source)
-    | Select Source
-    | Deselect
+    = Select Source
+    | Show Source
 
 
-update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
-update _ msg model =
+update : Flags -> Msg -> ( Cmd Msg, SelectMode )
+update _ msg =
     case msg of
-        SetSources list ->
-            ( { model | sources = list, selected = Nothing }, Cmd.none )
-
         Select source ->
-            ( { model | selected = Just source }, Cmd.none )
+            ( Cmd.none, Edit source )
 
-        Deselect ->
-            ( { model | selected = Nothing }, Cmd.none )
+        Show source ->
+            ( Cmd.none, Display source )
 
 
-view : Model -> Html Msg
-view model =
-    table [ class "ui selectable table" ]
+view : List Source -> Html Msg
+view sources =
+    table [ class "ui table" ]
         [ thead []
             [ tr []
-                [ th [ class "collapsing" ] [ text "Abbrev" ]
+                [ th [ class "collapsing" ] []
+                , th [ class "collapsing" ] [ text "Abbrev" ]
                 , th [ class "collapsing" ] [ text "Enabled" ]
                 , th [ class "collapsing" ] [ text "Counter" ]
                 , th [ class "collapsing" ] [ text "Priority" ]
@@ -59,17 +62,37 @@ view model =
                 ]
             ]
         , tbody []
-            (List.map (renderSourceLine model) model.sources)
+            (List.map renderSourceLine sources)
         ]
 
 
-renderSourceLine : Model -> Source -> Html Msg
-renderSourceLine model source =
+renderSourceLine : Source -> Html Msg
+renderSourceLine source =
     tr
-        [ classList [ ( "active", model.selected == Just source ) ]
-        , onClick (Select source)
-        ]
+        []
         [ td [ class "collapsing" ]
+            [ a
+                [ class "ui basic tiny primary button"
+                , href "#"
+                , onClick (Select source)
+                ]
+                [ i [ class "edit icon" ] []
+                , text "Edit"
+                ]
+            , a
+                [ classList
+                    [ ( "ui basic tiny primary button", True )
+                    , ( "disabled", not source.enabled )
+                    ]
+                , href "#"
+                , disabled (not source.enabled)
+                , onClick (Show source)
+                ]
+                [ i [ class "eye icon" ] []
+                , text "Show"
+                ]
+            ]
+        , td [ class "collapsing" ]
             [ text source.abbrev
             ]
         , td [ class "collapsing" ]
diff --git a/modules/webapp/src/main/elm/Ports.elm b/modules/webapp/src/main/elm/Ports.elm
index 4e88cf5c..d401bc1a 100644
--- a/modules/webapp/src/main/elm/Ports.elm
+++ b/modules/webapp/src/main/elm/Ports.elm
@@ -1,5 +1,6 @@
 port module Ports exposing
     ( getUiSettings
+    , initClipboard
     , loadUiSettings
     , onUiSettingsSaved
     , removeAccount
@@ -78,3 +79,6 @@ getUiSettings flags =
 
         Nothing ->
             Cmd.none
+
+
+port initClipboard : ( String, String ) -> Cmd msg
diff --git a/modules/webapp/src/main/webjar/docspell.css b/modules/webapp/src/main/webjar/docspell.css
index 20929c4a..630523c5 100644
--- a/modules/webapp/src/main/webjar/docspell.css
+++ b/modules/webapp/src/main/webjar/docspell.css
@@ -104,6 +104,14 @@
     background: rgba(220, 255, 71, 0.6);
 }
 
+.default-layout .qr-code svg {
+    width: 200px;
+    height: 200px;
+}
+.default-layout .qr-code.bordered svg {
+    border: 1px solid #ccc;
+}
+
 .markdown-preview {
     overflow: auto;
     max-height: 300px;
diff --git a/modules/webapp/src/main/webjar/docspell.js b/modules/webapp/src/main/webjar/docspell.js
index 6c1dacc5..cb91987f 100644
--- a/modules/webapp/src/main/webjar/docspell.js
+++ b/modules/webapp/src/main/webjar/docspell.js
@@ -83,3 +83,13 @@ elmApp.ports.requestUiSettings.subscribe(function(args) {
         }
     }
 });
+
+var docspell_clipboards = {};
+
+elmApp.ports.initClipboard.subscribe(function(args) {
+    var page = args[0];
+    if (!docspell_clipboards[page]) {
+        var sel = args[1];
+        docspell_clipboards[page] = new ClipboardJS(sel);
+    }
+});
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 63f4b1aa..21b9c694 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -9,6 +9,7 @@ object Dependencies {
   val BitpeaceVersion         = "0.5.0"
   val CalevVersion            = "0.3.1"
   val CirceVersion            = "0.13.0"
+  val ClipboardJsVersion      = "2.0.6"
   val DoobieVersion           = "0.9.0"
   val EmilVersion             = "0.6.2"
   val FastparseVersion        = "2.1.3"
@@ -245,10 +246,11 @@ object Dependencies {
   val betterMonadicFor    = "com.olegpy"    %% "better-monadic-for" % BetterMonadicForVersion
 
   val webjars = Seq(
-    "org.webjars" % "swagger-ui"  % SwaggerUIVersion,
-    "org.webjars" % "Semantic-UI" % SemanticUIVersion,
-    "org.webjars" % "jquery"      % JQueryVersion,
-    "org.webjars" % "viewerjs"    % ViewerJSVersion
+    "org.webjars" % "swagger-ui"   % SwaggerUIVersion,
+    "org.webjars" % "Semantic-UI"  % SemanticUIVersion,
+    "org.webjars" % "jquery"       % JQueryVersion,
+    "org.webjars" % "viewerjs"     % ViewerJSVersion,
+    "org.webjars" % "clipboard.js" % ClipboardJsVersion
   )
 
   val icu4j = Seq(
diff --git a/website/shell.nix b/website/shell.nix
index 4fed5990..e42b5750 100644
--- a/website/shell.nix
+++ b/website/shell.nix
@@ -1,5 +1,5 @@
 let
-    nixpkgsUnstable = builtins.fetchTarball {
+  nixpkgsUnstable = builtins.fetchTarball {
     url = "https://github.com/NixOS/nixpkgs-channels/archive/nixos-unstable.tar.gz";
   };
   pkgsUnstable = import nixpkgsUnstable { };