diff --git a/NAMESPACE b/NAMESPACE
index ce256da43262c13fd6b7215ba2dcc6ddbac69004..3cfa939c79fa3e3767d965d2849eb0ebca3d3c9a 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -50,4 +50,8 @@ importFrom(MSnbase,filterMz)
 importFrom(MSnbase,readMSData)
 importFrom(promises,"%...>%")
 importFrom(promises,future_promise)
+importFrom(shiny,HTML)
+importFrom(shiny,numericInput)
+importFrom(shiny,selectInput)
+importFrom(shiny,textInput)
 importFrom(shiny,validate)
diff --git a/R/envopts.R b/R/envopts.R
index e52961ce470caad759e4104f4f2cea084af14094..1abcff73e41ea3d91d8419fa27667c8ee9633587 100644
--- a/R/envopts.R
+++ b/R/envopts.R
@@ -38,10 +38,19 @@ envopts <- function(metfrag_db_dir="",metfrag_jar="") {
                                      #object.
 
     check_dir_absent(metfrag_db_dir,what="mf-db-dir")
-    res$metfrag$db_dir = metfrag_db_dir
+    res$metfrag$db_dir = norm_path(metfrag_db_dir)
 
     check_file_absent(metfrag_jar,what="mf-jar")
-    res$metfrag$jar = metfrag_jar
+    res$metfrag$jar = norm_path(metfrag_jar)
 
     res
 }
+
+
+is_metfrag_available <- function(e) {
+    nchar(e$metfrag$jar)>0L
+}
+
+is_metfrag_local_available <- function(e) {
+    is_metfrag_available(e) && nchar(e$metfrag$db_dir)>0L
+}
diff --git a/R/resources.R b/R/resources.R
index 041499a20813bc299b800506777ae8609660b8bd..7bda881a4e0c7477ec45e0b28cf1ec48fe7b00c2 100644
--- a/R/resources.R
+++ b/R/resources.R
@@ -317,7 +317,10 @@ METFRAG_ADDUCTS = c("[M+H]+","[M+NH4]+","[M+Na]+","[M+K]+",
                     "[M-H]-","[M+Cl]-","[M+HCOO]-","[M+CH3COO]-","[M]+/-")
 METFRAG_WRITER_CHOICES = c("CSV","PSV","XLS")
 METFRAG_DEFAULT_WRITER = "CSV"
-METFRAG_DATABASE_TYPE = c("KEGG","PubChem","ExtendedPubChem","LocalSDF","LocalPSV","LocalCSV")
+METFRAG_LOCAL_DATABASE_TYPE = c("LocalSDF","LocalPSV","LocalCSV")
+METFRAG_REMOTE_DATABASE_TYPE = c("KEGG","PubChem","ExtendedPubChem")
+METFRAG_DATABASE_TYPE = c(METFRAG_REMOTE_DATABASE_TYPE, METFRAG_LOCAL_DATABASE_TYPE)
+METFRAG_DEFAULT_REMOTE_DATABASE_TYPE = "PubChem"
 METFRAG_DEFAULT_DATABASE_TYPE = "LocalCSV"
 METFRAG_PREPFLT_CHOICES = c("UnconnectedCompoundFilter","IsotopeFilter")
 METFRAG_PREPFLT_DEFAULT = c("UnconnectedCompoundFilter","IsotopeFilter")
