diff --git a/src/daemon/config/dyncfg-intercept.c b/src/daemon/config/dyncfg-intercept.c
index dd7052e728..812059f6fc 100644
--- a/src/daemon/config/dyncfg-intercept.c
+++ b/src/daemon/config/dyncfg-intercept.c
@@ -180,7 +180,7 @@ static int dyncfg_intercept_early_error(struct rrd_function_execute *rfe, int rc
     return rc;
 }
 
-static const DICTIONARY_ITEM *dyncfg_get_template_of_new_job(const char *job_id) {
+const DICTIONARY_ITEM *dyncfg_get_template_of_new_job(const char *job_id) {
     char id_copy[strlen(job_id) + 1];
     memcpy(id_copy, job_id, sizeof(id_copy));
 
diff --git a/src/daemon/config/dyncfg-internals.h b/src/daemon/config/dyncfg-internals.h
index df9af6fd52..181d2328fc 100644
--- a/src/daemon/config/dyncfg-internals.h
+++ b/src/daemon/config/dyncfg-internals.h
@@ -76,6 +76,8 @@ const DICTIONARY_ITEM *dyncfg_add_internal(RRDHOST *host, const char *id, const
 int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data);
 void dyncfg_cleanup(DYNCFG *v);
 
+const DICTIONARY_ITEM *dyncfg_get_template_of_new_job(const char *job_id);
+
 bool dyncfg_is_user_disabled(const char *id);
 
 RRDHOST *dyncfg_rrdhost_by_uuid(UUID *uuid);
diff --git a/src/daemon/config/dyncfg-tree.c b/src/daemon/config/dyncfg-tree.c
index 0983a9ee1f..6af384daa2 100644
--- a/src/daemon/config/dyncfg-tree.c
+++ b/src/daemon/config/dyncfg-tree.c
@@ -204,31 +204,57 @@ static int dyncfg_config_execute_cb(struct rrd_function_execute *rfe, void *data
         action = path;
         path = NULL;
 
-        if(id && *id && dyncfg_cmds2id(action) == DYNCFG_CMD_REMOVE) {
-            const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
-            if(item) {
-                DYNCFG *df = dictionary_acquired_item_value(item);
+        DYNCFG_CMDS cmd = dyncfg_cmds2id(action);
+        const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
+        if(!item)
+            item = dyncfg_get_template_of_new_job(id);
 
-                if(!rrd_function_available(host, string2str(df->function)))
-                    df->current.status = DYNCFG_STATUS_ORPHAN;
+        if(item) {
+            DYNCFG *df = dictionary_acquired_item_value(item);
 
+            if(!rrd_function_available(host, string2str(df->function)))
+                df->current.status = DYNCFG_STATUS_ORPHAN;
+
+            if(cmd == DYNCFG_CMD_REMOVE) {
                 bool delete = (df->current.status == DYNCFG_STATUS_ORPHAN);
                 dictionary_acquired_item_release(dyncfg_globals.nodes, item);
+                item = NULL;
 
                 if(delete) {
+                    if(!http_access_user_has_enough_access_level_for_endpoint(rfe->user_access, df->edit_access)) {
+                        code = dyncfg_default_response(
+                            rfe->result.wb, HTTP_RESP_FORBIDDEN,
+                            "dyncfg: you don't have enough edit permissions to execute this command");
+                        goto cleanup;
+                    }
+
                     dictionary_del(dyncfg_globals.nodes, id);
                     dyncfg_file_delete(id);
                     code = dyncfg_default_response(rfe->result.wb, 200, "");
                     goto cleanup;
                 }
             }
+            else if(cmd == DYNCFG_CMD_TEST && df->type == DYNCFG_TYPE_TEMPLATE && df->current.status != DYNCFG_STATUS_ORPHAN) {
+                const char *old_rfe_function = rfe->function;
+                char buf2[2048];
+                snprintfz(buf2, sizeof(buf2), "config %s %s", dictionary_acquired_item_name(item), action);
+                rfe->function = buf2;
+                dictionary_acquired_item_release(dyncfg_globals.nodes, item);
+                item = NULL;
+                code = dyncfg_function_intercept_cb(rfe, data);
+                rfe->function = old_rfe_function;
+                return code;
+            }
+
+            if(item)
+                dictionary_acquired_item_release(dyncfg_globals.nodes, item);
         }
 
         code = HTTP_RESP_NOT_FOUND;
         nd_log(NDLS_DAEMON, NDLP_ERR,
                "DYNCFG: unknown config id '%s' in call: '%s'. "
                "This can happen if the plugin that registered the dynamic configuration is not running now.",
-               action, rfe->function);
+               id, rfe->function);
 
         rrd_call_function_error(
             rfe->result.wb,
@@ -248,7 +274,11 @@ cleanup:
 // for which there is no id overloaded.
 
 void dyncfg_host_init(RRDHOST *host) {
+    // IMPORTANT:
+    // This function needs to be async, although it is internal.
+    // The reason is that it can call by itself another function that may or may not be internal (sync).
+
     rrd_function_add(host, NULL, PLUGINSD_FUNCTION_CONFIG, 120,
                      1000, "Dynamic configuration", "config", HTTP_ACCESS_ANONYMOUS_DATA,
-                     true, dyncfg_config_execute_cb, host);
+                     false, dyncfg_config_execute_cb, host);
 }