diff --git a/pkg/extension/action_url.go b/pkg/extension/action_url.go
new file mode 100644
index 0000000000000000000000000000000000000000..72d35371e5d14603cba09b3c8f06629c3e3b1a55
--- /dev/null
+++ b/pkg/extension/action_url.go
@@ -0,0 +1,70 @@
+package extension
+
+import (
+	"net/url"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+// ActionURL структура для хранения данных о переданном действии
+type ActionURL struct {
+	actionURL   string
+	actionID    string
+	extensionID string
+	scheme      string
+	url         *url.URL
+}
+
+// NewActionURL возвращает пустую структуру ActionURL, если передано пустое действие
+// при передаче в функцию действия - заполняет структуру
+func NewActionURL(action string) (*ActionURL, error) {
+	if action != "" {
+		parsedURL, err := parseActionURL(action)
+		if err != nil {
+			return nil, err
+		}
+		return parsedURL, nil
+	}
+	return &ActionURL{}, nil
+}
+
+// ID возвращает сохраненный в ActionURL id действия
+func (p *ActionURL) ID() string {
+	return p.actionID
+}
+
+// ExtensionID возвращает сохраненный в ActionURL id расширения
+func (p *ActionURL) ExtensionID() string {
+	return p.extensionID
+}
+
+// Scheme возвращает сохраненную в ActionURL схему
+func (p *ActionURL) Scheme() string {
+	return p.scheme
+}
+
+// parseActionURL функция для заполнения структуры ActionURL из переданного действия
+func parseActionURL(action string) (*ActionURL, error) {
+	u, err := url.Parse(action)
+	if err != nil {
+		return nil, err
+	}
+	parsed := &ActionURL{}
+	parsed.actionURL = action
+	parsed.url = u
+	parsed.scheme = u.Scheme
+	if parsed.Scheme() == "grpc" {
+		path := u.Path
+		if strings.HasPrefix(u.Path, "/") {
+			path = u.Path[1:]
+		}
+		splitPath := strings.Split(path, "/")
+		if len(splitPath) < 2 {
+			return nil, errors.Errorf("incorrect action URL, no action id: '%s'", action)
+		}
+		parsed.extensionID = splitPath[0]
+		parsed.actionID = splitPath[1]
+	}
+	return parsed, nil
+}
diff --git a/pkg/extension/action_url_test.go b/pkg/extension/action_url_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7c5ef5e43559efb9158e698e9adfccfbf21c5d5a
--- /dev/null
+++ b/pkg/extension/action_url_test.go
@@ -0,0 +1,87 @@
+package extension
+
+import (
+	"fmt"
+	"net/url"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestNewActionURL(t *testing.T) {
+	u1, _ := url.Parse("build-site")
+	u2, _ := url.Parse("grpc:///perxisweb/build-site")
+	u3, _ := url.Parse("ui:///space/env/coll")
+	u4, _ := url.Parse("https://perx.ru")
+	tests := []struct {
+		name    string
+		action  string
+		want    *ActionURL
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			"Without action",
+			"",
+			&ActionURL{},
+			assert.NoError,
+		},
+		{
+			"Without deprecated action call",
+			"build-site",
+			&ActionURL{
+				actionURL: "build-site",
+				url:       u1,
+			},
+			assert.NoError,
+		},
+		{
+			name:   "With grpc action",
+			action: "grpc:///perxisweb/build-site",
+			want: &ActionURL{
+				actionURL:   "grpc:///perxisweb/build-site",
+				actionID:    "build-site",
+				extensionID: "perxisweb",
+				scheme:      "grpc",
+				url:         u2,
+			},
+			wantErr: assert.NoError,
+		},
+		{
+			name:   "With ui action",
+			action: "ui:///space/env/coll",
+			want: &ActionURL{
+				actionURL:   "ui:///space/env/coll",
+				actionID:    "",
+				extensionID: "",
+				scheme:      "ui",
+				url:         u3,
+			},
+			wantErr: assert.NoError,
+		},
+		{
+			name:   "With http action",
+			action: "https://perx.ru",
+			want: &ActionURL{
+				actionURL: "https://perx.ru",
+				scheme:    "https",
+				url:       u4,
+			},
+			wantErr: assert.NoError,
+		},
+		{
+			name:    "With error in parse",
+			action:  "grpc://user:abc{DEf1=ghi@example.com:5432/db?sslmode=require",
+			want:    nil,
+			wantErr: assert.Error,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := NewActionURL(tt.action)
+			if !tt.wantErr(t, err, fmt.Sprintf("NewActionURL(%v)", tt.action)) {
+				return
+			}
+			assert.Equalf(t, tt.want, got, "NewActionURL(%v)", tt.action)
+		})
+	}
+}
diff --git a/pkg/extension/extension.go b/pkg/extension/extension.go
index 174ac51c6d2d8af1bf56d87071e7bbee3dc80f34..fb8044c7a2f4c76b775840c5628319196a0493ae 100644
--- a/pkg/extension/extension.go
+++ b/pkg/extension/extension.go
@@ -3,8 +3,6 @@ package extension
 import (
 	"context"
 	"fmt"
-	"net/url"
-	"strings"
 
 	"git.perx.ru/perxis/perxis-go/pkg/content"
 	"git.perx.ru/perxis/perxis-go/pkg/errors"
@@ -107,48 +105,3 @@ func ExtensionFromError(err error) string {
 	ext, _ := v.(string)
 	return ext
 }
-
-type ParsedActionURL struct {
-	actionID    string
-	extensionID string
-	scheme      string
-}
-
-func (p *ParsedActionURL) New() *ParsedActionURL {
-	return &ParsedActionURL{}
-}
-
-func (p *ParsedActionURL) GetActionID() string {
-	return p.actionID
-}
-
-func (p *ParsedActionURL) GetExtensionID() string {
-	return p.extensionID
-}
-
-func (p *ParsedActionURL) GetScheme() string {
-	return p.scheme
-}
-
-func ParseActionURL(action string) (*ParsedActionURL, error) {
-
-	u, err := url.Parse(action)
-	if err != nil {
-		return nil, err
-	}
-	parsed := &ParsedActionURL{}
-	parsed.scheme = u.Scheme
-	if parsed.GetScheme() == "grpc" {
-		path := u.Path
-		if strings.HasPrefix(u.Path, "/") {
-			path = u.Path[1:]
-		}
-		splitPath := strings.Split(path, "/")
-		if len(splitPath) < 2 {
-			return nil, errors.Errorf("incorrect action URL, no action id: '%s'", action)
-		}
-		parsed.extensionID = splitPath[0]
-		parsed.actionID = splitPath[1]
-	}
-	return parsed, nil
-}
diff --git a/pkg/extension/server.go b/pkg/extension/server.go
index be4b6f0a4a26b30692d1a0c72f212ce0789e570c..bdeac58c14bf37b20104e23ecff96c70cf900930 100644
--- a/pkg/extension/server.go
+++ b/pkg/extension/server.go
@@ -80,13 +80,16 @@ func (srv *Server) Update(ctx context.Context, request *UpdateRequest) (*UpdateR
 }
 
 func (srv *Server) Action(ctx context.Context, in *pb.ActionRequest) (*pb.ActionResponse, error) {
-
-	parsed, err := ParseActionURL(in.Action)
-	if err != nil {
-		return nil, err
+	extensionID := in.Extension
+	if extensionID == "" {
+		actionURL, err := NewActionURL(in.Action)
+		if err != nil {
+			return nil, err
+		}
+		extensionID = actionURL.extensionID
 	}
 
-	svc, ok := srv.services[parsed.GetExtensionID()]
+	svc, ok := srv.services[extensionID]
 	if !ok {
 		return nil, ErrUnknownExtension
 	}
diff --git a/pkg/extension/server_test.go b/pkg/extension/server_test.go
index 5400c8c4786cbe09bf2c56fdfb0611d65048d31d..9e8b8f4c71bbdcfc8fa46bcf5651c9e4e0fe81a5 100644
--- a/pkg/extension/server_test.go
+++ b/pkg/extension/server_test.go
@@ -2,11 +2,13 @@ package extension
 
 import (
 	"context"
+	"fmt"
 	"reflect"
 	"strings"
 	"testing"
 
 	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"github.com/stretchr/testify/assert"
 )
 
 func TestGetResults(t *testing.T) {
@@ -102,3 +104,82 @@ func (t testServerExtension) Uninstall(ctx context.Context, in *UninstallRequest
 func (t testServerExtension) Action(ctx context.Context, in *ActionRequest) (*ActionResponse, error) {
 	return &ActionResponse{}, t.err
 }
+
+func TestServer_Action(t *testing.T) {
+	getDummyExtension := func(name string, wantErr ...bool) Extension {
+		ext := &testServerExtension{name: name}
+
+		if len(wantErr) > 0 {
+			ext.err = errors.WithDetail(errors.New("some err"), "Ошибка")
+		}
+
+		return ext
+	}
+
+	var tests = []struct {
+		name     string
+		services map[string]Extension
+		in       *ActionRequest
+		want     *ActionResponse
+		wantErr  assert.ErrorAssertionFunc
+	}{
+		{
+			name:     "OK (grpc)",
+			services: map[string]Extension{"test-extension": getDummyExtension("test-extension")},
+			in: &ActionRequest{
+				Action:  "grpc:///test-extension/test-action",
+				SpaceId: "sp",
+				EnvId:   "env",
+			},
+			want:    &ActionResponse{State: ResponseDone},
+			wantErr: assert.NoError,
+		},
+		{
+			name:     "OK (deprecated call)",
+			services: map[string]Extension{"test-extension": getDummyExtension("test-extension")},
+			in: &ActionRequest{
+				Action:    "test-action",
+				SpaceId:   "sp",
+				EnvId:     "env",
+				Extension: "test-extension",
+			},
+			want:    &ActionResponse{State: ResponseDone},
+			wantErr: assert.NoError,
+		},
+		{
+			name:     "Error (unknown extension)",
+			services: map[string]Extension{"test-extension": getDummyExtension("test-extension")},
+			in: &ActionRequest{
+				Action:  "grpc:///test-extension-2/test-action",
+				SpaceId: "sp",
+				EnvId:   "env",
+			},
+			want:    nil,
+			wantErr: assert.Error,
+		},
+		{
+			name:     "Error (deprecated call, no extension)",
+			services: map[string]Extension{"test-extension": getDummyExtension("test-extension")},
+			in: &ActionRequest{
+				Action:  "test-action",
+				SpaceId: "sp",
+				EnvId:   "env",
+			},
+			want:    nil,
+			wantErr: assert.Error,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			srv := &Server{
+				services: tt.services,
+			}
+			got, err := srv.Action(context.Background(), tt.in)
+			if !tt.wantErr(t, err, fmt.Sprintf("Action(%v)", tt.in)) {
+				return
+			}
+			assert.Equalf(t, tt.want, got, "Action(%v)", tt.in)
+		})
+	}
+}
diff --git a/pkg/extension/service/extension.go b/pkg/extension/service/extension.go
index e0b4e2abcfc82c8d9b3769fff58d53d49f2d7c40..f57f8253965bf45225532a71a67c95fc38f3a735 100644
--- a/pkg/extension/service/extension.go
+++ b/pkg/extension/service/extension.go
@@ -155,11 +155,15 @@ func (s *Extension) Uninstall(ctx context.Context, in *extension.UninstallReques
 }
 
 func (s *Extension) Action(ctx context.Context, in *extension.ActionRequest) (*extension.ActionResponse, error) {
-	parsed, err := extension.ParseActionURL(in.Action)
-	if err != nil {
-		return nil, err
+	extensionID := in.Extension
+	if extensionID == "" {
+		actionURL, err := extension.NewActionURL(in.Action)
+		if err != nil {
+			return nil, err
+		}
+		extensionID = actionURL.ExtensionID()
 	}
-	ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, parsed.GetExtensionID())
+	ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, extensionID)
 	if err != nil {
 		return nil, errors.Wrap(err, "check extension installed")
 	}