From 8b7eee46bb32dca090548f5f36baa7d4db0bcf4c Mon Sep 17 00:00:00 2001
From: appflowy <annie@appflowy.io>
Date: Sun, 27 Mar 2022 11:14:21 +0800
Subject: [PATCH] chore: duplicate and hide field

---
 .../grid/field/create_field_bloc.dart         |   1 +
 .../grid/field/edit_field_bloc.dart           |  31 +-
 .../application/grid/field/field_service.dart |  26 +-
 .../plugins/grid/src/grid_page.dart           |   2 +-
 .../src/widgets/header/edit_field_pannel.dart |  26 +-
 .../widgets/header/field_operation_list.dart  |  31 +-
 .../grid/src/widgets/header/header.dart       | 123 -----
 .../grid/src/widgets/header/header_cell.dart  |  36 --
 .../lib/style_widget/button.dart              |   3 +-
 .../dart_event/flowy-grid/dart_event.dart     |  21 +-
 .../flowy-grid-data-model/grid.pb.dart        | 122 +++++
 .../flowy-grid-data-model/grid.pbjson.dart    |  22 +
 .../protobuf/flowy-grid/event_map.pbenum.dart |   4 +-
 .../protobuf/flowy-grid/event_map.pbjson.dart |   5 +-
 .../rust-lib/flowy-grid/src/event_handler.rs  |  17 +-
 frontend/rust-lib/flowy-grid/src/event_map.rs |  12 +-
 .../src/protobuf/model/event_map.rs           |  15 +-
 .../src/protobuf/proto/event_map.proto        |   3 +-
 .../flowy-grid/src/services/grid_editor.rs    |  11 +-
 .../src/entities/grid.rs                      |  31 ++
 .../src/protobuf/model/grid.rs                | 489 ++++++++++++++++--
 .../src/protobuf/proto/grid.proto             |   8 +
 .../src/client_grid/grid_meta_pad.rs          |  83 +--
 23 files changed, 835 insertions(+), 287 deletions(-)
 delete mode 100644 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
 delete mode 100755 frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart

diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart
index 29f7187c2f..6a5a276214 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart
@@ -62,6 +62,7 @@ class CreateFieldBloc extends Bloc<CreateFieldEvent, CreateFieldState> {
         emit(state.copyWith(
           field: Some(editContext.gridField),
           typeOptionData: editContext.typeOptionData,
+          fieldName: editContext.gridField.name,
         ));
       },
       (err) => Log.error(err),
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart
index 11dbe765fc..6b92b0d0d5 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/edit_field_bloc.dart
@@ -1,3 +1,4 @@
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -18,9 +19,27 @@ class EditFieldBloc extends Bloc<EditFieldEvent, EditFieldState> {
           updateFieldName: (_UpdateFieldName value) {
             //
           },
-          hideField: (_HideField value) {},
-          deleteField: (_DeleteField value) {},
-          duplicateField: (_DuplicateField value) {},
+          hideField: (_HideField value) async {
+            final result = await service.updateField(fieldId: value.fieldId, visibility: false);
+            result.fold(
+              (l) => null,
+              (err) => Log.error(err),
+            );
+          },
+          deleteField: (_DeleteField value) async {
+            final result = await service.deleteField(fieldId: value.fieldId);
+            result.fold(
+              (l) => null,
+              (err) => Log.error(err),
+            );
+          },
+          duplicateField: (_DuplicateField value) async {
+            final result = await service.duplicateField(fieldId: value.fieldId);
+            result.fold(
+              (l) => null,
+              (err) => Log.error(err),
+            );
+          },
           saveField: (_SaveField value) {},
         );
       },
