diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index f8199caedd..85b8e21095 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestController;
 import org.thingsboard.rule.engine.api.ScriptEngine;
 import org.thingsboard.script.api.js.JsInvokeService;
+import org.thingsboard.script.api.mvel.MvelInvokeService;
 import org.thingsboard.server.actors.ActorSystemContext;
 import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
 import org.thingsboard.server.common.data.EventInfo;
@@ -60,6 +61,7 @@ import org.thingsboard.server.common.data.rule.RuleChainImportResult;
 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
 import org.thingsboard.server.common.data.rule.RuleChainType;
+import org.thingsboard.server.common.data.script.ScriptLanguage;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -67,6 +69,7 @@ import org.thingsboard.server.dao.event.EventService;
 import org.thingsboard.server.queue.util.TbCoreComponent;
 import org.thingsboard.server.service.rule.TbRuleChainService;
 import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
+import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
 import org.thingsboard.server.service.security.permission.Operation;
 import org.thingsboard.server.service.security.permission.Resource;
 
@@ -117,10 +120,10 @@ public class RuleChainController extends BaseController {
     private static final String RULE_CHAIN_DESCRIPTION = "The rule chain object is lightweight and contains general information about the rule chain. " +
             "List of rule nodes and their connection is stored in a separate 'metadata' object.";
     private static final String RULE_CHAIN_METADATA_DESCRIPTION = "The metadata object contains information about the rule nodes and their connections.";
-    private static final String TEST_JS_FUNCTION = "Execute the JavaScript function and return the result. The format of request: \n\n"
+    private static final String TEST_SCRIPT_FUNCTION = "Execute the Script function and return the result. The format of request: \n\n"
             + MARKDOWN_CODE_BLOCK_START
             + "{\n" +
-            "  \"script\": \"Your JS Function as String\",\n" +
+            "  \"script\": \"Your Function as String\",\n" +
             "  \"scriptType\": \"One of: update, generate, filter, switch, json, string\",\n" +
             "  \"argNames\": [\"msg\", \"metadata\", \"type\"],\n" +
             "  \"msg\": \"{\\\"temperature\\\": 42}\", \n" +
@@ -140,7 +143,10 @@ public class RuleChainController extends BaseController {
     private EventService eventService;
 
     @Autowired
-    private JsInvokeService scriptInvokeService;
+    private JsInvokeService jsInvokeService;
+
+    @Autowired(required = false)
+    private MvelInvokeService mvelInvokeService;
 
     @Autowired(required = false)
     private ActorSystemContext actorContext;
@@ -148,6 +154,9 @@ public class RuleChainController extends BaseController {
     @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}")
     private boolean debugPerTenantEnabled;
 
+    @Value("${mvel.enabled:true}")
+    private boolean mvelEnabled;
+
     @ApiOperation(value = "Get Rule Chain (getRuleChainById)",
             notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
     @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@@ -369,13 +378,23 @@ public class RuleChainController extends BaseController {
         }
     }
 
+    @ApiOperation(value = "Is MVEL script executor enabled",
+            notes = "Returns 'True' if the MVEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET)
+    @ResponseBody
+    public Boolean isMvelEnabled() {
+        return mvelEnabled;
+    }
 
-    @ApiOperation(value = "Test JavaScript function",
-            notes = TEST_JS_FUNCTION + TENANT_AUTHORITY_PARAGRAPH)
+    @ApiOperation(value = "Test Script function",
+            notes = TEST_SCRIPT_FUNCTION + TENANT_AUTHORITY_PARAGRAPH)
     @PreAuthorize("hasAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
     @ResponseBody
     public JsonNode testScript(
+            @ApiParam(value = "Script language: JS or MVEL")
+            @RequestParam(required = false) ScriptLanguage scriptLang,
             @ApiParam(value = "Test JS request. See API call description above.")
             @RequestBody JsonNode inputParams) throws ThingsboardException {
         try {
@@ -393,7 +412,17 @@ public class RuleChainController extends BaseController {
             String errorText = "";
             ScriptEngine engine = null;
             try {
-                engine = new RuleNodeJsScriptEngine(getTenantId(), scriptInvokeService, script, argNames);
+                if (scriptLang == null) {
+                    scriptLang = ScriptLanguage.JS;
+                }
+                if (ScriptLanguage.JS.equals(scriptLang)) {
+                    engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames);
+                } else {
+                    if (mvelInvokeService == null) {
+                        throw new IllegalArgumentException("MVEL script engine is disabled!");
+                    }
+                    engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames);
+                }
                 TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
                 switch (scriptType) {
                     case "update":
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
index 373c685cc1..c7d964e017 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
@@ -40,9 +40,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
                 "If True - send Message via True chain, otherwise False chain is used." +
                 "Message payload can be accessed via msg property. For example msg.temperature < 10;
" +
                 "Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" +
-                "Message type can be accessed via msgType property."
-//        uiResources = {"static/rulenode/rulenode-core-config.js"},
-//        configDirective = "tbFilterNodeScriptConfig")
+                "Message type can be accessed via msgType property.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbFilterNodeScriptConfig"
 )
 public class TbJsFilterNode implements TbNode {
 
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
index 3ef6f787d4..fd33bb4554 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
@@ -40,9 +40,9 @@ import java.util.List;
                 "msgType - is a Message type.
" +
                 "Should return the following structure:
" +
                 "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }
" +
-                "All fields in resulting object are optional and will be taken from original message if not specified."
-//        uiResources = {"static/rulenode/rulenode-core-config.js"},
-//        configDirective = "tbTransformationNodeScriptConfig"
+                "All fields in resulting object are optional and will be taken from original message if not specified.",
+        uiResources = {"static/rulenode/rulenode-core-config.js"},
+        configDirective = "tbTransformationNodeScriptConfig"
 )
 public class TbTransformMsgNode extends TbAbstractTransformNode {
 
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
index d9706f6b96..b4b4563b4d 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
 import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.script.ScriptLanguage;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -180,7 +181,7 @@ public class TbAlarmNodeTest {
 
         verifyError(msg, "message", NotImplementedException.class);
 
-        verify(ctx).createJsScriptEngine("DETAILS");
+        verify(ctx).createScriptEngine(ScriptLanguage.JS, "DETAILS");
         verify(ctx).getAlarmService();
         verify(ctx, times(3)).getDbCallbackExecutor();
         verify(ctx).logJsEvalRequest();
@@ -395,12 +396,13 @@ public class TbAlarmNodeTest {
         config.setPropagate(true);
         config.setSeverity("$[alarmSeverity]");
         config.setAlarmType("SomeType");
+        config.setScriptLang(ScriptLanguage.JS);
         config.setAlarmDetailsBuildJs("DETAILS");
         config.setDynamicSeverity(true);
         ObjectMapper mapper = new ObjectMapper();
         TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-        when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+        when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
 
         when(ctx.getTenantId()).thenReturn(tenantId);
         when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -456,6 +458,7 @@ public class TbAlarmNodeTest {
     public void testCreateAlarmWithDynamicSeverityFromMetadata() throws Exception {
         TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration();
         config.setPropagate(true);
+        config.setScriptLang(ScriptLanguage.JS);
         config.setSeverity("${alarmSeverity}");
         config.setAlarmType("SomeType");
         config.setAlarmDetailsBuildJs("DETAILS");
@@ -463,7 +466,7 @@ public class TbAlarmNodeTest {
         ObjectMapper mapper = new ObjectMapper();
         TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-        when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+        when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
 
         when(ctx.getTenantId()).thenReturn(tenantId);
         when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -521,12 +524,13 @@ public class TbAlarmNodeTest {
             config.setPropagateToTenant(true);
             config.setSeverity(CRITICAL.name());
             config.setAlarmType("SomeType" + i);
+            config.setScriptLang(ScriptLanguage.JS);
             config.setAlarmDetailsBuildJs("DETAILS");
             config.setDynamicSeverity(true);
             ObjectMapper mapper = new ObjectMapper();
             TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-            when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+            when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
 
             when(ctx.getTenantId()).thenReturn(tenantId);
             when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -584,11 +588,12 @@ public class TbAlarmNodeTest {
             config.setPropagate(true);
             config.setSeverity(CRITICAL.name());
             config.setAlarmType("SomeType");
+            config.setScriptLang(ScriptLanguage.JS);
             config.setAlarmDetailsBuildJs("DETAILS");
             ObjectMapper mapper = new ObjectMapper();
             TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-            when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+            when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
 
             when(ctx.getTenantId()).thenReturn(tenantId);
             when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -605,11 +610,12 @@ public class TbAlarmNodeTest {
         try {
             TbClearAlarmNodeConfiguration config = new TbClearAlarmNodeConfiguration();
             config.setAlarmType("SomeType");
+            config.setScriptLang(ScriptLanguage.JS);
             config.setAlarmDetailsBuildJs("DETAILS");
             ObjectMapper mapper = new ObjectMapper();
             TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-            when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+            when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
 
             when(ctx.getTenantId()).thenReturn(tenantId);
             when(ctx.getAlarmService()).thenReturn(alarmService);
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
index 63c94ea9a5..f40c6f0072 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
@@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 import org.thingsboard.rule.engine.api.TbNodeException;
 import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
+import org.thingsboard.server.common.data.script.ScriptLanguage;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -54,8 +55,6 @@ public class TbJsFilterNodeTest {
     @Mock
     private TbContext ctx;
     @Mock
-    private ListeningExecutor executor;
-    @Mock
     private ScriptEngine scriptEngine;
 
     private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
@@ -73,7 +72,7 @@ public class TbJsFilterNodeTest {
     }
 
     @Test
-    public void exceptionInJsThrowsException() throws TbNodeException, ScriptException {
+    public void exceptionInJsThrowsException() throws TbNodeException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
         TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
@@ -85,7 +84,7 @@ public class TbJsFilterNodeTest {
     }
 
     @Test
-    public void metadataConditionCanBeTrue() throws TbNodeException, ScriptException {
+    public void metadataConditionCanBeTrue() throws TbNodeException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
         TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
@@ -98,11 +97,12 @@ public class TbJsFilterNodeTest {
 
     private void initWithScript() throws TbNodeException {
         TbJsFilterNodeConfiguration config = new TbJsFilterNodeConfiguration();
+        config.setScriptLang(ScriptLanguage.JS);
         config.setJsScript("scr");
         ObjectMapper mapper = new ObjectMapper();
         TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-        when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
+        when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine);
 
         node = new TbJsFilterNode();
         node.init(ctx, nodeConfiguration);
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
index 411db21c7f..89480b404e 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
@@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 import org.thingsboard.rule.engine.api.TbNodeException;
 import org.thingsboard.server.common.data.id.RuleChainId;
 import org.thingsboard.server.common.data.id.RuleNodeId;
+import org.thingsboard.server.common.data.script.ScriptLanguage;
 import org.thingsboard.server.common.msg.TbMsg;
 import org.thingsboard.server.common.msg.TbMsgDataType;
 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -55,12 +56,10 @@ public class TbTransformMsgNodeTest {
     @Mock
     private TbContext ctx;
     @Mock
-    private ListeningExecutor executor;
-    @Mock
     private ScriptEngine scriptEngine;
 
     @Test
-    public void metadataCanBeUpdated() throws TbNodeException, ScriptException {
+    public void metadataCanBeUpdated() throws TbNodeException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
@@ -80,7 +79,7 @@ public class TbTransformMsgNodeTest {
     }
 
     @Test
-    public void exceptionHandledCorrectly() throws TbNodeException, ScriptException {
+    public void exceptionHandledCorrectly() throws TbNodeException {
         initWithScript();
         TbMsgMetaData metaData = new TbMsgMetaData();
         metaData.putValue("temp", "7");
@@ -97,11 +96,12 @@ public class TbTransformMsgNodeTest {
 
     private void initWithScript() throws TbNodeException {
         TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration();
+        config.setScriptLang(ScriptLanguage.JS);
         config.setJsScript("scr");
         ObjectMapper mapper = new ObjectMapper();
         TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
 
-        when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
+        when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine);
 
         node = new TbTransformMsgNode();
         node.init(ctx, nodeConfiguration);