diff --git a/pkg/collections/collection.go b/pkg/collections/collection.go
index ed9f91afe038dc1ebcb4f42a91f51a7828b47cf9..3e574d4071327460ea5b49c5d5b66641655ddc6a 100644
--- a/pkg/collections/collection.go
+++ b/pkg/collections/collection.go
@@ -101,16 +101,27 @@ type Collection struct {
 }
 
 // GetID возвращает идентификатор коллекции
-func (c Collection) GetID() string {
+func (c *Collection) GetID() string {
 	return c.ID
 }
 
-func (c Collection) GetSpaceID() string {
+func (c *Collection) GetSpaceID() string {
 	return c.SpaceID
 }
 
 // Equal сравнивает две коллекции, за исключением Schema, Access, StateInfo и Config
-func (c Collection) Equal(other *Collection) bool {
+// Deprecated: используйте метод IsEqual
+func (c *Collection) Equal(other *Collection) bool {
+	return c.IsEqual(other)
+}
+
+func (c *Collection) IsEqual(other *Collection) bool {
+	if c == other {
+		return true
+	}
+	if c == nil || other == nil {
+		return false
+	}
 	if c.ID != other.ID ||
 		c.SpaceID != other.SpaceID ||
 		c.EnvID != other.EnvID ||
@@ -132,6 +143,10 @@ func (c Collection) Equal(other *Collection) bool {
 	return true
 }
 
+func IsEqual(c1, c2 *Collection) bool {
+	return c1.IsEqual(c2)
+}
+
 type View struct {
 	SpaceID      string `json:"space_id" bson:"space_id"`             // SpaceID оригинальной коллекции
 	EnvID        string `json:"environment_id" bson:"environment_id"` // EnvID оригинальной коллекции
@@ -185,7 +200,7 @@ const (
 	StateChanged
 )
 
-func (c Collection) Clone() *Collection {
+func (c *Collection) Clone() *Collection {
 	clone := &Collection{
 		ID:           c.ID,
 		SpaceID:      c.SpaceID,
@@ -294,3 +309,116 @@ func RuleFromAccess(access *Access) *permission.Rule {
 		Hidden:          access.Hidden,
 	}
 }
+
+// Merge объединяет две коллекции, возвращая новую коллекцию
+// с объединенными данными. Если данные второй коллекции не пустые,
+// то данные из второй коллекции перезаписывают данные из первой.
+func (c *Collection) Merge(other *Collection) *Collection {
+	if c == nil {
+		if other == nil {
+			return nil
+		}
+		return other.Clone()
+	}
+
+	if other == nil {
+		return c.Clone()
+	}
+
+	merged := c.Clone()
+	merged.mergeScalarFields(other)
+	merged.mergePointerFields(other)
+	merged.mergeComplexFields(other)
+
+	return merged
+}
+
+// mergeScalarFields копирует скалярные поля из other коллекции в текущую коллекцию.
+func (c *Collection) mergeScalarFields(other *Collection) {
+	if other.ID != "" {
+		c.ID = other.ID
+	}
+	if other.Name != "" {
+		c.Name = other.Name
+	}
+	if other.SpaceID != "" {
+		c.SpaceID = other.SpaceID
+	}
+	if other.EnvID != "" {
+		c.EnvID = other.EnvID
+	}
+	if other.Hidden {
+		c.Hidden = other.Hidden
+	}
+	if other.NoPublish {
+		c.NoPublish = other.NoPublish
+	}
+	if other.NoArchive {
+		c.NoArchive = other.NoArchive
+	}
+	if other.NoRevisions {
+		c.NoRevisions = other.NoRevisions
+	}
+	if other.MaxRevisions > 0 {
+		c.MaxRevisions = other.MaxRevisions
+	}
+	if other.RevisionTTL > 0 {
+		c.RevisionTTL = other.RevisionTTL
+	}
+}
+
+// mergePointerFields копирует указатели из other коллекции в текущую коллекцию.
+func (c *Collection) mergePointerFields(other *Collection) {
+	if other.NoData != nil {
+		c.NoData = other.NoData
+	}
+	if other.Single != nil {
+		single := *other.Single
+		c.Single = &single
+	}
+	if other.System != nil {
+		system := *other.System
+		c.System = &system
+	}
+	if other.StateInfo != nil {
+		info := *other.StateInfo
+		c.StateInfo = &info
+	}
+	if other.View != nil {
+		view := *other.View
+		c.View = &view
+	}
+	if other.Config != nil {
+		cfg := *other.Config
+		c.Config = &cfg
+	}
+}
+
+// mergeComplexFields копирует сложные поля из other коллекции в текущую коллекцию.
+func (c *Collection) mergeComplexFields(other *Collection) {
+	if other.Schema != nil {
+		c.Schema = other.Schema.Clone(false)
+	}
+	if other.Access != nil {
+		c.Access = other.Access.Clone()
+	}
+	if other.Tags != nil {
+		tags := make([]string, len(other.Tags))
+		copy(tags, other.Tags)
+		c.Tags = tags
+	}
+	if len(other.Translations) > 0 {
+		translations := make(map[string]map[string]string, len(other.Translations))
+		for k, v := range other.Translations {
+			translations[k] = maps.Clone(v)
+		}
+		c.Translations = translations
+	}
+}
+
+// Merge объединяет две коллекции, возвращая новую коллекцию
+// с объединенными данными. Если данные второй коллекции не пустые,
+// то данные из второй коллекции перезаписывают данные из первой.
+func Merge(c1, c2 *Collection) *Collection {
+	return c1.Merge(c2)
+}
diff --git a/pkg/collections/collection_test.go b/pkg/collections/collection_test.go
index 79e16a8e6a397418ef2418979bf0e38947b730c8..84e0d5e509a256e6464e8e20ad9023109e97beb8 100644
--- a/pkg/collections/collection_test.go
+++ b/pkg/collections/collection_test.go
@@ -2,7 +2,9 @@ package collections
 
 import (
 	"testing"
+	"time"
 
+	"git.perx.ru/perxis/perxis-go/pkg/schema"
 	"github.com/stretchr/testify/require"
 )
 
@@ -100,3 +102,225 @@ func TestView_Equal(t *testing.T) {
 		})
 	}
 }
+
+func Test_Merge(t *testing.T) {
+	boolTrue := true
+	boolFalse := false
+
+	// Create schema objects with Title field
+	schema1 := schema.New()
+	schema1.Title = "schema1"
+
+	schema2 := schema.New()
+	schema2.Title = "schema2"
+
+	testCases := []struct {
+		name string
+		c1   *Collection
+		c2   *Collection
+		want *Collection
+	}{
+		{
+			name: "Both collections are nil",
+			c1:   nil,
+			c2:   nil,
+			want: nil,
+		},
+		{
+			name: "First collection is nil",
+			c1:   nil,
+			c2: &Collection{
+				ID:   "id2",
+				Name: "name2",
+			},
+			want: &Collection{
+				ID:   "id2",
+				Name: "name2",
+			},
+		},
+		{
+			name: "Second collection is nil",
+			c1: &Collection{
+				ID:   "id1",
+				Name: "name1",
+			},
+			c2: nil,
+			want: &Collection{
+				ID:   "id1",
+				Name: "name1",
+			},
+		},
+		{
+			name: "Merge primitive fields",
+			c1: &Collection{
+				ID:           "id1",
+				SpaceID:      "space1",
+				EnvID:        "env1",
+				Name:         "name1",
+				Hidden:       false,
+				NoPublish:    false,
+				NoArchive:    false,
+				NoRevisions:  false,
+				MaxRevisions: 5,
+				RevisionTTL:  time.Hour,
+			},
+			c2: &Collection{
+				ID:           "id2",
+				SpaceID:      "space2",
+				EnvID:        "env2",
+				Name:         "name2",
+				Hidden:       true,
+				NoPublish:    true,
+				NoArchive:    true,
+				NoRevisions:  true,
+				MaxRevisions: 10,
+				RevisionTTL:  time.Hour * 2,
+			},
+			want: &Collection{
+				ID:           "id2",
+				SpaceID:      "space2",
+				EnvID:        "env2",
+				Name:         "name2",
+				Hidden:       true,
+				NoPublish:    true,
+				NoArchive:    true,
+				NoRevisions:  true,
+				MaxRevisions: 10,
+				RevisionTTL:  time.Hour * 2,
+			},
+		},
+		{
+			name: "Merge pointer fields",
+			c1: &Collection{
+				NoData: &boolFalse,
+				Single: &boolFalse,
+				System: &boolFalse,
+			},
+			c2: &Collection{
+				NoData: &boolTrue,
+				Single: &boolTrue,
+				System: &boolTrue,
+			},
+			want: &Collection{
+				NoData: &boolTrue,
+				Single: &boolTrue,
+				System: &boolTrue,
+			},
+		},
+		{
+			name: "Merge complex fields",
+			c1: &Collection{
+				Schema: schema1,
+				View: &View{
+					SpaceID:      "space1",
+					EnvID:        "env1",
+					CollectionID: "collection1",
+					Filter:       "filter1",
+				},
+				Tags: []string{"tag1", "tag2"},
+				Translations: map[string]map[string]string{
+					"en": {
+						"key1": "value1",
+					},
+				},
+			},
+			c2: &Collection{
+				Schema: schema2,
+				View: &View{
+					SpaceID:      "space2",
+					EnvID:        "env2",
+					CollectionID: "collection2",
+					Filter:       "filter2",
+				},
+				Tags: []string{"tag3", "tag4"},
+				Translations: map[string]map[string]string{
+					"fr": {
+						"key2": "value2",
+					},
+				},
+			},
+			want: &Collection{
+				Schema: schema2,
+				View: &View{
+					SpaceID:      "space2",
+					EnvID:        "env2",
+					CollectionID: "collection2",
+					Filter:       "filter2",
+				},
+				Tags: []string{"tag3", "tag4"},
+				Translations: map[string]map[string]string{
+					"fr": {
+						"key2": "value2",
+					},
+				},
+			},
+		},
+		{
+			name: "Merge with empty second collection",
+			c1: &Collection{
+				ID:      "id1",
+				SpaceID: "space1",
+				EnvID:   "env1",
+				Name:    "name1",
+				NoData:  &boolTrue,
+				Single:  &boolTrue,
+				System:  &boolTrue,
+				Schema:  schema1,
+				Tags:    []string{"tag1", "tag2"},
+			},
+			c2: &Collection{},
+			want: &Collection{
+				ID:      "id1",
+				SpaceID: "space1",
+				EnvID:   "env1",
+				Name:    "name1",
+				NoData:  &boolTrue,
+				Single:  &boolTrue,
+				System:  &boolTrue,
+				Schema:  schema1,
+				Tags:    []string{"tag1", "tag2"},
+			},
+		},
+		{
+			name: "Merge with partial second collection",
+			c1: &Collection{
+				ID:      "id1",
+				SpaceID: "space1",
+				EnvID:   "env1",
+				Name:    "name1",
+				NoData:  &boolFalse,
+				Single:  &boolFalse,
+				System:  &boolFalse,
+				Schema:  schema1,
+				Tags:    []string{"tag1", "tag2"},
+			},
+			c2: &Collection{
+				Name:   "name2",
+				NoData: &boolTrue,
+				Schema: schema2,
+			},
+			want: &Collection{
+				ID:      "id1",
+				SpaceID: "space1",
+				EnvID:   "env1",
+				Name:    "name2",
+				NoData:  &boolTrue,
+				Single:  &boolFalse,
+				System:  &boolFalse,
+				Schema:  schema2,
+				Tags:    []string{"tag1", "tag2"},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			result := Merge(tc.c1, tc.c2)
+
+			require.True(t, IsEqual(result, tc.want))
+			if result != nil && tc.want != nil {
+				require.True(t, schema.IsEqual(result.Schema, tc.want.Schema))
+			}
+		})
+	}
+}
diff --git a/pkg/items/item.go b/pkg/items/item.go
index dc56fe63284be6885cf4560578df0bf77729e359..92ef2494981c12e7e28e05c474ceb0db09e3c8be 100644
--- a/pkg/items/item.go
+++ b/pkg/items/item.go
@@ -11,6 +11,7 @@ import (
 	"git.perx.ru/perxis/perxis-go/pkg/schema"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
 	"git.perx.ru/perxis/perxis-go/pkg/schema/localizer"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/walk"
 	pb "git.perx.ru/perxis/perxis-go/proto/items"
 	"google.golang.org/protobuf/types/known/structpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
@@ -662,3 +663,31 @@ func GetItemIDs(arr []*Item) []string {
 	}
 	return res
 }
+
+func MergeItemData(ctx context.Context, sch *schema.Schema, origData, updData map[string]any) (map[string]any, error) {
+	if origData == nil {
+		return updData, nil
+	}
+
+	w := walk.NewWalker(sch, &walk.WalkConfig{})
+	w.DefaultFn = func(c *walk.WalkContext) error {
+		if c.Src == nil || c.Dst != nil {
+			return nil
+		}
+		c.Dst = c.Src
+		c.Changed = true
+		return nil
+	}
+
+	res, _, err := w.DataWalk(ctx, updData, origData)
+	if err != nil {
+		return nil, err
+	}
+
+	v, ok := res.(map[string]any)
+	if !ok {
+		return nil, fmt.Errorf("expected map[string]interface{}, got %[1]T, %[1]v", res)
+	}
+
+	return v, nil
+}
diff --git a/pkg/items/item_test.go b/pkg/items/item_test.go
index 05baa60a3c1e8eaae9534fe9026d7456d78ab36c..1344444e685ee025e9b7e13a4bc2d781eb36a9de 100644
--- a/pkg/items/item_test.go
+++ b/pkg/items/item_test.go
@@ -264,3 +264,245 @@ func TestItem_Encode_Decode(t *testing.T) {
 		})
 	}
 }
+
+func Test_mergeItemData(t *testing.T) {
+	tests := []struct {
+		name     string
+		schema   *schema.Schema
+		origData map[string]any
+		updData  map[string]any
+		want     map[string]interface{}
+		wantErr  bool
+	}{
+		{
+			name: "merge with non-nil original data",
+			schema: schema.New(
+				"field1", field.String(),
+				"field2", field.String(),
+				"field3", field.String(),
+			),
+			origData: map[string]interface{}{
+				"field1": "value1",
+				"field2": "value2",
+			},
+			updData: map[string]interface{}{
+				"field2": "new_value2",
+				"field3": "value3",
+			},
+			want: map[string]interface{}{
+				"field1": "value1",
+				"field2": "new_value2",
+				"field3": "value3",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with nil original data",
+			schema: schema.New(
+				"field1", field.String(),
+			),
+			origData: nil,
+			updData: map[string]interface{}{
+				"field1": "value1",
+			},
+			want: map[string]interface{}{
+				"field1": "value1",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with empty original data",
+			schema: schema.New(
+				"field1", field.String(),
+			),
+			origData: map[string]interface{}{},
+			updData: map[string]interface{}{
+				"field1": "value1",
+			},
+			want: map[string]interface{}{
+				"field1": "value1",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with schema fields",
+			schema: schema.New(
+				"field1", field.String(),
+				"field2", field.String(),
+				"field3", field.String(),
+			),
+			origData: map[string]interface{}{
+				"field1": "value1",
+				"field2": "value2",
+			},
+			updData: map[string]interface{}{
+				"field2": "new_value2",
+				"field3": "value3",
+			},
+			want: map[string]interface{}{
+				"field1": "value1",
+				"field2": "new_value2",
+				"field3": "value3",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with extra fields not in schema",
+			schema: schema.New(
+				"field1", field.String(),
+				"field2", field.String(),
+			),
+			origData: map[string]interface{}{
+				"field1":      "value1",
+				"extra_field": "extra_value",
+			},
+			updData: map[string]interface{}{
+				"field2":        "value2",
+				"another_extra": "another_value",
+			},
+			want: map[string]interface{}{
+				"field1": "value1",
+				"field2": "value2",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with different field types",
+			schema: schema.New(
+				"string_field", field.String(),
+				"number_field", field.Number(field.NumberFormatInt),
+				"bool_field", field.String(),
+			),
+			origData: map[string]interface{}{
+				"string_field": "old_value",
+				"number_field": 42,
+			},
+			updData: map[string]interface{}{
+				"string_field": "new_value",
+				"bool_field":   "true",
+			},
+			want: map[string]interface{}{
+				"string_field": "new_value",
+				"number_field": 42,
+				"bool_field":   "true",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with nested schema",
+			schema: schema.New(
+				"user", field.Object(
+					"name", field.String(),
+					"age", field.Number(field.NumberFormatInt),
+					"active", field.Bool(),
+				),
+				"metadata", field.Object(
+					"created_at", field.String(),
+					"updated_at", field.String(),
+				),
+			),
+			origData: map[string]interface{}{
+				"user": map[string]interface{}{
+					"name":   "John",
+					"age":    30,
+					"active": true,
+				},
+				"metadata": map[string]interface{}{
+					"created_at": "2024-01-01",
+				},
+			},
+			updData: map[string]interface{}{
+				"user": map[string]interface{}{
+					"name": "John Doe",
+				},
+				"metadata": map[string]interface{}{
+					"updated_at": "2024-03-20",
+				},
+			},
+			want: map[string]interface{}{
+				"user": map[string]interface{}{
+					"name":   "John Doe",
+					"age":    30,
+					"active": true,
+				},
+				"metadata": map[string]interface{}{
+					"created_at": "2024-01-01",
+					"updated_at": "2024-03-20",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with array fields",
+			schema: schema.New(
+				"tags", field.Array(field.String()),
+				"numbers", field.Array(field.Number(field.NumberFormatInt)),
+				"mixed", field.Array(field.String()),
+			),
+			origData: map[string]interface{}{
+				"tags":    []interface{}{"tag1", "tag2"},
+				"numbers": []interface{}{1, 2, 3},
+			},
+			updData: map[string]interface{}{
+				"tags":  []interface{}{"tag3", "tag4"},
+				"mixed": []interface{}{"value1", "value2"},
+			},
+			want: map[string]interface{}{
+				"tags":    []interface{}{"tag3", "tag4"},
+				"numbers": []interface{}{1, 2, 3},
+				"mixed":   []interface{}{"value1", "value2"},
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with required fields",
+			schema: schema.New(
+				"required_field", field.String(),
+				"optional_field", field.String(),
+			),
+			origData: map[string]interface{}{
+				"required_field": "original",
+				"optional_field": "optional",
+			},
+			updData: map[string]interface{}{
+				"required_field": "updated",
+			},
+			want: map[string]interface{}{
+				"required_field": "updated",
+				"optional_field": "optional",
+			},
+			wantErr: false,
+		},
+		{
+			name: "merge with validation rules",
+			schema: schema.New(
+				"email", field.String(),
+				"age", field.Number(field.NumberFormatInt),
+			),
+			origData: map[string]interface{}{
+				"email": "test@example.com",
+				"age":   25,
+			},
+			updData: map[string]interface{}{
+				"email": "new@example.com",
+				"age":   30,
+			},
+			want: map[string]interface{}{
+				"email": "new@example.com",
+				"age":   30,
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := MergeItemData(context.Background(), tt.schema, tt.origData, tt.updData)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("mergeItemData() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go
index 7b337f8ab68f732fb3ab2ac804fe89e082c58c6a..bdebe4f04025278d00ec41b20bba2b596ac5de8a 100644
--- a/pkg/schema/schema.go
+++ b/pkg/schema/schema.go
@@ -48,14 +48,24 @@ func (s *Schema) ClearState() *Schema {
 	return s
 }
 
-func (s *Schema) Equal(sch *Schema) bool {
-	if s == sch {
+// Equal compares two schemas for equality.
+// Deprecated: use `IsEqual` method instead.
+func (s *Schema) Equal(other *Schema) bool {
+	return s.IsEqual(other)
+}
+
+func (s *Schema) IsEqual(other *Schema) bool {
+	if s == other {
 		return true
 	}
-	if s == nil || sch == nil {
+	if s == nil || other == nil {
 		return false
 	}
-	return reflect.DeepEqual(s.Field, sch.Field)
+	return reflect.DeepEqual(s.Field, other.Field)
+}
+
+func IsEqual(s1, s2 *Schema) bool {
+	return s1.IsEqual(s2)
 }
 
 func (s Schema) WithIncludes(includes ...interface{}) *Schema {
@@ -78,9 +88,9 @@ func (s Schema) SetMetadata(md map[string]string) *Schema {
 	return &s
 }
 
-func (f Schema) SetSingleLocale(r bool) *Schema {
-	f.SingleLocale = r
-	return &f
+func (s Schema) SetSingleLocale(r bool) *Schema {
+	s.SingleLocale = r
+	return &s
 }
 
 func (s *Schema) ConvertTypes() error {