diff --git a/perxis-proto b/perxis-proto
index b0b20c34b7b106bf6704a78fdaf8b808459bda14..81c967842f55811b459e455572703631712d7d86 160000
--- a/perxis-proto
+++ b/perxis-proto
@@ -1 +1 @@
-Subproject commit b0b20c34b7b106bf6704a78fdaf8b808459bda14
+Subproject commit 81c967842f55811b459e455572703631712d7d86
diff --git a/pkg/expr/expr.go b/pkg/expr/expr.go
index 9c788e71e108e85b483ec71a8700b7fc3bb046be..93d6ee612968ac0a7e1c963a05cda4955532f110 100644
--- a/pkg/expr/expr.go
+++ b/pkg/expr/expr.go
@@ -1,12 +1,21 @@
 package expr
 
 import (
+	"regexp"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/data"
 	compiler2 "github.com/antonmedv/expr/compiler"
 	"github.com/antonmedv/expr/parser"
 	"github.com/antonmedv/expr/vm"
 	"golang.org/x/net/context"
 )
 
+var (
+	additionalFunctions = []string{"contains", "startsWith", "endsWith", "and", "or", "in", "not"}
+	isExpression        = regexp.MustCompile(`[()}{<>=|&%*+\-\/\]\[\\]`).MatchString
+)
+
 const EnvContextKey = "$context"
 
 func Eval(ctx context.Context, input string, env map[string]interface{}) (interface{}, error) {
@@ -56,3 +65,17 @@ func EvalKV(ctx context.Context, input string, kv ...interface{}) (interface{},
 
 	return Eval(ctx, input, m)
 }
+
+func IsExpression(input string) bool {
+	if isExpression(input) {
+		return true
+	}
+
+	for _, s := range strings.Fields(input) {
+		if data.Contains(s, additionalFunctions) {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/expr/expr_test.go b/pkg/expr/expr_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5eafc368bed934e0bd0805f4818442a75256931c
--- /dev/null
+++ b/pkg/expr/expr_test.go
@@ -0,0 +1,51 @@
+package expr
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsExpression(t *testing.T) {
+	now := time.Now()
+
+	tests := []struct {
+		name string
+		eval string
+		want bool
+	}{
+		{"equal", "i == 3", true},
+		{"in array", "i in [1,2,3]", true},
+		{"contains", "value contains 'some'", true},
+		{"contains with . + () $ {} ^", "value contains 'something with . + () $ {} ^'", true},
+		{"startsWith", "value startsWith 'some'", true},
+		{"startsWith . + () $ {} ^", "value startsWith '. + () $ {} ^'", true},
+		{"endsWith", "value endsWith 'some'", true},
+		{"endsWith . + () $ {} ^", "value endsWith '. + () $ {} ^'", true},
+		{"icontains", "icontains(value, 'some')", true},
+		{"icontains with . + () $ {} ^", "icontains (value, 'something with . + () $ {} ^')", true},
+		{"istartsWith", "istartsWith(value, 'Some')", true},
+		{"istartsWith . + () $ {} ^ . + () $ {} ^", "istartsWith(value, '. + () $ {} ^')", true},
+		{"iendsWith", "iendsWith(value, 'some')", true},
+		{"iendsWith . + () $ {} ^", "iendsWith(value,'. + () $ {} ^')", true},
+		{"or", "i == 2 || i > 10", true},
+		{"search", "search('some') || i > 10", true},
+		{"vars:or", "i == a + 2 || i > a + 10", true},
+		{"near", "near(a, [55.5, 37.5], 1000)", true},
+		{"within", "within(a, 'box', [[54.54, 36.36], [55.55, 37.37]])", true},
+		{"time", "d > Time.Date('2021-08-31')", true},
+		{"time", fmt.Sprintf("d > Time.Time('%s')", now.Format(time.RFC3339)), true},
+		{"in", "In(s, [1,2,3])", true},
+		{"in", "In(s, 1)", true},
+		{"text search or id", "id", false},
+		{"numbers", "3", false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := IsExpression(tt.eval)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}