diff --git a/R/shiny-state.R b/R/shiny-state.R
index d3992f292d37c63bc0ff6969020bd20898599139..c0401f166ac6f7edc1dda5b5f07355b7140e0d9c 100644
--- a/R/shiny-state.R
+++ b/R/shiny-state.R
@@ -1,4 +1,25 @@
-## Managing the state of shiny application.
+## Copyright (C) 2020,2021,2023 by University of Luxembourg
+
+## 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.
+
+## Description
+## 
+## Helpers used in shiny server. Makes testing easier.
+
+
+## Imports from shiny:
+
+#' @importFrom shiny selectInput numericInput textInput HTML
 
 
 GUI_SELECT_INPUTS <- c("proj_list",
@@ -507,3 +528,61 @@ get_mprop_ms2_metadata <- function(ltab_entry) {
  res
   
 }
+
+make_metfrag_panel <- function(envopts) {
+
+    ctrls = list(HTML("MetFrag submodule has been disabled, because no MetFrag runtime has been specified in `envopts'"))
+    if (is_metfrag_available(envopts)) {
+
+        if (is_metfrag_local_available(envopts)) {
+            dbtype = selectInput("mf_database_type", label="Database type",
+                                 choices=METFRAG_DATABASE_TYPE,
+                                 selected=METFRAG_DEFAULT_DATABASE_TYPE)
+            dbselect = selectInput("mf_local_database_path",
+                                   "Local Database Path",
+                                   choices=character(0))
+            
+        } else {
+            dbtype = selectInput("mf_database_type", label="Database type",
+                                 choices=METFRAG_REMOTE_DATABASE_TYPE,
+                                 selected=METFRAG_DEFAULT_REMOTE_DATABASE_TYPE)
+            dbselect = HTML("No local MetFrag databases available.")
+        }
+        
+        
+        ctrls = list(numericInput("mf_database_search_relative_mass_deviation",
+                              label="Database search relative mass deviation",
+                              value=5),
+                     numericInput("mf_fragment_peak_match_absolute_mass_deviation",
+                                  label="Fragment peak match absolute mass deviation",
+                                  value=5),
+                     numericInput("mf_fragment_peak_match_relative_mass_deviation",
+                                  label="Fragment peak match relative mass deviation",
+                                  value=5),
+                     numericInput("mf_maximum_tree_depth", label="MaximumTreeDepth",
+                                  value=2),
+                     selectInput("mf_metfrag_candidate_writer",
+                                 label="MetFrag Candidate Writer",
+                                 choices=shinyscreen:::METFRAG_WRITER_CHOICES,
+                                 selected=shinyscreen:::METFRAG_DEFAULT_WRITER),
+                     dbtype,
+                     dbselect,
+                     selectInput("mf_pre_processing_candidate_filter",
+                                 label="Preprocessing candidate filter",
+                                 choices=shinyscreen:::METFRAG_PREPFLT_CHOICES,
+                                 selected=shinyscreen:::METFRAG_PREPFLT_DEFAULT,
+                                 multiple=T),
+                     selectInput("mf_post_processing_candidate_filter",
+                                 label="Postprocessing candidate filter",
+                                 choices=shinyscreen:::METFRAG_POSTPFLT_CHOICES,
+                                 selected=shinyscreen:::METFRAG_POSTPFLT_DEFAULT,
+                                 multiple=T),
+                     textInput("mf_score_types",label="Score
+             Types",value=METFRAG_STANDARD_SCORES),
+             textInput("mf_score_weights",label="Score Weights",
+                       value=METFRAG_STANDARD_WEIGHTS))
+    }
+
+
+    ctrls
+}
diff --git a/R/shiny-ui-base.R b/R/shiny-ui-base.R
index 4941b39d44f46ea8eb50da3c88a4788f7c0a52f6..18a7c7930e6b83676befce753de73d7a5b4e7712 100644
--- a/R/shiny-ui-base.R
+++ b/R/shiny-ui-base.R
@@ -970,10 +970,7 @@ mk_shinyscreen_server <- function(projects,init) {
             wd <- input$proj_list
             req(!is.null(wd) && !is.na(wd) && nchar(wd)>0)
             fullwd <- file.path(init$projects,wd)
-            fullwdq <- file.exists(fullwd)
-            if (!fullwdq) {
-                stop("The project path does not exist!?")
-            }
+            check_dir_absent(fullwd,what="project")
             ## Load saved state if existing, create if it does not.
             fn_packed_state <- file.path(fullwd,FN_GUI_STATE)
             fn_state <- file.path(fullwd,FN_STATE)
@@ -1198,6 +1195,24 @@ mk_shinyscreen_server <- function(projects,init) {
             })
         })
 
+        ## OBSERVERS: METFRAG
+
+        observeEvent(input$mf_database_type,{
+            dtype = input$mf_database_type
+            if (dtype %in% METFRAG_LOCAL_DATABASE_TYPE) {
+                if (dtype == "LocalCSV") patt = "(csv)|(CSV)$"
+                if (dtype == "LocalSDF") patt = "(sdf)|(SDF)$"
+                if (dtype == "LocalPSV") patt = "(psv)|(PSV)$"
+                updateSelectInput(session=session,
+                                  inputId="mf_local_database_path",
+                                  choices=list.files(path=init$envopts$metfrag$db_dir,
+                                                     pattern=patt))
+            } else {
+                   updateSelectInput(session=session,
+                                     inputId="mf_local_database_path",
+                                     choices=character(0))
+            }
+        }, label = "mf-database-type")
         
         ## OBSERVERS: VIEWER
 
@@ -1519,6 +1534,18 @@ mk_shinyscreen_server <- function(projects,init) {
             ##                              scroller = T))
         })
 
