diff --git a/state/mapping/mapping_test.go b/state/mapping/mapping_test.go
index 2a6eaaf8..f3419f7e 100644
--- a/state/mapping/mapping_test.go
+++ b/state/mapping/mapping_test.go
@@ -3,6 +3,7 @@ package mapping_test
 import (
 	"strings"
 	"testing"
+	"time"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -10,6 +11,7 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/ptypes"
 	"github.com/hyperledger/fabric-protos-go/peer"
+
 	identitytestdata "github.com/s7techlab/cckit/identity/testdata"
 	"github.com/s7techlab/cckit/state"
 	"github.com/s7techlab/cckit/state/mapping"
@@ -168,7 +170,11 @@ var _ = Describe(`Mapping`, func() {
 
 	Describe(`Entity with complex id`, func() {
 
-		ent1 := &schema.EntityWithComplexId{Id: &schema.EntityComplexId{IdPart1: `aaa`, IdPart2: `bbb`}}
+		ent1 := &schema.EntityWithComplexId{Id: &schema.EntityComplexId{
+			IdPart1: []string{`aaa`, `bb`},
+			IdPart2: `ccc`,
+			IdPart3: testcc.MustTime(`2020-01-28T17:00:00Z`),
+		}}
 
 		It("Allow to add data to chaincode state", func() {
 			expectcc.ResponseOk(complexIDCC.Invoke(`entityInsert`, ent1))
@@ -176,9 +182,15 @@ var _ = Describe(`Mapping`, func() {
 				`debugStateKeys`, `EntityWithComplexId`), &[]string{}).([]string)
 			Expect(len(keys)).To(Equal(1))
 
+			timeStr := time.Unix(ent1.Id.IdPart3.GetSeconds(), int64(ent1.Id.IdPart3.GetNanos())).Format(`2006-01-02`)
 			// from hyperledger/fabric/core/chaincode/shim/chaincode.go
 			Expect(keys[0]).To(Equal(
-				"\x00" + `EntityWithComplexId` + string(rune(0)) + ent1.Id.IdPart1 + string(rune(0)) + ent1.Id.IdPart2 + string(rune(0))))
+				string(rune(0)) +
+					`EntityWithComplexId` + string(rune(0)) +
+					ent1.Id.IdPart1[0] + string(rune(0)) +
+					ent1.Id.IdPart1[1] + string(rune(0)) +
+					ent1.Id.IdPart2 + string(rune(0)) +
+					timeStr + string(rune(0))))
 		})
 
 		It("Allow to get entity", func() {
diff --git a/state/mapping/state_mapping.go b/state/mapping/state_mapping.go
index aad00946..56942068 100644
--- a/state/mapping/state_mapping.go
+++ b/state/mapping/state_mapping.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
+
 	"github.com/s7techlab/cckit/state"
 )
 
@@ -60,6 +61,7 @@ type (
 		Keyer    InstanceMultiKeyer // index can have multiple keys
 	}
 
+	// StateIndexDef additional index definition
 	StateIndexDef struct {
 		Name     string
 		Fields   []string
diff --git a/state/mapping/state_mapping_opt.go b/state/mapping/state_mapping_opt.go
index f0b98c24..c2b3bccd 100644
--- a/state/mapping/state_mapping_opt.go
+++ b/state/mapping/state_mapping_opt.go
@@ -65,6 +65,7 @@ func WithIndex(idx *StateIndexDef) StateMappingOpt {
 				aa = idx.Fields
 			}
 
+			// multiple external ids refers to one entry
 			if idx.Multi {
 				keyer = attrMultiKeyer(aa[0])
 			} else {
@@ -222,8 +223,6 @@ func keysFromValue(v reflect.Value) ([]state.Key, error) {
 
 // keyFromValue creates string representation of value for state key
 func keyFromValue(v reflect.Value) (state.Key, error) {
-	var key state.Key
-
 	switch v.Kind() {
 
 	// enum in protobuf
@@ -244,7 +243,7 @@ func keyFromValue(v reflect.Value) (state.Key, error) {
 			return state.Key{t.Format(TimestampKeyLayout)}, nil
 
 		default:
-
+			key := state.Key{}
 			s := reflect.ValueOf(v.Interface()).Elem()
 			fs := s.Type()
 			// get all field values from struct
@@ -253,7 +252,11 @@ func keyFromValue(v reflect.Value) (state.Key, error) {
 				if skipField(fs.Field(i).Name, field) {
 					continue
 				} else {
-					key = append(key, reflect.Indirect(v).Field(i).String())
+					subKey, err := keyFromValue(reflect.Indirect(v).Field(i))
+					if err != nil {
+						return nil, fmt.Errorf(`sub key=%s: %w`, fs.Field(i).Name, err)
+					}
+					key = key.Append(subKey)
 				}
 			}
 
@@ -265,17 +268,17 @@ func keyFromValue(v reflect.Value) (state.Key, error) {
 
 	case `string`, `int32`, `bool`:
 		// multi key possible
-		key = state.Key{v.String()}
+		return state.Key{v.String()}, nil
 
 	case `[]string`:
+		key := state.Key{}
 		// every slice element is a part of one key
 		for i := 0; i < v.Len(); i++ {
 			key = append(key, v.Index(i).String())
 		}
+		return key, nil
 
 	default:
 		return nil, ErrFieldTypeNotSupportedForKeyExtraction
 	}
-
-	return key, nil
 }
diff --git a/state/mapping/testdata/schema/with_complex_id.pb.go b/state/mapping/testdata/schema/with_complex_id.pb.go
index 8a9471cd..3820e848 100644
--- a/state/mapping/testdata/schema/with_complex_id.pb.go
+++ b/state/mapping/testdata/schema/with_complex_id.pb.go
@@ -87,8 +87,9 @@ type EntityComplexId struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	IdPart1 string `protobuf:"bytes,1,opt,name=idPart1,proto3" json:"idPart1,omitempty"`
-	IdPart2 string `protobuf:"bytes,2,opt,name=idPart2,proto3" json:"idPart2,omitempty"`
+	IdPart1 []string             `protobuf:"bytes,1,rep,name=idPart1,proto3" json:"idPart1,omitempty"`
+	IdPart2 string               `protobuf:"bytes,2,opt,name=idPart2,proto3" json:"idPart2,omitempty"`
+	IdPart3 *timestamp.Timestamp `protobuf:"bytes,3,opt,name=idPart3,proto3" json:"idPart3,omitempty"`
 }
 
 func (x *EntityComplexId) Reset() {
@@ -123,11 +124,11 @@ func (*EntityComplexId) Descriptor() ([]byte, []int) {
 	return file_with_complex_id_proto_rawDescGZIP(), []int{1}
 }
 
-func (x *EntityComplexId) GetIdPart1() string {
+func (x *EntityComplexId) GetIdPart1() []string {
 	if x != nil {
 		return x.IdPart1
 	}
-	return ""
+	return nil
 }
 
 func (x *EntityComplexId) GetIdPart2() string {
@@ -137,6 +138,13 @@ func (x *EntityComplexId) GetIdPart2() string {
 	return ""
 }
 
+func (x *EntityComplexId) GetIdPart3() *timestamp.Timestamp {
+	if x != nil {
+		return x.IdPart3
+	}
+	return nil
+}
+
 var File_with_complex_id_proto protoreflect.FileDescriptor
 
 var file_with_complex_id_proto_rawDesc = []byte{
@@ -151,12 +159,15 @@ var file_with_complex_id_proto_rawDesc = []byte{
 	0x12, 0x37, 0x0a, 0x09, 0x73, 0x6f, 0x6d, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
 	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
 	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
-	0x08, 0x73, 0x6f, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x65, 0x22, 0x45, 0x0a, 0x0f, 0x45, 0x6e, 0x74,
+	0x08, 0x73, 0x6f, 0x6d, 0x65, 0x44, 0x61, 0x74, 0x65, 0x22, 0x7b, 0x0a, 0x0f, 0x45, 0x6e, 0x74,
 	0x69, 0x74, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07,
-	0x69, 0x64, 0x50, 0x61, 0x72, 0x74, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69,
+	0x69, 0x64, 0x50, 0x61, 0x72, 0x74, 0x31, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x69,
 	0x64, 0x50, 0x61, 0x72, 0x74, 0x31, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x64, 0x50, 0x61, 0x72, 0x74,
 	0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x64, 0x50, 0x61, 0x72, 0x74, 0x32,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x12, 0x34, 0x0a, 0x07, 0x69, 0x64, 0x50, 0x61, 0x72, 0x74, 0x33, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x69,
+	0x64, 0x50, 0x61, 0x72, 0x74, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -180,11 +191,12 @@ var file_with_complex_id_proto_goTypes = []interface{}{
 var file_with_complex_id_proto_depIdxs = []int32{
 	1, // 0: schema.EntityWithComplexId.Id:type_name -> schema.EntityComplexId
 	2, // 1: schema.EntityWithComplexId.some_date:type_name -> google.protobuf.Timestamp
-	2, // [2:2] is the sub-list for method output_type
-	2, // [2:2] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	2, // 2: schema.EntityComplexId.idPart3:type_name -> google.protobuf.Timestamp
+	3, // [3:3] is the sub-list for method output_type
+	3, // [3:3] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
 }
 
 func init() { file_with_complex_id_proto_init() }
diff --git a/state/mapping/testdata/schema/with_complex_id.proto b/state/mapping/testdata/schema/with_complex_id.proto
index a7a6f53d..d7241d76 100644
--- a/state/mapping/testdata/schema/with_complex_id.proto
+++ b/state/mapping/testdata/schema/with_complex_id.proto
@@ -10,6 +10,7 @@ message EntityWithComplexId {
 
 // EntityComplexId
 message EntityComplexId {
-    string idPart1 = 1;
+    repeated string idPart1 = 1;
     string idPart2 = 2;
+    google.protobuf.Timestamp idPart3 = 3;
 }