diff --git a/.github/workflows/frontend-test.yml b/.github/workflows/frontend-test.yml
index 48efbf7..e579ece 100644
--- a/.github/workflows/frontend-test.yml
+++ b/.github/workflows/frontend-test.yml
@@ -28,6 +28,9 @@ jobs:
       - name: Run eslint check
         run: pnpm lint
         working-directory: frontend/web
+      - name: Run type check
+        run: pnpm type-check
+        working-directory: frontend/web
 
   frontend-build:
     runs-on: ubuntu-latest
diff --git a/frontend/extension/tsconfig.json b/frontend/extension/tsconfig.json
index 2618f69..6224ea3 100644
--- a/frontend/extension/tsconfig.json
+++ b/frontend/extension/tsconfig.json
@@ -1,18 +1,10 @@
 {
   "extends": "plasmo/templates/tsconfig.base",
-  "exclude": [
-    "node_modules"
-  ],
-  "include": [
-    ".plasmo/index.d.ts",
-    "./**/*.ts",
-    "./**/*.tsx",
-  ],
+  "exclude": ["node_modules"],
+  "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"],
   "compilerOptions": {
     "paths": {
-      "@/*": [
-        "./src/*"
-      ],
+      "@/*": ["./src/*"]
     },
     "baseUrl": "."
   }
diff --git a/frontend/web/package.json b/frontend/web/package.json
index 32a1a8f..0c16448 100644
--- a/frontend/web/package.json
+++ b/frontend/web/package.json
@@ -2,10 +2,11 @@
   "name": "slash",
   "scripts": {
     "dev": "vite",
-    "build": "tsc && vite build",
+    "build": "vite build",
     "serve": "vite preview",
     "lint": "eslint --ext .js,.ts,.tsx, src",
     "lint-fix": "eslint --ext .js,.ts,.tsx, src --fix",
+    "type-check": "tsc --noEmit --skipLibCheck",
     "postinstall": "cd ../../proto && buf generate"
   },
   "dependencies": {
@@ -33,6 +34,7 @@
   },
   "devDependencies": {
     "@bufbuild/buf": "^1.39.0",
+    "@bufbuild/protobuf": "^2.1.0",
     "@trivago/prettier-plugin-sort-imports": "^4.3.0",
     "@types/lodash-es": "^4.17.12",
     "@types/react": "^18.3.5",
diff --git a/frontend/web/pnpm-lock.yaml b/frontend/web/pnpm-lock.yaml
index 304e660..3216976 100644
--- a/frontend/web/pnpm-lock.yaml
+++ b/frontend/web/pnpm-lock.yaml
@@ -78,6 +78,9 @@ importers:
       '@bufbuild/buf':
         specifier: ^1.39.0
         version: 1.39.0
+      '@bufbuild/protobuf':
+        specifier: ^2.1.0
+        version: 2.1.0
       '@trivago/prettier-plugin-sort-imports':
         specifier: ^4.3.0
         version: 4.3.0(prettier@3.3.3)
@@ -256,6 +259,9 @@ packages:
     engines: {node: '>=12'}
     hasBin: true
 
+  '@bufbuild/protobuf@2.1.0':
+    resolution: {integrity: sha512-+2Mx67Y3skJ4NCD/qNSdBJNWtu6x6Qr53jeNg+QcwiL6mt0wK+3jwHH2x1p7xaYH6Ve2JKOVn0OxU35WsmqI9A==}
+
   '@emotion/babel-plugin@11.12.0':
     resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
 
@@ -2588,6 +2594,8 @@ snapshots:
       '@bufbuild/buf-win32-arm64': 1.39.0
       '@bufbuild/buf-win32-x64': 1.39.0
 
+  '@bufbuild/protobuf@2.1.0': {}
+
   '@emotion/babel-plugin@11.12.0':
     dependencies:
       '@babel/helper-module-imports': 7.24.7
diff --git a/frontend/web/tsconfig.json b/frontend/web/tsconfig.json
index 4a48a6e..f364b07 100644
--- a/frontend/web/tsconfig.json
+++ b/frontend/web/tsconfig.json
@@ -16,11 +16,10 @@
     "noEmit": true,
     "jsx": "react-jsx",
     "paths": {
-      "@/*": [
-        "./src/*"
-      ],
+      "@/*": ["./src/*"]
     },
     "baseUrl": "."
   },
-  "include": ["./src"]
+  "include": ["./src"],
+  "exclude": ["node_modules"]
 }
diff --git a/proto/gen/apidocs.swagger.yaml b/proto/gen/apidocs.swagger.yaml
index b711767..8a8e8bc 100644
--- a/proto/gen/apidocs.swagger.yaml
+++ b/proto/gen/apidocs.swagger.yaml
@@ -775,7 +775,9 @@ definitions:
       expiresAt:
         type: string
         format: date-time
-        description: "expires_at is the expiration time of the access token.\r\nIf expires_at is not set, the access token will never expire."
+        description: |-
+          expires_at is the expiration time of the access token.
+          If expires_at is not set, the access token will never expire.
   apiv1Collection:
     type: object
     properties:
