diff --git a/pkg/action/action.go b/pkg/action/action.go
deleted file mode 100644
index 4233d986f5cf0ecd199f64c42b5f6453e0533fce..0000000000000000000000000000000000000000
--- a/pkg/action/action.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package action
-
-import (
-	"net/url"
-	"strings"
-)
-
-// URL структура для хранения данных о переданном действии.
-type URL struct {
-	*url.URL
-}
-
-// NewURL возвращает структуру ActionURL
-func NewURL(action string) (*URL, error) {
-	u, err := url.Parse(action)
-	if err != nil {
-		return nil, err
-	}
-	return &URL{URL: u}, nil
-}
-
-func (u *URL) actionParts() (string, string) {
-	if u.URL != nil && u.URL.Scheme == "grpc" {
-		splitPath := strings.Split(strings.TrimLeft(u.Path, "/"), "/")
-		if len(splitPath) >= 2 {
-			return splitPath[0], splitPath[1]
-		}
-	}
-	return "", ""
-}
-
-func (u *URL) Action() string {
-	_, action := u.actionParts()
-	return action
-}
-
-func (u *URL) Extension() string {
-	ext, _ := u.actionParts()
-	return ext
-}
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)
+		})
+	}
+}
diff --git a/pkg/extension/action_handler.go b/pkg/extension/action_handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..3079b88582a8c392b948bb5d46ff2bbbb7d91e06
--- /dev/null
+++ b/pkg/extension/action_handler.go
@@ -0,0 +1,39 @@
+package extension
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+var (
+	ErrInvalidAction = errors.New("invalid action")
+)
+
+// ActionHandler выполняет действие в url
+type ActionHandler func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error)
+
+// ActionRouter возвращает ActionHandler для указанного действия
+type ActionRouter func(ctx context.Context, url *ActionURL, req *ActionRequest) (ActionHandler, error)
+
+// Chain возвращает ActionRouter, который последовательно вызывает переданные ActionRouter
+func Chain(chain ...ActionRouter) ActionRouter {
+	return func(ctx context.Context, url *ActionURL, req *ActionRequest) (ActionHandler, error) {
+		for _, f := range chain {
+			if h, err := f(ctx, url, req); err == nil && h != nil {
+				return h, nil
+			}
+		}
+		return nil, ErrInvalidAction
+	}
+}
+
+// Named возвращает ActionRouter, который возвращает ActionHandler для указанного действия по имени
+func Named(name string, handler ActionHandler) ActionRouter {
+	return func(ctx context.Context, url *ActionURL, req *ActionRequest) (ActionHandler, error) {
+		if url.Action() == name {
+			return handler, nil
+		}
+		return nil, ErrInvalidAction
+	}
+}
diff --git a/pkg/extension/action_handler_test.go b/pkg/extension/action_handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c73a3b87c5e4a372e7a67858ab508cbe7ec93cfc
--- /dev/null
+++ b/pkg/extension/action_handler_test.go
@@ -0,0 +1,99 @@
+package extension
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestActionHandler(t *testing.T) {
+	tests := []struct {
+		name    string
+		request *ActionRequest
+		router  ActionRouter
+		want    string
+		wantErr bool
+	}{
+		{
+			name:    "No named action call",
+			request: &ActionRequest{Action: "grpc:///ext/action/"},
+			router: func(ctx context.Context, url *ActionURL, req *ActionRequest) (ActionHandler, error) {
+				return func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+					return &ActionResponse{State: 0, Msg: "Called - action"}, nil
+				}, nil
+			},
+			want:    "Called - action",
+			wantErr: false,
+		},
+		{
+			name:    "Named action call",
+			request: &ActionRequest{Action: "grpc:///ext/action/"},
+			router: Named("action", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+				return &ActionResponse{State: 0, Msg: "Called named action"}, nil
+			}),
+			want:    "Called named action",
+			wantErr: false,
+		},
+		{
+			name:    "Named action chain call",
+			request: &ActionRequest{Action: "grpc:///ext/action1/"},
+			router: Chain(
+				Named("action1", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+					return &ActionResponse{State: 0, Msg: "Called named action1"}, nil
+				}),
+				Named("action2", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+					return &ActionResponse{State: 0, Msg: "Called named action2"}, nil
+				}),
+			),
+			want:    "Called named action1",
+			wantErr: false,
+		},
+		{
+			name:    "Error (no action in router)",
+			request: &ActionRequest{Action: "grpc:///ext/action3/"},
+			router: Chain(
+				Named("action1", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+					return &ActionResponse{State: 0, Msg: "Called named action1"}, nil
+				}),
+				Named("action2", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+					return &ActionResponse{State: 0, Msg: "Called named action2"}, nil
+				}),
+			),
+			wantErr: true,
+		},
+		{
+			name:    "Named action call with deprecated request",
+			request: &ActionRequest{Action: "action"},
+			router: Named("action", func(ctx context.Context, url *ActionURL, req *ActionRequest) (*ActionResponse, error) {
+				return &ActionResponse{State: 0, Msg: "Called named deprecated action"}, nil
+			}),
+			want:    "Called named deprecated action",
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			url, err := NewActionURL(tt.request.Action)
+			require.NoError(t, err)
+
+			handler, err := tt.router(context.Background(), url, tt.request)
+			if tt.wantErr {
+				require.Error(t, err)
+				return
+			} else {
+				require.NoError(t, err)
+			}
+
+			resp, err := handler(context.Background(), url, tt.request)
+			if tt.wantErr {
+				require.Error(t, err)
+			} else {
+				require.NoError(t, err)
+			}
+
+			assert.Equal(t, tt.want, resp.Msg)
+		})
+	}
+}
diff --git a/pkg/extension/action_url.go b/pkg/extension/action_url.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec03375f840bcd3889cba680e5b9087dd89c7f80
--- /dev/null
+++ b/pkg/extension/action_url.go
@@ -0,0 +1,42 @@
+package extension
+
+import (
+	"net/url"
+	"strings"
+)
+
+// URL структура для хранения данных о переданном действии.
+type ActionURL struct {
+	*url.URL
+}
+
+// NewURL возвращает структуру ActionURL
+func NewActionURL(action string) (*ActionURL, error) {
+	u, err := url.Parse(action)
+	if err != nil {
+		return nil, err
+	}
+	return &ActionURL{URL: u}, nil
+}
+
+func (u *ActionURL) actionParts() (string, string) {
+	splitPath := strings.Split(strings.TrimLeft(u.Path, "/"), "/")
+	if u.URL != nil && u.URL.Scheme == "grpc" && len(splitPath) >= 2 {
+		return splitPath[0], splitPath[1]
+	}
+	// for backwards compatibility
+	if len(splitPath) == 1 {
+		return "", splitPath[0]
+	}
+	return "", ""
+}
+
+func (u *ActionURL) Action() string {
+	_, action := u.actionParts()
+	return action
+}
+
+func (u *ActionURL) Extension() string {
+	ext, _ := u.actionParts()
+	return ext
+}
diff --git a/pkg/action/action_test.go b/pkg/extension/action_url_test.go
similarity index 71%
rename from pkg/action/action_test.go
rename to pkg/extension/action_url_test.go
index 2988744f4b88b734c4085b5aa20ceb95fe0657da..e4545b39a21922222ff7a0f140f8488007168f22 100644
--- a/pkg/action/action_test.go
+++ b/pkg/extension/action_url_test.go
@@ -1,4 +1,4 @@
-package action
+package extension
 
 import (
 	"fmt"
@@ -12,13 +12,13 @@ func TestActionURL_New(t *testing.T) {
 	tests := []struct {
 		name    string
 		action  string
-		want    *URL
+		want    *ActionURL
 		url     string
 		wantErr assert.ErrorAssertionFunc
 	}{
 		{
 			name: "Without action",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{},
 			},
 			wantErr: assert.NoError,
@@ -26,7 +26,7 @@ func TestActionURL_New(t *testing.T) {
 		{
 			name:   "Without deprecated action call",
 			action: "build-site",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{
 					Path: "build-site",
 				},
@@ -37,7 +37,7 @@ func TestActionURL_New(t *testing.T) {
 		{
 			name:   "With grpc action",
 			action: "grpc:///perxisweb/build-site",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{
 					Scheme: "grpc",
 					Path:   "/perxisweb/build-site",
@@ -49,7 +49,7 @@ func TestActionURL_New(t *testing.T) {
 		{
 			name:   "With ui action",
 			action: "ui:///space/env/coll",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{
 					Scheme: "ui",
 					Path:   "/space/env/coll",
@@ -61,7 +61,7 @@ func TestActionURL_New(t *testing.T) {
 		{
 			name:   "With http action",
 			action: "https://perx.ru",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{
 					Scheme: "https",
 					Host:   "perx.ru",
@@ -79,7 +79,7 @@ func TestActionURL_New(t *testing.T) {
 		{
 			name:   "With no action id",
 			action: "grpc:///perxisweb",
-			want: &URL{
+			want: &ActionURL{
 				URL: &url.URL{
 					Scheme: "grpc",
 					Path:   "/perxisweb",
@@ -90,7 +90,7 @@ func TestActionURL_New(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := NewURL(tt.action)
+			got, err := NewActionURL(tt.action)
 			if !tt.wantErr(t, err, fmt.Sprintf("NewURL(%v)", tt.action)) {
 				return
 			}
@@ -128,8 +128,45 @@ func TestActionURL_String(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			p, _ := NewURL(tt.url)
+			p, _ := NewActionURL(tt.url)
 			assert.Equalf(t, tt.url, p.String(), "String()")
 		})
 	}
 }
+
+func TestActionURL_Action(t *testing.T) {
+	tests := []struct {
+		name string
+		url  string
+		want string
+	}{
+		{
+			name: "GRPC action",
+			url:  "grpc:///perxisweb/build-site",
+			want: "build-site",
+		},
+		{
+			name: "UI action #1",
+			url:  "ui:///space/env/coll",
+		},
+		{
+			name: "UI action deprecated call #2",
+			url:  "space/env/coll",
+		},
+		{
+			name: "Https action",
+			url:  "https://perx.ru",
+		},
+		{
+			name: "With action deprecated call",
+			url:  "extension-id",
+			want: "extension-id",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p, _ := NewActionURL(tt.url)
+			assert.Equalf(t, tt.want, p.Action(), "Action()")
+		})
+	}
+}
diff --git a/pkg/extension/server.go b/pkg/extension/server.go
index 42c1f241e93bca687c5cf0afd75d63a74ceb22f2..0b16125450a89d001ab6fa2228454ad03085b6bb 100644
--- a/pkg/extension/server.go
+++ b/pkg/extension/server.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"strings"
 
-	"git.perx.ru/perxis/perxis-go/pkg/action"
 	"git.perx.ru/perxis/perxis-go/pkg/errors"
 	"git.perx.ru/perxis/perxis-go/pkg/operation"
 	commonpb "git.perx.ru/perxis/perxis-go/proto/common"
@@ -162,14 +161,15 @@ func (s *Server) Check(ctx context.Context, req *CheckRequest) (*operation.Proto
 func (s *Server) Action(ctx context.Context, in *pb.ActionRequest) (*pb.ActionResponse, error) {
 	actionURL, err := action.NewURL(in.Action)
 	if err != nil {
-		return nil, err
+		return nil, ErrInvalidAction
 	}
+
 	ext := actionURL.Extension()
 	if ext == "" {
 		ext = in.Extension
 	}
 	if ext == "" {
-		return nil, errors.New("extension ID required")
+		return nil, ErrInvalidAction
 	}
 
 	svc, err := s.extensions(ctx, ext)
diff --git a/pkg/extension/server_test.go b/pkg/extension/server_test.go
index f80988fab0c7846ed79bcf340cbd22a0748b13c5..8c93f48d00d87c45363a04eec2fbc55153cbc45b 100644
--- a/pkg/extension/server_test.go
+++ b/pkg/extension/server_test.go
@@ -69,7 +69,7 @@ func TestServer_Action(t *testing.T) {
 				EnvId:   "env",
 			},
 			want:    &ActionResponse{State: ResponseDone},
-			wantErr: "extension ID required",
+			wantErr: "invalid action",
 		},
 		{
 			name:      "Deprecated call",
@@ -91,7 +91,7 @@ func TestServer_Action(t *testing.T) {
 				EnvId:   "env",
 			},
 			want:    nil,
-			wantErr: errors.Wrap(ErrUnknownExtension, "test-extension-2").Error(),
+			wantErr: "invalid action",
 		},
 		{
 			name:      "Deprecated call, without extension",
@@ -102,7 +102,7 @@ func TestServer_Action(t *testing.T) {
 				EnvId:   "env",
 			},
 			want:    nil,
-			wantErr: "extension ID required",
+			wantErr: "invalid action",
 		},
 		{
 			name:      "Deprecated call, without action and extension)",
@@ -112,7 +112,7 @@ func TestServer_Action(t *testing.T) {
 				EnvId:   "env",
 			},
 			want:    nil,
-			wantErr: "extension ID required",
+			wantErr: "invalid action",
 		},
 	}
 	for _, tt := range tests {
diff --git a/pkg/extension/service/extension.go b/pkg/extension/service/extension.go
index 370e56529f1581db74f7dd9a2df4b7ba0b5138bf..995c343467fef3a9b1963ef5d65a1c230ad4c69d 100644
--- a/pkg/extension/service/extension.go
+++ b/pkg/extension/service/extension.go
@@ -4,10 +4,8 @@ import (
 	"context"
 	"fmt"
 
-	"git.perx.ru/perxis/perxis-go/pkg/action"
 	"git.perx.ru/perxis/perxis-go/pkg/clients"
 	"git.perx.ru/perxis/perxis-go/pkg/content"
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
 	"git.perx.ru/perxis/perxis-go/pkg/extension"
 	"git.perx.ru/perxis/perxis-go/pkg/roles"
 	"git.perx.ru/perxis/perxis-go/pkg/setup"
@@ -32,6 +30,7 @@ type Extension struct {
 	Content   *content.Content
 	Logger    *zap.Logger
 	manager   extension.Manager
+	router    extension.ActionRouter
 
 	withClient bool
 	role       *roles.Role
@@ -39,12 +38,13 @@ type Extension struct {
 	keyFn      extension.KeyFunc
 }
 
-func NewExtension(desc *extension.ExtensionDescriptor, cnt *content.Content, setupFunc SetupFunc, logger *zap.Logger) *Extension {
+func NewExtension(desc *extension.ExtensionDescriptor, cnt *content.Content, setupFunc SetupFunc, router extension.ActionRouter, logger *zap.Logger) *Extension {
 	if logger == nil {
 		logger = zap.NewNop()
 	}
 	return &Extension{
 		desc:      desc,
+		router:    router,
 		setupFunc: setupFunc,
 		Content:   cnt,
 		Logger:    logger,
@@ -151,25 +151,36 @@ func (s *Extension) Uninstall(ctx context.Context, in *extension.UninstallReques
 	return s.GetSetup(in.SpaceId, in.EnvId).WithForce(in.Force).WithRemove(in.Remove).Uninstall(ctx)
 }
 
-func (s *Extension) Action(ctx context.Context, in *extension.ActionRequest) (*extension.ActionResponse, error) {
-	actionURL, err := action.NewURL(in.Action)
-	if err != nil {
-		return nil, err
-	}
-	ext := actionURL.Extension()
+// isCorrectExtension проверяет что расширение в url совпадает с расширением расширения
+func (s *Extension) isCorrectExtension(ctx context.Context, url *extension.ActionURL, in *extension.ActionRequest) bool {
+	ext := url.Extension()
 	if ext == "" {
 		ext = in.Extension
 	}
-	if ext == "" {
-		return nil, errors.New("extension ID required")
-	}
-	ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, ext)
-	if err != nil {
-		return nil, errors.Wrap(err, "check extension installed")
+	if ext == s.desc.Extension {
+		return true
 	}
-	if !ok {
-		return nil, errors.New("extension not installed")
+	return false
+}
+
+func (s *Extension) Action(ctx context.Context, in *extension.ActionRequest) (*extension.ActionResponse, error) {
+	// TBD: нужно ли проверять что действие установлено в пространство
+	// мы так и не договорились, но проверка появилась
+	// пусть решает каждое расширение само
+	//
+	//ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, ext)
+	//if err != nil {
+	//	return nil, errors.Wrap(err, "check extension installed")
+	//}
+	//if !ok {
+	//	return nil, errors.New("extension not installed")
+	//}
+
+	if url, err := extension.NewActionURL(in.Action); s.router != nil && err == nil && url != nil && s.isCorrectExtension(ctx, url, in) {
+		if h, err := s.router(ctx, url, in); err == nil && h != nil {
+			return h(ctx, url, in)
+		}
 	}
 
-	return &extension.ActionResponse{}, nil
+	return nil, extension.ErrInvalidAction
 }
diff --git a/pkg/extension/service/extension_test.go b/pkg/extension/service/extension_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0582966ae25186f6ddef5014fdd972544521f8a
--- /dev/null
+++ b/pkg/extension/service/extension_test.go
@@ -0,0 +1,68 @@
+package service
+
+import (
+	"context"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/extension"
+	"github.com/stretchr/testify/require"
+)
+
+func TestExtension_Action(t *testing.T) {
+	tests := []struct {
+		name    string
+		desc    *extension.ExtensionDescriptor
+		in      *extension.ActionRequest
+		router  extension.ActionRouter
+		want    string
+		wantErr bool
+	}{
+		{
+			name: "Router with 1 named action",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test/action", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			want: "Called action",
+			router: extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone, Msg: "Called action"}, nil
+			}),
+			wantErr: false,
+		},
+		{
+			name: "Router with 2 named actions",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test/action2", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			want: "Called action2",
+			router: extension.Chain(extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone}, nil
+			}), extension.Named("action2", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone, Msg: "Called action2"}, nil
+			})),
+			wantErr: false,
+		},
+		{
+			name: "Error (no extension)",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test2/action2", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			router: extension.Chain(extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone}, nil
+			}), extension.Named("action2", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseParametersRequired}, nil
+			})),
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := &Extension{
+				desc:   tt.desc,
+				router: tt.router,
+			}
+			got, err := s.Action(context.Background(), tt.in)
+			if tt.wantErr {
+				require.Error(t, err)
+				return
+			}
+			require.Equal(t, tt.want, got.Msg)
+		})
+	}
+}
diff --git a/pkg/schema/field/number.go b/pkg/schema/field/number.go
index 502a2fef7c406b4b4dc96aad34efce2e518a1e28..40d9c8167f54d23faaa40666ec1934b451d7a516 100644
--- a/pkg/schema/field/number.go
+++ b/pkg/schema/field/number.go
@@ -10,6 +10,10 @@ import (
 const (
 	NumberFormatInt   = "int"
 	NumberFormatFloat = "float"
+
+	// The largest integer that can be represented in IEEE 754 double (64-bit) is a value from -2^53 to 2^53.
+	maxInt = 1<<53 - 1
+	minInt = -1<<53 + 1
 )
 
 var numberType = &NumberType{}
@@ -84,10 +88,19 @@ func (NumberType) decode(_ context.Context, field *Field, v interface{}) (interf
 	case NumberFormatInt:
 		switch i := n.(type) {
 		case int64:
+			if i > maxInt || i < minInt {
+				return nil, errors.New("integer out of range")
+			}
 			return i, nil
 		case uint64:
+			if i > maxInt {
+				return nil, errors.New("integer out of range")
+			}
 			return i, nil
 		case float64:
+			if i > maxInt || i < minInt {
+				return nil, errors.New("integer out of range")
+			}
 			return int64(math.Round(i)), nil
 		}
 	case NumberFormatFloat:
diff --git a/pkg/schema/field/number_test.go b/pkg/schema/field/number_test.go
index e731793d12135127dbfa67d949572f52708075a6..367ffe4249f36f2dff17aa1e61ef6a753ccabee8 100644
--- a/pkg/schema/field/number_test.go
+++ b/pkg/schema/field/number_test.go
@@ -1,6 +1,8 @@
 package field
 
 import (
+	"context"
+	"math"
 	"reflect"
 	"testing"
 )
@@ -13,18 +15,25 @@ func TestNumberField_Decode(t *testing.T) {
 		want    interface{}
 		wantErr bool
 	}{
-		{"Correct", Number("int"), int64(2), int64(2), false},     // #0
-		{"Correct", Number("int"), 2.2, int64(2), false},          // #1
-		{"Correct", Number("int"), 2, int64(2), false},            // #2
-		{"Correct", Number("int"), float32(2.2), int64(2), false}, // #3
-		{"Correct", Number("int"), float64(2.6), int64(3), false}, // #4
-		{"Correct", Number("int"), 2.6, int64(3), false},          // #5
+		{"Correct", Number("int"), int64(2), int64(2), false},                               // #0
+		{"Correct", Number("int"), 2.2, int64(2), false},                                    // #1
+		{"Correct", Number("int"), 2, int64(2), false},                                      // #2
+		{"Correct", Number("int"), float32(2.2), int64(2), false},                           // #3
+		{"Correct", Number("int"), float64(2.6), int64(3), false},                           // #4
+		{"Correct", Number("int"), 2.6, int64(3), false},                                    // #5
+		{"MaxInt64", Number(NumberFormatInt), int64(math.MaxInt64), nil, true},              // #6
+		{"MinInt64", Number(NumberFormatInt), int64(math.MinInt64), nil, true},              // #7
+		{"maxInt in float", Number(NumberFormatInt), float64(maxInt), int64(maxInt), false}, // #8
+		{"minInt in float", Number(NumberFormatInt), float64(minInt), int64(minInt), false}, // #9
+		{"Convert error", Number(NumberFormatInt), math.MaxFloat64, nil, true},              // #10
+		{"Convert error", Number(NumberFormatInt), -math.MaxFloat64, nil, true},             // #11
+		{"Convert error", Number(NumberFormatInt), float64(math.MaxInt64), nil, true},       // #13
+		{"Convert error", Number(NumberFormatInt), float64(math.MinInt64), nil, true},       // #14
 
-		{"Correct", Number("float"), int8(2), 2.0, false},                    // #6
-		{"Correct", Number("float"), 2.2, 2.2, false},                        // #7
-		{"Correct", Number("float"), 2, 2.0, false},                          // #8
-		{"Correct", Number("float"), float32(2.2), 2.200000047683716, false}, // #9
-		{"Correct", Number("float"), int64(2), 2.0, false},                   // #10
+		{"Correct", Number("float"), int8(2), 2.0, false},  // #15
+		{"Correct", Number("float"), 2.2, 2.2, false},      // #16
+		{"Correct", Number("float"), 2, 2.0, false},        // #17
+		{"Correct", Number("float"), int64(2), 2.0, false}, // #18
 
 		{"Wrong data", Number("int"), "", nil, true},         // #0
 		{"Wrong data", Number("int"), []byte(""), nil, true}, // #1
@@ -34,7 +43,7 @@ func TestNumberField_Decode(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := Decode(nil, tt.field, tt.data)
+			got, err := Decode(context.Background(), tt.field, tt.data)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr)
 				return
@@ -61,11 +70,10 @@ func TestNumberField_Encode(t *testing.T) {
 		{"Correct", Number("int"), float32(2.6), int64(3), false}, // #4
 		{"Correct", Number("int"), 2.6, int64(3), false},          // #5
 
-		{"Correct", Number("float"), int8(2), 2.0, false},                    // #6
-		{"Correct", Number("float"), 2.2, 2.2, false},                        // #7
-		{"Correct", Number("float"), 2, 2.0, false},                          // #8
-		{"Correct", Number("float"), float32(2.2), 2.200000047683716, false}, // #9
-		{"Correct", Number("float"), int64(2), 2.0, false},                   // #10
+		{"Correct", Number("float"), int8(2), 2.0, false},  // #6
+		{"Correct", Number("float"), 2.2, 2.2, false},      // #7
+		{"Correct", Number("float"), 2, 2.0, false},        // #8
+		{"Correct", Number("float"), int64(2), 2.0, false}, // #9
 
 		{"Wrong data", Number("int"), "", nil, true},         // #0
 		{"Wrong data", Number("int"), []byte(""), nil, true}, // #1
@@ -75,7 +83,7 @@ func TestNumberField_Encode(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := Encode(nil, tt.field, tt.data)
+			got, err := Encode(context.Background(), tt.field, tt.data)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr)
 				return
diff --git a/pkg/schema/test/object_test.go b/pkg/schema/test/object_test.go
index e5af975cdb9ed8e86044e5d357530dd2420afee9..38868f66989108c142e50eaad62539be381edb4f 100644
--- a/pkg/schema/test/object_test.go
+++ b/pkg/schema/test/object_test.go
@@ -74,6 +74,80 @@ func TestNumberField_JSON(t *testing.T) {
 	assert.Equal(t, fld, res)
 }
 
+func TestNumberField_JSON_With_MaxInt(t *testing.T) {
+	t.Run("Without overflow from int64", func(t *testing.T) {
+		fld := field.Object("num", field.Number(field.NumberFormatInt))
+
+		itemData := map[string]interface{}{"num": 1<<53 - 1}
+
+		b, err := json.Marshal(itemData)
+		require.NoError(t, err)
+
+		itemDataToDecode := make(map[string]interface{})
+		require.NoError(t, json.Unmarshal(b, &itemDataToDecode))
+
+		_, err = field.Decode(context.Background(), fld, itemDataToDecode)
+		require.NoError(t, err)
+	})
+	t.Run("Without overflow from float64", func(t *testing.T) {
+		fld := field.Object("num", field.Number(field.NumberFormatInt))
+
+		itemData := map[string]interface{}{"num": float64(1<<53 - 1)}
+
+		b, err := json.Marshal(itemData)
+		require.NoError(t, err)
+
+		itemDataToDecode := make(map[string]interface{})
+		require.NoError(t, json.Unmarshal(b, &itemDataToDecode))
+
+		_, err = field.Decode(context.Background(), fld, itemDataToDecode)
+		require.NoError(t, err)
+	})
+	t.Run("With overflow from int64", func(t *testing.T) {
+		fld := field.Object("num", field.Number(field.NumberFormatInt))
+
+		itemData := map[string]interface{}{"num": 1 << 53}
+
+		b, err := json.Marshal(itemData)
+		require.NoError(t, err)
+
+		itemDataToDecode := make(map[string]interface{})
+		require.NoError(t, json.Unmarshal(b, &itemDataToDecode))
+
+		_, err = field.Decode(context.Background(), fld, itemDataToDecode)
+		require.Error(t, err, "integer out of range")
+	})
+	t.Run("With overflow from uint64", func(t *testing.T) {
+		fld := field.Object("num", field.Number(field.NumberFormatInt))
+
+		itemData := map[string]interface{}{"num": uint64(1 << 53)}
+
+		b, err := json.Marshal(itemData)
+		require.NoError(t, err)
+
+		itemDataToDecode := make(map[string]interface{})
+		require.NoError(t, json.Unmarshal(b, &itemDataToDecode))
+
+		_, err = field.Decode(context.Background(), fld, itemDataToDecode)
+		require.Error(t, err, "integer out of range")
+	})
+	t.Run("With overflow from float64", func(t *testing.T) {
+		fld := field.Object("num", field.Number(field.NumberFormatInt))
+
+		itemData := map[string]interface{}{"num": float64(1 << 53)}
+
+		b, err := json.Marshal(itemData)
+		require.NoError(t, err)
+
+		itemDataToDecode := make(map[string]interface{})
+		require.NoError(t, json.Unmarshal(b, &itemDataToDecode))
+
+		_, err = field.Decode(context.Background(), fld, itemDataToDecode)
+		require.Error(t, err, "integer out of range")
+	})
+
+}
+
 func TestSchema_JSON(t *testing.T) {
 	enumStr := field.String().AddOptions(
 		validate.Enum(