@@ -37,9 +56,9 @@ class EditFieldBloc extends Bloc<EditFieldEvent, EditFieldState> {
 class EditFieldEvent with _$EditFieldEvent {
   const factory EditFieldEvent.initial() = _InitialField;
   const factory EditFieldEvent.updateFieldName(String name) = _UpdateFieldName;
-  const factory EditFieldEvent.hideField() = _HideField;
-  const factory EditFieldEvent.duplicateField() = _DuplicateField;
-  const factory EditFieldEvent.deleteField() = _DeleteField;
+  const factory EditFieldEvent.hideField(String fieldId) = _HideField;
+  const factory EditFieldEvent.duplicateField(String fieldId) = _DuplicateField;
+  const factory EditFieldEvent.deleteField(String fieldId) = _DeleteField;
   const factory EditFieldEvent.saveField() = _SaveField;
 }
 
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
index 84566da604..6824ce0b1b 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
@@ -21,7 +21,7 @@ class FieldService {
   }
 
   Future<Either<Unit, FlowyError>> updateField({
-    required Field field,
+    required String fieldId,
     String? name,
     FieldType? fieldType,
     bool? frozen,
@@ -29,7 +29,9 @@ class FieldService {
     double? width,
     List<int>? typeOptionData,
   }) {
-    var payload = FieldChangesetPayload.create()..gridId = gridId;
+    var payload = FieldChangesetPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId;
 
     if (name != null) {
       payload.name = name;
@@ -74,6 +76,26 @@ class FieldService {
 
     return GridEventCreateField(payload).send();
   }
+
+  Future<Either<Unit, FlowyError>> deleteField({
+    required String fieldId,
+  }) {
+    final payload = FieldIdentifierPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId;
+
+    return GridEventDeleteField(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> duplicateField({
+    required String fieldId,
+  }) {
+    final payload = FieldIdentifierPayload.create()
+      ..gridId = gridId
+      ..fieldId = fieldId;
+
+    return GridEventDuplicateField(payload).send();
+  }
 }
 
 class GridFieldData extends Equatable {
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
index 5cd23accc6..e1c44c253b 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
@@ -13,7 +13,7 @@ import 'layout/layout.dart';
 import 'layout/sizes.dart';
 import 'widgets/content/grid_row.dart';
 import 'widgets/footer/grid_footer.dart';
-import 'widgets/header/header.dart';
+import 'widgets/header/grid_header.dart';
 
 class GridPage extends StatefulWidget {
   final View view;
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart
index 47affb67e8..ce542b6760 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/edit_field_pannel.dart
@@ -2,7 +2,6 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -38,9 +37,7 @@ class EditFieldPannel extends StatelessWidget {
             const VSpace(6),
             const _FieldTypeSwitcher(),
             const VSpace(6),
-            FieldOperationList(
-              onDismiss: () => FlowyOverlay.of(context).remove(identifier()),
-            ),
+            _FieldOperationList(fieldData, () => FlowyOverlay.of(context).remove(identifier())),
           ],
         ),
       ),
@@ -52,6 +49,27 @@ class EditFieldPannel extends StatelessWidget {
   }
 }
 
+class _FieldOperationList extends StatelessWidget {
+  final GridFieldData fieldData;
+  final VoidCallback onDismissed;
+  const _FieldOperationList(this.fieldData, this.onDismissed, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final actions = FieldAction.values
+        .map(
+          (action) => FieldActionItem(
+            fieldId: fieldData.field.id,
+            action: action,
+            onTap: onDismissed,
+          ),
+        )
+        .toList();
+
+    return FieldOperationList(actions: actions);
+  }
+}
+
 class _FieldTypeSwitcher extends StatelessWidget {
   const _FieldTypeSwitcher({Key? key}) : super(key: key);
 
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart
index fb639e5f5b..ddf5eb7660 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_operation_list.dart
@@ -9,17 +9,11 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class FieldOperationList extends StatelessWidget {
-  final VoidCallback onDismiss;
-  const FieldOperationList({required this.onDismiss, Key? key}) : super(key: key);
+  final List<FieldActionItem> actions;
+  const FieldOperationList({required this.actions, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    final children = FieldAction.values
-        .map((action) => FieldActionItem(
-              action: action,
-              onTap: onDismiss,
-            ))
-        .toList();
     return GridView(
       // https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
       shrinkWrap: true,
@@ -28,15 +22,22 @@ class FieldOperationList extends StatelessWidget {
         childAspectRatio: 4.0,
         mainAxisSpacing: 8,
       ),
-      children: children,
+      children: actions,
     );
   }
 }
 
 class FieldActionItem extends StatelessWidget {
+  final String fieldId;
   final VoidCallback onTap;
   final FieldAction action;
-  const FieldActionItem({required this.action, required this.onTap, Key? key}) : super(key: key);
+
+  const FieldActionItem({
+    required this.fieldId,
+    required this.action,
+    required this.onTap,
+    Key? key,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -45,7 +46,7 @@ class FieldActionItem extends StatelessWidget {
       text: FlowyText.medium(action.title(), fontSize: 12),
       hoverColor: theme.hover,
       onTap: () {
-        action.run(context);
+        action.run(context, fieldId);
         onTap();
       },
       leftIcon: svg(action.iconName(), color: theme.iconColor),
@@ -82,16 +83,16 @@ extension _FieldActionExtension on FieldAction {
     }
   }
 
-  void run(BuildContext context) {
+  void run(BuildContext context, String fieldId) {
     switch (this) {
       case FieldAction.hide:
-        context.read<EditFieldBloc>().add(const EditFieldEvent.hideField());
+        context.read<EditFieldBloc>().add(EditFieldEvent.hideField(fieldId));
         break;
       case FieldAction.duplicate:
-        context.read<EditFieldBloc>().add(const EditFieldEvent.duplicateField());
+        context.read<EditFieldBloc>().add(EditFieldEvent.duplicateField(fieldId));
         break;
       case FieldAction.delete:
-        context.read<EditFieldBloc>().add(const EditFieldEvent.deleteField());
+        context.read<EditFieldBloc>().add(EditFieldEvent.deleteField(fieldId));
         break;
     }
   }
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
deleted file mode 100644
index dbebb311b9..0000000000
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
+++ /dev/null
@@ -1,123 +0,0 @@
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/button.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-import 'create_field_pannel.dart';
-import 'header_cell.dart';
-
-class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
-  final String gridId;
-  final List<Field> fields;
-
-  GridHeaderDelegate({required this.gridId, required this.fields});
-
-  @override
-  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
-    return GridHeader(gridId: gridId, fields: fields, key: ObjectKey(fields));
-  }
-
-  @override
-  double get maxExtent => GridSize.headerHeight;
-
-  @override
-  double get minExtent => GridSize.headerHeight;
-
-  @override
-  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
-    if (oldDelegate is GridHeaderDelegate) {
-      return fields != oldDelegate.fields;
-    }
-    return false;
-  }
-}
-
-class GridHeader extends StatelessWidget {
-  final List<Field> fields;
-  final String gridId;
-  const GridHeader({required this.gridId, required this.fields, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return BlocProvider(
-      create: (context) {
-        final bloc = getIt<GridHeaderBloc>(param1: gridId, param2: fields);
-        bloc.add(const GridHeaderEvent.initial());
-        return bloc;
-      },
-      child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
-        builder: (context, state) {
-          final cells = state.fields.map(
-            (field) => HeaderCell(
-              GridFieldData(gridId: gridId, field: field),
-            ),
-          );
-
-          final row = Row(
-            crossAxisAlignment: CrossAxisAlignment.stretch,
-            children: [
-              const _HeaderLeading(),
-              ...cells,
-              _HeaderTrailing(gridId: gridId),
-            ],
-          );
-
-          return Container(color: theme.surface, child: row);
-        },
-      ),
-    );
-  }
-}
-
-class _HeaderLeading extends StatelessWidget {
-  const _HeaderLeading({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox(
-      width: GridSize.leadingHeaderPadding,
-    );
-  }
-}
-
-class _HeaderTrailing extends StatelessWidget {
-  final String gridId;
-  const _HeaderTrailing({required this.gridId, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
-    return Container(
-      width: GridSize.trailHeaderPadding,
-      decoration: BoxDecoration(
-        border: Border(top: borderSide, bottom: borderSide),
-      ),
-      padding: GridSize.headerContentInsets,
-      child: CreateFieldButton(gridId: gridId),
-    );
-  }
-}
-
-class CreateFieldButton extends StatelessWidget {
-  final String gridId;
-  const CreateFieldButton({required this.gridId, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return FlowyButton(
-      text: const FlowyText.medium('New column', fontSize: 12),
-      hoverColor: theme.hover,
-      onTap: () => CreateFieldPannel(gridId: gridId).show(context, gridId),
-      leftIcon: svg("home/add"),
-    );
-  }
-}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
deleted file mode 100755
index 52e7e38d75..0000000000
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
+++ /dev/null
@@ -1,36 +0,0 @@
-import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
-import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:flowy_infra/image.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/button.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-import 'edit_field_pannel.dart';
-
-class HeaderCell extends StatelessWidget {
-  final GridFieldData fieldData;
-  const HeaderCell(this.fieldData, {Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    final button = FlowyButton(
-      hoverColor: theme.hover,
-      onTap: () => EditFieldPannel.show(context, fieldData),
-      rightIcon: svg("editor/details", color: theme.iconColor),
-      text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(fieldData.field.name, fontSize: 12)),
-    );
-
-    final borderSide = BorderSide(color: theme.shader4, width: 0.4);
-    final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
-
-    return Container(
-      width: fieldData.field.width.toDouble(),
-      decoration: decoration,
-      padding: GridSize.headerContentInsets,
-      child: button,
-    );
-  }
-}
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart
index 78d65a9941..0bcd05f56e 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart
@@ -40,10 +40,9 @@ class FlowyButton extends StatelessWidget {
       children.add(const HSpace(6));
     }
 
-    children.add(text);
+    children.add(Expanded(child: text));
 
     if (rightIcon != null) {
-      children.add(const Spacer());
       children.add(SizedBox.fromSize(size: const Size.square(16), child: rightIcon!));
       children.add(const HSpace(6));
     }
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
index 0c3d242c37..7f25329284 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dart_event/flowy-grid/dart_event.dart
@@ -53,7 +53,7 @@ class GridEventGetFields {
 }
 
 class GridEventUpdateField {
-     FieldChangeset request;
+     FieldChangesetPayload request;
      GridEventUpdateField(this.request);
 
     Future<Either<Unit, FlowyError>> send() {
@@ -87,7 +87,7 @@ class GridEventCreateField {
 }
 
 class GridEventDeleteField {
-     FieldOrder request;
+     FieldIdentifierPayload request;
      GridEventDeleteField(this.request);
 
     Future<Either<Unit, FlowyError>> send() {
@@ -103,6 +103,23 @@ class GridEventDeleteField {
     }
 }
 
+class GridEventDuplicateField {
+     FieldIdentifierPayload request;
+     GridEventDuplicateField(this.request);
+
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = GridEvent.DuplicateField.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class GridEventCreateEditFieldContext {
      CreateEditFieldContextParams request;
      GridEventCreateEditFieldContext(this.request);
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
index 4ea447d729..bd9afe2243 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pb.dart
@@ -205,6 +205,128 @@ class Field extends $pb.GeneratedMessage {
   void clearWidth() => clearField(7);
 }
 
+class FieldIdentifierPayload extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldIdentifierPayload', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..hasRequiredFields = false
+  ;
+
+  FieldIdentifierPayload._() : super();
+  factory FieldIdentifierPayload({
+    $core.String? fieldId,
+    $core.String? gridId,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    return _result;
+  }
+  factory FieldIdentifierPayload.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldIdentifierPayload.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  FieldIdentifierPayload clone() => FieldIdentifierPayload()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  FieldIdentifierPayload copyWith(void Function(FieldIdentifierPayload) updates) => super.copyWith((message) => updates(message as FieldIdentifierPayload)) as FieldIdentifierPayload; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static FieldIdentifierPayload create() => FieldIdentifierPayload._();
+  FieldIdentifierPayload createEmptyInstance() => create();
+  static $pb.PbList<FieldIdentifierPayload> createRepeated() => $pb.PbList<FieldIdentifierPayload>();
+  @$core.pragma('dart2js:noInline')
+  static FieldIdentifierPayload getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldIdentifierPayload>(create);
+  static FieldIdentifierPayload? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get gridId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set gridId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasGridId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearGridId() => clearField(2);
+}
+
+class FieldIdentifierParams extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldIdentifierParams', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'gridId')
+    ..hasRequiredFields = false
+  ;
+
+  FieldIdentifierParams._() : super();
+  factory FieldIdentifierParams({
+    $core.String? fieldId,
+    $core.String? gridId,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    if (gridId != null) {
+      _result.gridId = gridId;
+    }
+    return _result;
+  }
+  factory FieldIdentifierParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory FieldIdentifierParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  FieldIdentifierParams clone() => FieldIdentifierParams()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  FieldIdentifierParams copyWith(void Function(FieldIdentifierParams) updates) => super.copyWith((message) => updates(message as FieldIdentifierParams)) as FieldIdentifierParams; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static FieldIdentifierParams create() => FieldIdentifierParams._();
+  FieldIdentifierParams createEmptyInstance() => create();
+  static $pb.PbList<FieldIdentifierParams> createRepeated() => $pb.PbList<FieldIdentifierParams>();
+  @$core.pragma('dart2js:noInline')
+  static FieldIdentifierParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FieldIdentifierParams>(create);
+  static FieldIdentifierParams? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get gridId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set gridId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasGridId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearGridId() => clearField(2);
+}
+
 class FieldOrder extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'FieldOrder', createEmptyInstance: create)
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
index a7dc3255c8..0b900d46f9 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/grid.pbjson.dart
@@ -36,6 +36,28 @@ const Field$json = const {
 
 /// Descriptor for `Field`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIeCgp2aXNpYmlsaXR5GAYgASgIUgp2aXNpYmlsaXR5EhQKBXdpZHRoGAcgASgFUgV3aWR0aA==');
+@$core.Deprecated('Use fieldIdentifierPayloadDescriptor instead')
+const FieldIdentifierPayload$json = const {
+  '1': 'FieldIdentifierPayload',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'grid_id', '3': 2, '4': 1, '5': 9, '10': 'gridId'},
+  ],
+};
+
+/// Descriptor for `FieldIdentifierPayload`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldIdentifierPayloadDescriptor = $convert.base64Decode('ChZGaWVsZElkZW50aWZpZXJQYXlsb2FkEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElkEhcKB2dyaWRfaWQYAiABKAlSBmdyaWRJZA==');
+@$core.Deprecated('Use fieldIdentifierParamsDescriptor instead')
+const FieldIdentifierParams$json = const {
+  '1': 'FieldIdentifierParams',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+    const {'1': 'grid_id', '3': 2, '4': 1, '5': 9, '10': 'gridId'},
+  ],
+};
+
+/// Descriptor for `FieldIdentifierParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List fieldIdentifierParamsDescriptor = $convert.base64Decode('ChVGaWVsZElkZW50aWZpZXJQYXJhbXMSGQoIZmllbGRfaWQYASABKAlSB2ZpZWxkSWQSFwoHZ3JpZF9pZBgCIAEoCVIGZ3JpZElk');
 @$core.Deprecated('Use fieldOrderDescriptor instead')
 const FieldOrder$json = const {
   '1': 'FieldOrder',
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
index 755b1c1ed1..46291923e0 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbenum.dart
@@ -16,7 +16,8 @@ class GridEvent extends $pb.ProtobufEnum {
   static const GridEvent UpdateField = GridEvent._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateField');
   static const GridEvent CreateField = GridEvent._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateField');
   static const GridEvent DeleteField = GridEvent._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteField');
-  static const GridEvent CreateEditFieldContext = GridEvent._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateEditFieldContext');
+  static const GridEvent DuplicateField = GridEvent._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateField');
+  static const GridEvent CreateEditFieldContext = GridEvent._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateEditFieldContext');
   static const GridEvent CreateRow = GridEvent._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateRow');
   static const GridEvent GetRow = GridEvent._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetRow');
   static const GridEvent UpdateCell = GridEvent._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateCell');
@@ -28,6 +29,7 @@ class GridEvent extends $pb.ProtobufEnum {
     UpdateField,
     CreateField,
     DeleteField,
+    DuplicateField,
     CreateEditFieldContext,
     CreateRow,
     GetRow,
diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
index 9441a03bea..278e0b14c3 100644
--- a/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
+++ b/frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid/event_map.pbjson.dart
@@ -18,7 +18,8 @@ const GridEvent$json = const {
     const {'1': 'UpdateField', '2': 11},
     const {'1': 'CreateField', '2': 12},
     const {'1': 'DeleteField', '2': 13},
-    const {'1': 'CreateEditFieldContext', '2': 14},
+    const {'1': 'DuplicateField', '2': 15},
+    const {'1': 'CreateEditFieldContext', '2': 16},
     const {'1': 'CreateRow', '2': 21},
     const {'1': 'GetRow', '2': 22},
     const {'1': 'UpdateCell', '2': 30},
@@ -26,4 +27,4 @@ const GridEvent$json = const {
 };
 
 /// Descriptor for `GridEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SGgoWQ3JlYXRlRWRpdEZpZWxkQ29udGV4dBAOEg0KCUNyZWF0ZVJvdxAVEgoKBkdldFJvdxAWEg4KClVwZGF0ZUNlbGwQHg==');
+final $typed_data.Uint8List gridEventDescriptor = $convert.base64Decode('CglHcmlkRXZlbnQSDwoLR2V0R3JpZERhdGEQABIRCg1HZXRHcmlkQmxvY2tzEAESDQoJR2V0RmllbGRzEAoSDwoLVXBkYXRlRmllbGQQCxIPCgtDcmVhdGVGaWVsZBAMEg8KC0RlbGV0ZUZpZWxkEA0SEgoORHVwbGljYXRlRmllbGQQDxIaChZDcmVhdGVFZGl0RmllbGRDb250ZXh0EBASDQoJQ3JlYXRlUm93EBUSCgoGR2V0Um93EBYSDgoKVXBkYXRlQ2VsbBAe');
diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs
index e16c761cb2..97b91e5557 100644
--- a/frontend/rust-lib/flowy-grid/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs
@@ -68,12 +68,23 @@ pub(crate) async fn create_field_handler(
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn delete_field_handler(
-    data: Data<FieldOrder>,
+    data: Data<FieldIdentifierPayload>,
     manager: AppData<Arc<GridManager>>,
 ) -> Result<(), FlowyError> {
-    let field_order: FieldOrder = data.into_inner();
+    let params: FieldIdentifierParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let _ = editor.delete_field(&field_order.field_id).await?;
+    let _ = editor.delete_field(&params.field_id).await?;
+    Ok(())
+}
+
+#[tracing::instrument(level = "debug", skip(data, manager), err)]
+pub(crate) async fn duplicate_field_handler(
+    data: Data<FieldIdentifierPayload>,
+    manager: AppData<Arc<GridManager>>,
+) -> Result<(), FlowyError> {
+    let params: FieldIdentifierParams = data.into_inner().try_into()?;
+    let editor = manager.get_grid_editor(&params.grid_id)?;
+    let _ = editor.duplicate_field(&params.field_id).await?;
     Ok(())
 }
 
diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs
index 9819f2142e..efe5ddc094 100644
--- a/frontend/rust-lib/flowy-grid/src/event_map.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_map.rs
@@ -13,7 +13,8 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetFields, get_fields_handler)
         .event(GridEvent::UpdateField, update_field_handler)
         .event(GridEvent::CreateField, create_field_handler)
-        .event(GridEvent::DeleteField, create_field_handler)
+        .event(GridEvent::DeleteField, delete_field_handler)
+        .event(GridEvent::DuplicateField, duplicate_field_handler)
         .event(GridEvent::CreateEditFieldContext, create_edit_field_context_handler)
         .event(GridEvent::CreateRow, create_row_handler)
         .event(GridEvent::GetRow, get_row_handler)
@@ -34,17 +35,20 @@ pub enum GridEvent {
     #[event(input = "QueryFieldPayload", output = "RepeatedField")]
     GetFields = 10,
 
-    #[event(input = "FieldChangeset")]
+    #[event(input = "FieldChangesetPayload")]
     UpdateField = 11,
 
     #[event(input = "CreateFieldPayload")]
     CreateField = 12,
 
-    #[event(input = "FieldOrder")]
+    #[event(input = "FieldIdentifierPayload")]
     DeleteField = 13,
 
+    #[event(input = "FieldIdentifierPayload")]
+    DuplicateField = 15,
+
     #[event(input = "CreateEditFieldContextParams", output = "EditFieldContext")]
-    CreateEditFieldContext = 14,
+    CreateEditFieldContext = 16,
 
     #[event(input = "CreateRowPayload", output = "Row")]
     CreateRow = 21,
diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
index dfa0f30b74..13ee0705cc 100644
--- a/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
+++ b/frontend/rust-lib/flowy-grid/src/protobuf/model/event_map.rs
@@ -31,7 +31,8 @@ pub enum GridEvent {
     UpdateField = 11,
     CreateField = 12,
     DeleteField = 13,
-    CreateEditFieldContext = 14,
+    DuplicateField = 15,
+    CreateEditFieldContext = 16,
     CreateRow = 21,
     GetRow = 22,
     UpdateCell = 30,
@@ -50,7 +51,8 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             11 => ::std::option::Option::Some(GridEvent::UpdateField),
             12 => ::std::option::Option::Some(GridEvent::CreateField),
             13 => ::std::option::Option::Some(GridEvent::DeleteField),
-            14 => ::std::option::Option::Some(GridEvent::CreateEditFieldContext),
+            15 => ::std::option::Option::Some(GridEvent::DuplicateField),
+            16 => ::std::option::Option::Some(GridEvent::CreateEditFieldContext),
             21 => ::std::option::Option::Some(GridEvent::CreateRow),
             22 => ::std::option::Option::Some(GridEvent::GetRow),
             30 => ::std::option::Option::Some(GridEvent::UpdateCell),
@@ -66,6 +68,7 @@ impl ::protobuf::ProtobufEnum for GridEvent {
             GridEvent::UpdateField,
             GridEvent::CreateField,
             GridEvent::DeleteField,
+            GridEvent::DuplicateField,
             GridEvent::CreateEditFieldContext,
             GridEvent::CreateRow,
             GridEvent::GetRow,
@@ -98,12 +101,12 @@ impl ::protobuf::reflect::ProtobufValue for GridEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0fevent_map.proto*\xb8\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
+    \n\x0fevent_map.proto*\xcc\x01\n\tGridEvent\x12\x0f\n\x0bGetGridData\x10\
     \0\x12\x11\n\rGetGridBlocks\x10\x01\x12\r\n\tGetFields\x10\n\x12\x0f\n\
     \x0bUpdateField\x10\x0b\x12\x0f\n\x0bCreateField\x10\x0c\x12\x0f\n\x0bDe\
-    leteField\x10\r\x12\x1a\n\x16CreateEditFieldContext\x10\x0e\x12\r\n\tCre\
-    ateRow\x10\x15\x12\n\n\x06GetRow\x10\x16\x12\x0e\n\nUpdateCell\x10\x1eb\
-    \x06proto3\
+    leteField\x10\r\x12\x12\n\x0eDuplicateField\x10\x0f\x12\x1a\n\x16CreateE\
+    ditFieldContext\x10\x10\x12\r\n\tCreateRow\x10\x15\x12\n\n\x06GetRow\x10\
+    \x16\x12\x0e\n\nUpdateCell\x10\x1eb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
diff --git a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
index 36e5b81d12..327a1e3aee 100644
--- a/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
+++ b/frontend/rust-lib/flowy-grid/src/protobuf/proto/event_map.proto
@@ -7,7 +7,8 @@ enum GridEvent {
     UpdateField = 11;
     CreateField = 12;
     DeleteField = 13;
-    CreateEditFieldContext = 14;
+    DuplicateField = 15;
+    CreateEditFieldContext = 16;
     CreateRow = 21;
     GetRow = 22;
     UpdateCell = 30;
diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
index 022dc3bbfa..7ff089ff9b 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
@@ -110,6 +110,12 @@ impl ClientGridEditor {
         Ok(())
     }
 
+    pub async fn duplicate_field(&self, field_id: &str) -> FlowyResult<()> {
+        let _ = self.modify(|grid| Ok(grid.duplicate_field(field_id)?)).await?;
+        let _ = self.notify_did_update_fields().await?;
+        Ok(())
+    }
+
     pub async fn create_block(&self, grid_block: GridBlockMeta) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.create_block(grid_block)?)).await?;
         Ok(())
@@ -254,8 +260,9 @@ impl ClientGridEditor {
     }
 
     pub async fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> FlowyResult<Vec<FieldMeta>> {
-        let field_meta = self.pad.read().await.get_field_metas(field_orders)?;
-        Ok(field_meta)
+        let mut field_metas = self.pad.read().await.get_field_metas(field_orders)?;
+        field_metas.retain(|field_meta| field_meta.visibility);
+        Ok(field_metas)
     }
 
     pub async fn get_block_meta_data_vec(
diff --git a/shared-lib/flowy-grid-data-model/src/entities/grid.rs b/shared-lib/flowy-grid-data-model/src/entities/grid.rs
index 22d03a5993..dfd5dc397f 100644
--- a/shared-lib/flowy-grid-data-model/src/entities/grid.rs
+++ b/shared-lib/flowy-grid-data-model/src/entities/grid.rs
@@ -55,6 +55,37 @@ impl std::convert::From<FieldMeta> for Field {
     }
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct FieldIdentifierPayload {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2)]
+    pub grid_id: String,
+}
+
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct FieldIdentifierParams {
+    #[pb(index = 1)]
+    pub field_id: String,
+
+    #[pb(index = 2)]
+    pub grid_id: String,
+}
+
+impl TryInto<FieldIdentifierParams> for FieldIdentifierPayload {
+    type Error = ErrorCode;
+
+    fn try_into(self) -> Result<FieldIdentifierParams, Self::Error> {
+        let grid_id = NotEmptyUuid::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?;
+        let field_id = NotEmptyUuid::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
+        Ok(FieldIdentifierParams {
+            grid_id: grid_id.0,
+            field_id: field_id.0,
+        })
+    }
+}
+
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct FieldOrder {
     #[pb(index = 1)]
diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
index a6afa46d9a..c18043184f 100644
--- a/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
+++ b/shared-lib/flowy-grid-data-model/src/protobuf/model/grid.rs
@@ -659,6 +659,408 @@ impl ::protobuf::reflect::ProtobufValue for Field {
     }
 }
 
+#[derive(PartialEq,Clone,Default)]
+pub struct FieldIdentifierPayload {
+    // message fields
+    pub field_id: ::std::string::String,
+    pub grid_id: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a FieldIdentifierPayload {
+    fn default() -> &'a FieldIdentifierPayload {
+        <FieldIdentifierPayload as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl FieldIdentifierPayload {
+    pub fn new() -> FieldIdentifierPayload {
+        ::std::default::Default::default()
+    }
+
+    // string field_id = 1;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    }
+
+    // string grid_id = 2;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for FieldIdentifierPayload {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.field_id);
+        }
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.grid_id);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.field_id.is_empty() {
+            os.write_string(1, &self.field_id)?;
+        }
+        if !self.grid_id.is_empty() {
+            os.write_string(2, &self.grid_id)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> FieldIdentifierPayload {
+        FieldIdentifierPayload::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &FieldIdentifierPayload| { &m.field_id },
+                |m: &mut FieldIdentifierPayload| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &FieldIdentifierPayload| { &m.grid_id },
+                |m: &mut FieldIdentifierPayload| { &mut m.grid_id },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldIdentifierPayload>(
+                "FieldIdentifierPayload",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static FieldIdentifierPayload {
+        static instance: ::protobuf::rt::LazyV2<FieldIdentifierPayload> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(FieldIdentifierPayload::new)
+    }
+}
+
+impl ::protobuf::Clear for FieldIdentifierPayload {
+    fn clear(&mut self) {
+        self.field_id.clear();
+        self.grid_id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for FieldIdentifierPayload {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for FieldIdentifierPayload {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct FieldIdentifierParams {
+    // message fields
+    pub field_id: ::std::string::String,
+    pub grid_id: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a FieldIdentifierParams {
+    fn default() -> &'a FieldIdentifierParams {
+        <FieldIdentifierParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl FieldIdentifierParams {
+    pub fn new() -> FieldIdentifierParams {
+        ::std::default::Default::default()
+    }
+
+    // string field_id = 1;
+
+
+    pub fn get_field_id(&self) -> &str {
+        &self.field_id
+    }
+    pub fn clear_field_id(&mut self) {
+        self.field_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_field_id(&mut self, v: ::std::string::String) {
+        self.field_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_field_id(&mut self) -> &mut ::std::string::String {
+        &mut self.field_id
+    }
+
+    // Take field
+    pub fn take_field_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.field_id, ::std::string::String::new())
+    }
+
+    // string grid_id = 2;
+
+
+    pub fn get_grid_id(&self) -> &str {
+        &self.grid_id
+    }
+    pub fn clear_grid_id(&mut self) {
+        self.grid_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_grid_id(&mut self, v: ::std::string::String) {
+        self.grid_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_grid_id(&mut self) -> &mut ::std::string::String {
+        &mut self.grid_id
+    }
+
+    // Take field
+    pub fn take_grid_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.grid_id, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for FieldIdentifierParams {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.grid_id)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.field_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.field_id);
+        }
+        if !self.grid_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.grid_id);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.field_id.is_empty() {
+            os.write_string(1, &self.field_id)?;
+        }
+        if !self.grid_id.is_empty() {
+            os.write_string(2, &self.grid_id)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> FieldIdentifierParams {
+        FieldIdentifierParams::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "field_id",
+                |m: &FieldIdentifierParams| { &m.field_id },
+                |m: &mut FieldIdentifierParams| { &mut m.field_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "grid_id",
+                |m: &FieldIdentifierParams| { &m.grid_id },
+                |m: &mut FieldIdentifierParams| { &mut m.grid_id },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<FieldIdentifierParams>(
+                "FieldIdentifierParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static FieldIdentifierParams {
+        static instance: ::protobuf::rt::LazyV2<FieldIdentifierParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(FieldIdentifierParams::new)
+    }
+}
+
+impl ::protobuf::Clear for FieldIdentifierParams {
+    fn clear(&mut self) {
+        self.field_id.clear();
+        self.grid_id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for FieldIdentifierParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for FieldIdentifierParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
 #[derive(PartialEq,Clone,Default)]
 pub struct FieldOrder {
     // message fields
@@ -4865,48 +5267,51 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12)\n\nfield_type\x18\x04\x20\
     \x01(\x0e2\n.FieldTypeR\tfieldType\x12\x16\n\x06frozen\x18\x05\x20\x01(\
     \x08R\x06frozen\x12\x1e\n\nvisibility\x18\x06\x20\x01(\x08R\nvisibility\
-    \x12\x14\n\x05width\x18\x07\x20\x01(\x05R\x05width\"'\n\nFieldOrder\x12\
-    \x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\"b\n\x1cCreateEditFiel\
-    dContextParams\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12)\n\
-    \nfield_type\x18\x02\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditF\
-    ieldContext\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ng\
-    rid_field\x18\x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_opt\
-    ion_data\x18\x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\
-    \x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12Repeat\
-    edFieldOrder\x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05it\
-    ems\"T\n\x08RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\
-    \x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06heigh\
-    t\x18\x03\x20\x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\
-    \x01\x20\x01(\tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\
-    \x17.Row.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\
-    \x20\x01(\x05R\x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\
-    \x18\x01\x20\x01(\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05\
-    .CellR\x05value:\x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\
-    \x20\x03(\x0b2\x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05\
-    items\x18\x01\x20\x03(\x0b2\n.GridBlockR\x05items\"+\n\x0eGridBlockOrder\
-    \x12\x19\n\x08block_id\x18\x01\x20\x01(\tR\x07blockId\"E\n\tGridBlock\
-    \x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\
-    \x20\x03(\x0b2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\x08field_id\
-    \x18\x01\x20\x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\
-    \x07content\"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b\
-    2\x05.CellR\x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\
-    \x20\x01(\tR\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\
-    \x01(\tR\x05value\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01\
-    (\tR\x05value\"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\
-    \x01(\tR\x06gridId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstart\
-    RowIdB\x15\n\x13one_of_start_row_id\"\xb6\x01\n\x12CreateFieldPayload\
-    \x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\
-    \x18\x02\x20\x01(\x0b2\x06.FieldR\x05field\x12(\n\x10type_option_data\
-    \x18\x03\x20\x01(\x0cR\x0etypeOptionData\x12&\n\x0estart_field_id\x18\
-    \x04\x20\x01(\tH\0R\x0cstartFieldIdB\x17\n\x15one_of_start_field_id\"d\n\
-    \x11QueryFieldPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\
-    \x126\n\x0cfield_orders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\
-    \x0bfieldOrders\"e\n\x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\
-    \x01\x20\x01(\tR\x06gridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\
-    \x0f.GridBlockOrderR\x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\
-    \x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\
-    \x20\x01(\tR\x07blockId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowId\
-    b\x06proto3\
+    \x12\x14\n\x05width\x18\x07\x20\x01(\x05R\x05width\"L\n\x16FieldIdentifi\
+    erPayload\x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\
+    \x07grid_id\x18\x02\x20\x01(\tR\x06gridId\"K\n\x15FieldIdentifierParams\
+    \x12\x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x17\n\x07grid_\
+    id\x18\x02\x20\x01(\tR\x06gridId\"'\n\nFieldOrder\x12\x19\n\x08field_id\
+    \x18\x01\x20\x01(\tR\x07fieldId\"b\n\x1cCreateEditFieldContextParams\x12\
+    \x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12)\n\nfield_type\x18\
+    \x02\x20\x01(\x0e2\n.FieldTypeR\tfieldType\"|\n\x10EditFieldContext\x12\
+    \x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x12%\n\ngrid_field\x18\
+    \x02\x20\x01(\x0b2\x06.FieldR\tgridField\x12(\n\x10type_option_data\x18\
+    \x03\x20\x01(\x0cR\x0etypeOptionData\"-\n\rRepeatedField\x12\x1c\n\x05it\
+    ems\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"7\n\x12RepeatedFieldOrder\
+    \x12!\n\x05items\x18\x01\x20\x03(\x0b2\x0b.FieldOrderR\x05items\"T\n\x08\
+    RowOrder\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\x05rowId\x12\x19\n\x08b\
+    lock_id\x18\x02\x20\x01(\tR\x07blockId\x12\x16\n\x06height\x18\x03\x20\
+    \x01(\x05R\x06height\"\xb8\x01\n\x03Row\x12\x0e\n\x02id\x18\x01\x20\x01(\
+    \tR\x02id\x12@\n\x10cell_by_field_id\x18\x02\x20\x03(\x0b2\x17.Row.CellB\
+    yFieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x03\x20\x01(\x05R\
+    \x06height\x1aG\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01\
+    (\tR\x03key\x12\x1b\n\x05value\x18\x02\x20\x01(\x0b2\x05.CellR\x05value:\
+    \x028\x01\")\n\x0bRepeatedRow\x12\x1a\n\x05items\x18\x01\x20\x03(\x0b2\
+    \x04.RowR\x05items\"5\n\x11RepeatedGridBlock\x12\x20\n\x05items\x18\x01\
+    \x20\x03(\x0b2\n.GridBlockR\x05items\"+\n\x0eGridBlockOrder\x12\x19\n\
+    \x08block_id\x18\x01\x20\x01(\tR\x07blockId\"E\n\tGridBlock\x12\x0e\n\
+    \x02id\x18\x01\x20\x01(\tR\x02id\x12(\n\nrow_orders\x18\x02\x20\x03(\x0b\
+    2\t.RowOrderR\trowOrders\";\n\x04Cell\x12\x19\n\x08field_id\x18\x01\x20\
+    \x01(\tR\x07fieldId\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07content\
+    \"+\n\x0cRepeatedCell\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.CellR\
+    \x05items\"'\n\x11CreateGridPayload\x12\x12\n\x04name\x18\x01\x20\x01(\t\
+    R\x04name\"\x1e\n\x06GridId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05va\
+    lue\"#\n\x0bGridBlockId\x12\x14\n\x05value\x18\x01\x20\x01(\tR\x05value\
+    \"f\n\x10CreateRowPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gr\
+    idId\x12\"\n\x0cstart_row_id\x18\x02\x20\x01(\tH\0R\nstartRowIdB\x15\n\
+    \x13one_of_start_row_id\"\xb6\x01\n\x12CreateFieldPayload\x12\x17\n\x07g\
+    rid_id\x18\x01\x20\x01(\tR\x06gridId\x12\x1c\n\x05field\x18\x02\x20\x01(\
+    \x0b2\x06.FieldR\x05field\x12(\n\x10type_option_data\x18\x03\x20\x01(\
+    \x0cR\x0etypeOptionData\x12&\n\x0estart_field_id\x18\x04\x20\x01(\tH\0R\
+    \x0cstartFieldIdB\x17\n\x15one_of_start_field_id\"d\n\x11QueryFieldPaylo\
+    ad\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06gridId\x126\n\x0cfield_or\
+    ders\x18\x02\x20\x01(\x0b2\x13.RepeatedFieldOrderR\x0bfieldOrders\"e\n\
+    \x16QueryGridBlocksPayload\x12\x17\n\x07grid_id\x18\x01\x20\x01(\tR\x06g\
+    ridId\x122\n\x0cblock_orders\x18\x02\x20\x03(\x0b2\x0f.GridBlockOrderR\
+    \x0bblockOrders\"\\\n\x0fQueryRowPayload\x12\x17\n\x07grid_id\x18\x01\
+    \x20\x01(\tR\x06gridId\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07bloc\
+    kId\x12\x15\n\x06row_id\x18\x03\x20\x01(\tR\x05rowIdb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
diff --git a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
index ec109f580f..3a74e23026 100644
--- a/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
+++ b/shared-lib/flowy-grid-data-model/src/protobuf/proto/grid.proto
@@ -15,6 +15,14 @@ message Field {
     bool visibility = 6;
     int32 width = 7;
 }
+message FieldIdentifierPayload {
+    string field_id = 1;
+    string grid_id = 2;
+}
+message FieldIdentifierParams {
+    string field_id = 1;
+    string grid_id = 2;
+}
 message FieldOrder {
     string field_id = 1;
 }
diff --git a/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs
index e1712d2584..78c0091b57 100644
--- a/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs
+++ b/shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs
@@ -72,42 +72,17 @@ impl GridMetaPad {
         })
     }
 
-    pub fn contain_field(&self, field_id: &str) -> bool {
-        self.grid_meta.fields.iter().any(|field| field.id == field_id)
-    }
-
-    pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
-        self.grid_meta.fields.iter().find(|field| field.id == field_id)
-    }
-
-    pub fn get_field_orders(&self) -> Vec<FieldOrder> {
-        self.grid_meta.fields.iter().map(FieldOrder::from).collect()
-    }
-
-    pub fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<FieldMeta>> {
-        match field_orders {
-            None => Ok(self.grid_meta.fields.clone()),
-            Some(field_orders) => {
-                let field_by_field_id = self
-                    .grid_meta
-                    .fields
-                    .iter()
-                    .map(|field| (&field.id, field))
-                    .collect::<HashMap<&String, &FieldMeta>>();
-
-                let fields = field_orders
-                    .iter()
-                    .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
-                        None => {
-                            tracing::error!("Can't find the field with id: {}", field_order.field_id);
-                            None
-                        }
-                        Some(field) => Some((*field).clone()),
-                    })
-                    .collect::<Vec<FieldMeta>>();
-                Ok(fields)
+    pub fn duplicate_field(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
+        self.modify_grid(|grid| match grid.fields.iter().position(|field| field.id == field_id) {
+            None => Ok(None),
+            Some(index) => {
+                let mut duplicate_field_meta = grid.fields[index].clone();
+                duplicate_field_meta.id = uuid();
+                duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name);
+                grid.fields.insert(index + 1, duplicate_field_meta);
+                Ok(Some(()))
             }
-        }
+        })
     }
 
     pub fn update_field(&mut self, changeset: FieldChangesetParams) -> CollaborateResult<Option<GridChangeset>> {
@@ -160,6 +135,44 @@ impl GridMetaPad {
         })
     }
 
+    pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
+        self.grid_meta.fields.iter().find(|field| field.id == field_id)
+    }
+
+    pub fn contain_field(&self, field_id: &str) -> bool {
+        self.grid_meta.fields.iter().any(|field| field.id == field_id)
+    }
+
+    pub fn get_field_orders(&self) -> Vec<FieldOrder> {
+        self.grid_meta.fields.iter().map(FieldOrder::from).collect()
+    }
+
+    pub fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<FieldMeta>> {
+        match field_orders {
+            None => Ok(self.grid_meta.fields.clone()),
+            Some(field_orders) => {
+                let field_by_field_id = self
+                    .grid_meta
+                    .fields
+                    .iter()
+                    .map(|field| (&field.id, field))
+                    .collect::<HashMap<&String, &FieldMeta>>();
+
+                let fields = field_orders
+                    .iter()
+                    .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
+                        None => {
+                            tracing::error!("Can't find the field with id: {}", field_order.field_id);
+                            None
+                        }
+                        Some(field) => Some((*field).clone()),
+                    })
+                    .collect::<Vec<FieldMeta>>();
+                Ok(fields)
+            }
+        }
+    }
+
     pub fn create_block(&mut self, block: GridBlockMeta) -> CollaborateResult<Option<GridChangeset>> {
         self.modify_grid(|grid| {
             if grid.block_metas.iter().any(|b| b.block_id == block.block_id) {