+        ## RENDER: METFRAG
+
+        output$cando_metfrag = renderText({
+            if (is_metfrag_available(init$envopts))
+                "available" else "unavailable"
+        })
+
+        output$metfrag_panel = renderUI({
+            ctrls = make_metfrag_panel(envopts=init$envopts)
+            do.call(tagList,ctrls)
+        })
+
         ## RENDER: STATUS
 
         output$is_extracted_stat <- renderText({
diff --git a/inst/rmd/app.Rmd b/inst/rmd/app.Rmd
index d539dbd69c3ed630676bf4e944bfcb821b11f285..a0c275ac0cf86a33d649be72fd3cca19d627cffd 100644
--- a/inst/rmd/app.Rmd
+++ b/inst/rmd/app.Rmd
@@ -493,48 +493,14 @@ shinyscreen::rt_input(input_rt = "ret_time_shift_tol",
                       def_unit = vu[['unit']])
 ```
 
-### MetFrag (experimental, optional)
+### MetFrag (`r htmlOutput("cando_metfrag", inline=T)`)
 
 If `java` and `MetFragCL` are available, Shinyscreen can run
 `MetFragCL`. The configuration is a subset of what a MetFrag config
 file looks like.
 
 ```{r, echo=F}
-numericInput("mf_database_search_relative_mass_deviation",
-             label="Database search relative mass deviation",
-             value=5)
-numericInput("mf_fragment_peak_match_absolute_mass_deviation",
-             label="Fragment peak match absolute mass deviation",
-             value=5)
-numericInput("mf_fragment_peak_match_relative_mass_deviation",
-             label="Fragment peak match relative mass deviation",
-             value=5)
-numericInput("mf_maximum_tree_depth",
-             label="MaximumTreeDepth",
-             value=2)
-selectInput("mf_metfrag_candidate_writer",
-            label="MetFrag Candidate Writer",
-            choices=shinyscreen:::METFRAG_WRITER_CHOICES,
-            selected=shinyscreen:::METFRAG_DEFAULT_WRITER)
-selectInput("mf_database_type",
-            label="Database type",
-            choices=shinyscreen:::METFRAG_DATABASE_TYPE,
-            selected=shinyscreen:::METFRAG_DEFAULT_DATABASE_TYPE)
-selectInput("mf_local_database_path","Local Database Path",
-            choices=NA_character_,
-            selected=NA_character_)
-selectInput("mf_pre_processing_candidate_filter",
-            label="Preprocessing candidate filter",
-            choices=shinyscreen:::METFRAG_PREPFLT_CHOICES,
-            selected=shinyscreen:::METFRAG_PREPFLT_DEFAULT,
-            multiple=T)
-selectInput("mf_post_processing_candidate_filter",
-            label="Postprocessing candidate filter",
-            choices=shinyscreen:::METFRAG_POSTPFLT_CHOICES,
-            selected=shinyscreen:::METFRAG_POSTPFLT_DEFAULT,
-            multiple=T)
-textInput("mf_score_types",label="Score Types",value=METFRAG_STANDARD_SCORES)
-textInput("mf_score_weights",label="Score Weights",value=METFRAG_STANDARD_WEIGHTS)
+uiOutput('metfrag_panel')
 ```
 ### Report
 
diff --git a/tests/testthat/test-shiny-state.R b/tests/testthat/test-shiny-state.R
new file mode 100644
index 0000000000000000000000000000000000000000..0b57b7cc72e08a98bdfa648c7ba892466aa3ea27
--- /dev/null
+++ b/tests/testthat/test-shiny-state.R
@@ -0,0 +1,37 @@
+test_that("Test make_metfrag_panel (local and remote)",{
+    withr::with_tempdir({
+        mfdb = "demodb.csv"
+        mfdbdir = "demodir"
+        dir.create(mfdbdir)
+        mfjar = "demomf.jar"
+        saveRDS("",file.path(mfdbdir,mfdb))
+        saveRDS("",mfjar)
+
+        eo = envopts(metfrag_db_dir = mfdbdir,
+                     metfrag_jar = mfjar)
+
+        xx = make_metfrag_panel(eo)
+        res = sapply(METFRAG_DATABASE_TYPE,function (p) {grepl(p,xx[[6L]])})
+        expect_true(all(res))
+    })
+})
+
+test_that("Test make_metfrag_panel (remote)",{
+    withr::with_tempdir({
+        mfjar = "demomf.jar"
+        saveRDS("",mfjar)
+        eo = envopts(metfrag_jar = mfjar)
+        xx = make_metfrag_panel(eo)
+        res = sapply(METFRAG_REMOTE_DATABASE_TYPE,function (p) {grepl(p,xx[[6L]])})
+        expect_true(all(res))
+        
+        res2 = sapply(METFRAG_LOCAL_DATABASE_TYPE,function (p) {grepl(p,xx[[6L]])})
+        expect_false(any(res2))
+    })
+})
+
+test_that("Test make_metfrage_panel (disabled)",{
+    eo = envopts()
+    xx = make_metfrag_panel(eo)
+    expect_true(length(xx)==1L)
+})