@@ -1141,7 +1143,9 @@ definitions:
         description: Current workspace version.
       owner:
         type: string
-        title: "The owner name.\r\nFormat: \"users/{id}\""
+        title: |-
+          The owner name.
+          Format: "users/{id}"
       subscription:
         $ref: '#/definitions/v1Subscription'
         description: The workspace subscription.
diff --git a/proto/gen/store/README.md b/proto/gen/store/README.md
index 498b1f7..70cb699 100644
--- a/proto/gen/store/README.md
+++ b/proto/gen/store/README.md
@@ -6,6 +6,8 @@
 - [store/activity.proto](#store_activity-proto)
     - [ActivityShorcutCreatePayload](#slash-store-ActivityShorcutCreatePayload)
     - [ActivityShorcutViewPayload](#slash-store-ActivityShorcutViewPayload)
+    - [ActivityShorcutViewPayload.ParamsEntry](#slash-store-ActivityShorcutViewPayload-ParamsEntry)
+    - [ActivityShorcutViewPayload.ValueList](#slash-store-ActivityShorcutViewPayload-ValueList)
   
 - [store/common.proto](#store_common-proto)
     - [RowStatus](#slash-store-RowStatus)
@@ -81,6 +83,38 @@
 | ip | [string](#string) |  |  |
 | referer | [string](#string) |  |  |
 | user_agent | [string](#string) |  |  |
+| params | [ActivityShorcutViewPayload.ParamsEntry](#slash-store-ActivityShorcutViewPayload-ParamsEntry) | repeated |  |
+
+
+
+
+
+
+<a name="slash-store-ActivityShorcutViewPayload-ParamsEntry"></a>
+
+### ActivityShorcutViewPayload.ParamsEntry
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| key | [string](#string) |  |  |
+| value | [ActivityShorcutViewPayload.ValueList](#slash-store-ActivityShorcutViewPayload-ValueList) |  |  |
+
+
+
+
+
+
+<a name="slash-store-ActivityShorcutViewPayload-ValueList"></a>
+
+### ActivityShorcutViewPayload.ValueList
+
+
+
+| Field | Type | Label | Description |
+| ----- | ---- | ----- | ----------- |
+| values | [string](#string) | repeated |  |
 
 
 
diff --git a/proto/gen/store/activity.pb.go b/proto/gen/store/activity.pb.go
index 194d71d..0ecf94a 100644
--- a/proto/gen/store/activity.pb.go
+++ b/proto/gen/store/activity.pb.go
@@ -72,10 +72,11 @@ type ActivityShorcutViewPayload struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	ShortcutId int32  `protobuf:"varint,1,opt,name=shortcut_id,json=shortcutId,proto3" json:"shortcut_id,omitempty"`
-	Ip         string `protobuf:"bytes,2,opt,name=ip,proto3" json:"ip,omitempty"`
-	Referer    string `protobuf:"bytes,3,opt,name=referer,proto3" json:"referer,omitempty"`
-	UserAgent  string `protobuf:"bytes,4,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
+	ShortcutId int32                                            `protobuf:"varint,1,opt,name=shortcut_id,json=shortcutId,proto3" json:"shortcut_id,omitempty"`
+	Ip         string                                           `protobuf:"bytes,2,opt,name=ip,proto3" json:"ip,omitempty"`
+	Referer    string                                           `protobuf:"bytes,3,opt,name=referer,proto3" json:"referer,omitempty"`
+	UserAgent  string                                           `protobuf:"bytes,4,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
+	Params     map[string]*ActivityShorcutViewPayload_ValueList `protobuf:"bytes,5,rep,name=params,proto3" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
 }
 
 func (x *ActivityShorcutViewPayload) Reset() {
@@ -138,6 +139,60 @@ func (x *ActivityShorcutViewPayload) GetUserAgent() string {
 	return ""
 }
 
+func (x *ActivityShorcutViewPayload) GetParams() map[string]*ActivityShorcutViewPayload_ValueList {
+	if x != nil {
+		return x.Params
+	}
+	return nil
+}
+
+type ActivityShorcutViewPayload_ValueList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
+}
+
+func (x *ActivityShorcutViewPayload_ValueList) Reset() {
+	*x = ActivityShorcutViewPayload_ValueList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_store_activity_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ActivityShorcutViewPayload_ValueList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ActivityShorcutViewPayload_ValueList) ProtoMessage() {}
+
+func (x *ActivityShorcutViewPayload_ValueList) ProtoReflect() protoreflect.Message {
+	mi := &file_store_activity_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ActivityShorcutViewPayload_ValueList.ProtoReflect.Descriptor instead.
+func (*ActivityShorcutViewPayload_ValueList) Descriptor() ([]byte, []int) {
+	return file_store_activity_proto_rawDescGZIP(), []int{1, 1}
+}
+
+func (x *ActivityShorcutViewPayload_ValueList) GetValues() []string {
+	if x != nil {
+		return x.Values
+	}
+	return nil
+}
+
 var File_store_activity_proto protoreflect.FileDescriptor
 
 var file_store_activity_proto_rawDesc = []byte{
@@ -147,7 +202,7 @@ var file_store_activity_proto_rawDesc = []byte{
 	0x68, 0x6f, 0x72, 0x63, 0x75, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6c,
 	0x6f, 0x61, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x5f,
 	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63,
-	0x75, 0x74, 0x49, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x1a, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74,
+	0x75, 0x74, 0x49, 0x64, 0x22, 0xe6, 0x02, 0x0a, 0x1a, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74,
 	0x79, 0x53, 0x68, 0x6f, 0x72, 0x63, 0x75, 0x74, 0x56, 0x69, 0x65, 0x77, 0x50, 0x61, 0x79, 0x6c,
 	0x6f, 0x61, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63, 0x75, 0x74, 0x5f,
 	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x63,
@@ -155,7 +210,21 @@ var file_store_activity_proto_rawDesc = []byte{
 	0x52, 0x02, 0x69, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x18,
 	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 0x12, 0x1d,
 	0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x42, 0x9e, 0x01,
+	0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a,
+	0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e,
+	0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69,
+	0x76, 0x69, 0x74, 0x79, 0x53, 0x68, 0x6f, 0x72, 0x63, 0x75, 0x74, 0x56, 0x69, 0x65, 0x77, 0x50,
+	0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74,
+	0x72, 0x79, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x6c, 0x0a, 0x0b, 0x50, 0x61,
+	0x72, 0x61, 0x6d, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x47, 0x0a, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x73, 0x6c, 0x61,
+	0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74,
+	0x79, 0x53, 0x68, 0x6f, 0x72, 0x63, 0x75, 0x74, 0x56, 0x69, 0x65, 0x77, 0x50, 0x61, 0x79, 0x6c,
+	0x6f, 0x61, 0x64, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x23, 0x0a, 0x09, 0x56, 0x61, 0x6c, 0x75,
+	0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x42, 0x9e, 0x01,
 	0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x73, 0x6c, 0x61, 0x73, 0x68, 0x2e, 0x73, 0x74, 0x6f, 0x72,
 	0x65, 0x42, 0x0d, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f,
 	0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x79,
@@ -181,17 +250,21 @@ func file_store_activity_proto_rawDescGZIP() []byte {
 	return file_store_activity_proto_rawDescData
 }
 
-var file_store_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_store_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
 var file_store_activity_proto_goTypes = []any{
 	(*ActivityShorcutCreatePayload)(nil), // 0: slash.store.ActivityShorcutCreatePayload
 	(*ActivityShorcutViewPayload)(nil),   // 1: slash.store.ActivityShorcutViewPayload
+	nil,                                  // 2: slash.store.ActivityShorcutViewPayload.ParamsEntry
+	(*ActivityShorcutViewPayload_ValueList)(nil), // 3: slash.store.ActivityShorcutViewPayload.ValueList
 }
 var file_store_activity_proto_depIdxs = []int32{
-	0, // [0:0] is the sub-list for method output_type
-	0, // [0:0] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
+	2, // 0: slash.store.ActivityShorcutViewPayload.params:type_name -> slash.store.ActivityShorcutViewPayload.ParamsEntry
+	3, // 1: slash.store.ActivityShorcutViewPayload.ParamsEntry.value:type_name -> slash.store.ActivityShorcutViewPayload.ValueList
+	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
 }
 
 func init() { file_store_activity_proto_init() }
@@ -224,6 +297,18 @@ func file_store_activity_proto_init() {
 				return nil
 			}
 		}
+		file_store_activity_proto_msgTypes[3].Exporter = func(v any, i int) any {
+			switch v := v.(*ActivityShorcutViewPayload_ValueList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -231,7 +316,7 @@ func file_store_activity_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_store_activity_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   2,
+			NumMessages:   4,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/proto/store/activity.proto b/proto/store/activity.proto
index ee8d849..d3ef1e2 100644
--- a/proto/store/activity.proto
+++ b/proto/store/activity.proto
@@ -13,4 +13,9 @@ message ActivityShorcutViewPayload {
   string ip = 2;
   string referer = 3;
   string user_agent = 4;
+  map<string, ValueList> params = 5;
+
+  message ValueList {
+    repeated string values = 1;
+  }
 }
diff --git a/server/route/frontend/frontend.go b/server/route/frontend/frontend.go
index d2d4cc6..80699ef 100644
--- a/server/route/frontend/frontend.go
+++ b/server/route/frontend/frontend.go
@@ -122,11 +122,16 @@ func (s *FrontendService) createShortcutViewActivity(ctx context.Context, reques
 	ip := getReadUserIP(request)
 	referer := request.Header.Get("Referer")
 	userAgent := request.Header.Get("User-Agent")
+	params := map[string]*storepb.ActivityShorcutViewPayload_ValueList{}
+	for key, values := range request.URL.Query() {
+		params[key] = &storepb.ActivityShorcutViewPayload_ValueList{Values: values}
+	}
 	payload := &storepb.ActivityShorcutViewPayload{
 		ShortcutId: shortcut.Id,
 		Ip:         ip,
 		Referer:    referer,
 		UserAgent:  userAgent,
+		Params:     params,
 	}
 	payloadStr, err := protojson.Marshal(payload)
 	if err != nil {