Repository: grpc-ecosystem/grpc-gateway

Issue:
protoc-gen-openapiv2: support deprecated field option
## 🚀 Feature

When generating an openapiv2 definition, any [protobuf `deprecated` field options](https://developers.google.com/protocol-buffers/docs/proto3#options) are currently ignored. It would be nice if the generator automatically set the [deprecated field on the resulting parameter object](https://swagger.io/specification/#parameter-object) accordingly.

Note that the deprecated field on [_operation_ objects](https://swagger.io/specification/#operation-object) is already supported, though this is handled through a custom annotation instead of the official `deprecated` option. This was already touched on in #2856, but in my opinion it makes sense to support the official option ([`deprecated = true` can be set on `rpc`, `message`, `enum`, and `field`](https://github.com/protocolbuffers/protobuf/issues/1734#issuecomment-234681729)) wherever possible instead of requiring the use of custom annotations.

Discussion:
@johanbrandhorst: Hi, thanks for your issue! I'm surprised we didn't already have an issue for this, I agree that it makes sense to automatically apply this. Furthermore, if both an annotation and a protobuf level option is set, the annotation should take precedence (so that it's always possible to change the output of the openapi generator without changing the protobuf definition).@denisdubovitskiy: @johanbrandhorst i think that this annotation should also add some context value to indicate that the method is deprecated. Having this, we can use an interceptor to collect a metric and notify clients about new implementation. What do you think?@johanbrandhorst: I don't think that should be necessary - with APIv2 it should be possible to tell already through reflection whether a method is deprecated. I would consider this ask outside the scope of the gateway.@ankur-kalita: can you please assign this issue to me ?
@johanbrandhorst: Please just submit a PR if you want to work on this

Solution patch:
diff --git a/.github/Dockerfile b/.github/Dockerfile
index 067743df..1aa3bec6 100644
--- a/.github/Dockerfile
+++ b/.github/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.25.3
+FROM golang:1.25.4
 
 RUN apt-get update && \
     DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends \
diff --git a/MODULE.bazel b/MODULE.bazel
index 5f1abfba..645d5f4d 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -15,7 +15,7 @@ bazel_dep(name = "rules_shell", version = "0.6.1")
 bazel_dep(name = "gazelle", version = "0.46.0", repo_name = "bazel_gazelle")
 bazel_dep(name = "bazel_skylib", version = "1.8.2")
 bazel_dep(name = "protobuf", version = "33.0", repo_name = "com_google_protobuf")
-bazel_dep(name = "googleapis", version = "0.0.0-20251003-2193a2bf")
+bazel_dep(name = "googleapis", version = "0.0.0-20251104-53af3b72")
 
 # This is required as a transitive dependency and not directly needed by this module.
 # We have this version pinned to solve for differences in the MODULE.bazel.lock file
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index bbba4ad3..f25546ea 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -61,8 +61,8 @@
     "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
     "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/MODULE.bazel": "97c6a4d413b373d4cc97065da3de1b2166e22cbbb5f4cc9f05760bfa83619e24",
     "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/source.json": "cf611c836a60e98e2e2ab2de8004f119e9f06878dcf4ea2d95a437b1b7a89fe9",
-    "https://bcr.bazel.build/modules/googleapis/0.0.0-20251003-2193a2bf/MODULE.bazel": "cc9e5ed294ed9ebf42cdbbdddd2df29048519e3797004df1e3f369f31ff4f2d4",
-    "https://bcr.bazel.build/modules/googleapis/0.0.0-20251003-2193a2bf/source.json": "21558a194c519e27262cca9cf031bb166a666a8b7fb89993b700c828f8dc0857",
+    "https://bcr.bazel.build/modules/googleapis/0.0.0-20251104-53af3b72/MODULE.bazel": "6b300b194dc6ee47cb076256e4f75e2844902806f6be419967cb68bb3da508e3",
+    "https://bcr.bazel.build/modules/googleapis/0.0.0-20251104-53af3b72/source.json": "f017d97017b5f9b07cb44a7d9a118ba40e691080347c434bea6c2f1f3fe3a8e2",
     "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
     "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
     "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
diff --git a/WORKSPACE b/WORKSPACE
index 25f6c629..112f732b 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -42,10 +42,10 @@ http_archive(
 
 http_archive(
     name = "googleapis",
-    sha256 = "54fe5e0a572ce4e925365f9b800e5ba19518d3d4eecc927f600fb9daec739972",
-    strip_prefix = "googleapis-53af3b727f84acc34e31c23f3b6e7b8aa4b7e837",
+    sha256 = "341e4345daa794c9656a276164dd2f09d2e67c25d4fbf16e01b41bc90a76cad5",
+    strip_prefix = "googleapis-8cc2927c2ea64e6876568683050deabf2b52ce56",
     urls = [
-        "https://github.com/googleapis/googleapis/archive/53af3b727f84acc34e31c23f3b6e7b8aa4b7e837.zip",
+        "https://github.com/googleapis/googleapis/archive/8cc2927c2ea64e6876568683050deabf2b52ce56.zip",
     ],
 )
 
diff --git a/docs/docs/mapping/customizing_openapi_output.md b/docs/docs/mapping/customizing_openapi_output.md
index 1ff35226..3a079ef0 100644
--- a/docs/docs/mapping/customizing_openapi_output.md
+++ b/docs/docs/mapping/customizing_openapi_output.md
@@ -1114,6 +1114,88 @@ definitions:
         type: string
 ```
 
+### Deprecating parameters
 
-{% endraw %}
+With the `enable_field_deprecation` option, OpenAPI parameters will be marked as deprecated based on the protobuf `deprecated` field option. Allowed values are: `true`, `false`.
+
+For example, if you are using `buf`:
 
+```yaml
+version: v1
+plugins:
+  - name: openapiv2
+    out: .
+    opt:
+      - enable_field_deprecation=true
+```
+
+or with `protoc`
+
+```sh
+protoc --openapiv2_out=. --openapiv2_opt=enable_field_deprecation=true ./path/to/file.proto
+```
+
+Input example:
+
+```protobuf
+message SearchRequest {
+  string legacy_filter = 1 [deprecated = true];
+  string query = 2;
+}
+```
+
+Output (excerpt):
+
+```yaml
+paths:
+  /v1/search:
+    get:
+      parameters:
+        - name: legacy_filter
+          in: query
+          required: false
+          type: string
+          deprecated: true
+        - name: query
+          in: query
+          required: false
+          type: string
+```
+
+If you prefer to leave the protobuf definition active, you can use the `field_configuration.deprecated` annotation on the `openapiv2_field` option instead:
+
+```protobuf
+message SearchRequest {
+  string legacy_filter = 1 [
+    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+      description: "Legacy filter syntax. Prefer the new 'query' field."
+      field_configuration: { deprecated: true }
+    }
+  ];
+  string query = 2;
+}
+```
+
+This keeps the protobuf field untouched but still emits a deprecated parameter in the generated spec:
+
+```yaml
+paths:
+  /v1/search:
+    get:
+      parameters:
+        - name: legacy_filter
+          in: query
+          required: false
+          type: string
+          description: Legacy filter syntax. Prefer the new 'query' field.
+          deprecated: true
+        - name: query
+          in: query
+          required: false
+          type: string
+```
+
+If you set both the protobuf `deprecated = true` option and `field_configuration.deprecated`, the OpenAPI parameter is marked as deprecated regardless of the `enable_field_deprecation` option.
+
+
+{% endraw %}
diff --git a/internal/descriptor/registry.go b/internal/descriptor/registry.go
index 743cea8f..d2b71500 100644
--- a/internal/descriptor/registry.go
+++ b/internal/descriptor/registry.go
@@ -167,6 +167,9 @@ type Registry struct {
 	// enableRpcDeprecation whether to process grpc method's deprecated option
 	enableRpcDeprecation bool
 
+	// enableFieldDeprecation whether to process proto field's deprecated option
+	enableFieldDeprecation bool
+
 	// expandSlashedPathPatterns, if true, for a path parameter carrying a sub-path, described via parameter pattern (i.e.
 	// the pattern contains forward slashes), this will expand the _pattern_ into the URI and will _replace_ the parameter
 	// with new path parameters inferred from patterns wildcards.
@@ -915,6 +918,16 @@ func (r *Registry) GetEnableRpcDeprecation() bool {
 	return r.enableRpcDeprecation
 }
 
+// SetEnableFieldDeprecation sets enableFieldDeprecation
+func (r *Registry) SetEnableFieldDeprecation(enable bool) {
+	r.enableFieldDeprecation = enable
+}
+
+// GetEnableFieldDeprecation returns enableFieldDeprecation
+func (r *Registry) GetEnableFieldDeprecation() bool {
+	return r.enableFieldDeprecation
+}
+
 func (r *Registry) SetExpandSlashedPathPatterns(expandSlashedPathPatterns bool) {
 	r.expandSlashedPathPatterns = expandSlashedPathPatterns
 }
diff --git a/protoc-gen-openapiv2/defs.bzl b/protoc-gen-openapiv2/defs.bzl
index 29e548c0..b25736c4 100644
--- a/protoc-gen-openapiv2/defs.bzl
+++ b/protoc-gen-openapiv2/defs.bzl
@@ -76,6 +76,7 @@ def _run_proto_gen_openapi(
         use_allof_for_refs,
         disable_default_responses,
         enable_rpc_deprecation,
+        enable_field_deprecation,
         expand_slashed_path_patterns,
         preserve_rpc_order,
         generate_x_go_type):
@@ -155,6 +156,9 @@ def _run_proto_gen_openapi(
     if enable_rpc_deprecation:
         args.add("--openapiv2_opt", "enable_rpc_deprecation=true")
 
+    if enable_field_deprecation:
+        args.add("--openapiv2_opt", "enable_field_deprecation=true")
+
     if expand_slashed_path_patterns:
         args.add("--openapiv2_opt", "expand_slashed_path_patterns=true")
 
@@ -271,6 +275,7 @@ def _proto_gen_openapi_impl(ctx):
                     use_allof_for_refs = ctx.attr.use_allof_for_refs,
                     disable_default_responses = ctx.attr.disable_default_responses,
                     enable_rpc_deprecation = ctx.attr.enable_rpc_deprecation,
+                    enable_field_deprecation = ctx.attr.enable_field_deprecation,
                     expand_slashed_path_patterns = ctx.attr.expand_slashed_path_patterns,
                     preserve_rpc_order = ctx.attr.preserve_rpc_order,
                     generate_x_go_type = ctx.attr.generate_x_go_type,
@@ -434,6 +439,11 @@ protoc_gen_openapiv2 = rule(
             mandatory = False,
             doc = "whether to process grpc method's deprecated option.",
         ),
+        "enable_field_deprecation": attr.bool(
+            default = False,
+            mandatory = False,
+            doc = "whether to process proto field's deprecated option.",
+        ),
         "expand_slashed_path_patterns": attr.bool(
             default = False,
             mandatory = False,
diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go
index 6265da8f..e1d9ffba 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/template.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/template.go
@@ -345,6 +345,11 @@ func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, pre
 			}
 		}
 
+		// verify if the field is deprecated, either via proto or annotation
+		protoDeprecated := field.GetOptions().GetDeprecated() && reg.GetEnableFieldDeprecation()
+		annotationDeprecated := getFieldConfiguration(reg, field).GetDeprecated()
+		deprecated := protoDeprecated || annotationDeprecated
+
 		param := openapiParameterObject{
 			Description: desc,
 			In:          "query",
@@ -354,6 +359,7 @@ func nestedQueryParams(message *descriptor.Message, field *descriptor.Field, pre
 			Format:      schema.Format,
 			Pattern:     schema.Pattern,
 			Required:    required,
+			Deprecated:  deprecated,
 			UniqueItems: schema.UniqueItems,
 			extensions:  schema.extensions,
 			Enum:        schema.Enum,
@@ -1454,18 +1460,26 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r
 					if regExp, ok := pathParamRegexpMap[parameterString]; ok {
 						pattern = regExp
 					}
-					if fc := getFieldConfiguration(reg, parameter.Target); fc != nil {
+					fc := getFieldConfiguration(reg, parameter.Target)
+					if fc != nil {
 						pathParamName := fc.GetPathParamName()
 						if pathParamName != "" && pathParamName != parameterString {
 							pathParamNames["{"+parameterString+"}"] = "{" + pathParamName + "}"
 							parameterString, _, _ = strings.Cut(pathParamName, "=")
 						}
 					}
+
+					// verify if the parameter is deprecated, either via proto or annotation
+					protoDeprecated := parameter.Target.GetOptions().GetDeprecated() && reg.GetEnableFieldDeprecation()
+					annotationDeprecated := fc.GetDeprecated()
+					deprecated := protoDeprecated || annotationDeprecated
+
 					parameters = append(parameters, openapiParameterObject{
 						Name:        parameterString,
 						Description: desc,
 						In:          "path",
 						Required:    true,
+						Deprecated:  deprecated,
 						Default:     defaultValue,
 						// Parameters in gRPC-Gateway can only be strings?
 						Type:             paramType,
diff --git a/protoc-gen-openapiv2/internal/genopenapi/types.go b/protoc-gen-openapiv2/internal/genopenapi/types.go
index db9be4de..ab60dca4 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/types.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/types.go
@@ -149,6 +149,7 @@ type openapiParameterObject struct {
 	Description      string              `json:"description,omitempty" yaml:"description,omitempty"`
 	In               string              `json:"in,omitempty" yaml:"in,omitempty"`
 	Required         bool                `json:"required" yaml:"required"`
+	Deprecated       bool                `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
 	Type             string              `json:"type,omitempty" yaml:"type,omitempty"`
 	Format           string              `json:"format,omitempty" yaml:"format,omitempty"`
 	UniqueItems      bool                `json:"uniqueItems,omitempty" yaml:"uniqueItems,omitempty"`
diff --git a/protoc-gen-openapiv2/main.go b/protoc-gen-openapiv2/main.go
index 82062cc9..f8fa9a31 100644
--- a/protoc-gen-openapiv2/main.go
+++ b/protoc-gen-openapiv2/main.go
@@ -50,6 +50,7 @@ var (
 	allowPatchFeature              = flag.Bool("allow_patch_feature", true, "whether to hide update_mask fields in PATCH requests from the generated swagger file.")
 	preserveRPCOrder               = flag.Bool("preserve_rpc_order", false, "if true, will ensure the order of paths emitted in openapi swagger files mirror the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically.")
 	enableRpcDeprecation           = flag.Bool("enable_rpc_deprecation", false, "whether to process grpc method's deprecated option.")
+	enableFieldDeprecation         = flag.Bool("enable_field_deprecation", false, "whether to process proto field's deprecated option.")
 	expandSlashedPathPatterns      = flag.Bool("expand_slashed_path_patterns", false, "if set, expands path parameters with URI sub-paths into the URI. For example, \"/v1/{name=projects/*}/resource\" becomes \"/v1/projects/{project}/resource\".")
 	useProto3FieldSemantics        = flag.Bool("use_proto3_field_semantics", false, "if set, uses proto3 field semantics for the OpenAPI schema. This means that fields are required by default.")
 	generateXGoType                = flag.Bool("generate_x_go_type", false, "if set, generates x-go-type extension using the go_package option from proto files")
@@ -177,6 +178,7 @@ func main() {
 	reg.SetAllowPatchFeature(*allowPatchFeature)
 	reg.SetPreserveRPCOrder(*preserveRPCOrder)
 	reg.SetEnableRpcDeprecation(*enableRpcDeprecation)
+	reg.SetEnableFieldDeprecation(*enableFieldDeprecation)
 	reg.SetExpandSlashedPathPatterns(*expandSlashedPathPatterns)
 	reg.SetGenerateXGoType(*generateXGoType)
 
diff --git a/protoc-gen-openapiv2/options/openapiv2.pb.go b/protoc-gen-openapiv2/options/openapiv2.pb.go
index 3a34e664..5121dce3 100644
--- a/protoc-gen-openapiv2/options/openapiv2.pb.go
+++ b/protoc-gen-openapiv2/options/openapiv2.pb.go
@@ -3476,6 +3476,9 @@ type JSONSchema_FieldConfiguration struct {
 	// parameter. Use this to avoid having auto generated path parameter names
 	// for overlapping paths.
 	PathParamName string `protobuf:"bytes,47,opt,name=path_param_name,json=pathParamName,proto3" json:"path_param_name,omitempty"`
+	// Declares this field to be deprecated. Allows for the generated OpenAPI
+	// parameter to be marked as deprecated without affecting the proto field.
+	Deprecated    bool `protobuf:"varint,49,opt,name=deprecated,proto3" json:"deprecated,omitempty"`
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
 }
@@ -3512,10 +3515,21 @@ func (x *JSONSchema_FieldConfiguration) GetPathParamName() string {
 	return ""
 }
 
+func (x *JSONSchema_FieldConfiguration) GetDeprecated() bool {
+	if x != nil {
+		return x.Deprecated
+	}
+	return false
+}
+
 func (x *JSONSchema_FieldConfiguration) SetPathParamName(v string) {
 	x.PathParamName = v
 }
 
+func (x *JSONSchema_FieldConfiguration) SetDeprecated(v bool) {
+	x.Deprecated = v
+}
+
 type JSONSchema_FieldConfiguration_builder struct {
 	_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
 
@@ -3524,6 +3538,9 @@ type JSONSchema_FieldConfiguration_builder struct {
 	// parameter. Use this to avoid having auto generated path parameter names
 	// for overlapping paths.
 	PathParamName string
+	// Declares this field to be deprecated. Allows for the generated OpenAPI
+	// parameter to be marked as deprecated without affecting the proto field.
+	Deprecated bool
 }
 
 func (b0 JSONSchema_FieldConfiguration_builder) Build() *JSONSchema_FieldConfiguration {
@@ -3531,6 +3548,7 @@ func (b0 JSONSchema_FieldConfiguration_builder) Build() *JSONSchema_FieldConfigu
 	b, x := &b0, m0
 	_, _ = b, x
 	x.PathParamName = b.PathParamName
+	x.Deprecated = b.Deprecated
 	return m0
 }
 
@@ -3904,7 +3922,7 @@ var file_protoc_gen_openapiv2_options_openapiv2_proto_rawDesc = []byte{
 	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05,
 	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f,
 	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61,
-	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd7,
+	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf7,
 	0x0a, 0x0a, 0x0a, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x10, 0x0a,
 	0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12,
 	0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
@@ -3968,11 +3986,13 @@ var file_protoc_gen_openapiv2_options_openapiv2_proto_rawDesc = []byte{
 	0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f, 0x70,
 	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61,
 	0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
-	0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3c, 0x0a, 0x12,
+	0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x5c, 0x0a, 0x12,
 	0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69,
 	0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d,
 	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x2f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x74,
-	0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x55, 0x0a, 0x0f, 0x45, 0x78,
+	0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65,
+	0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x31, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
+	0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x0f, 0x45, 0x78,
 	0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 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,
 	0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
diff --git a/protoc-gen-openapiv2/options/openapiv2.proto b/protoc-gen-openapiv2/options/openapiv2.proto
index 5313f081..444a5687 100644
--- a/protoc-gen-openapiv2/options/openapiv2.proto
+++ b/protoc-gen-openapiv2/options/openapiv2.proto
@@ -612,6 +612,9 @@ message JSONSchema {
     // parameter. Use this to avoid having auto generated path parameter names
     // for overlapping paths.
     string path_param_name = 47;
+    // Declares this field to be deprecated. Allows for the generated OpenAPI
+    // parameter to be marked as deprecated without affecting the proto field.
+    bool deprecated = 49;
   }
   // Custom properties that start with "x-" such as "x-foo" used to describe
   // extra functionality that is not covered by the standard OpenAPI Specification.
diff --git a/protoc-gen-openapiv2/options/openapiv2_protoopaque.pb.go b/protoc-gen-openapiv2/options/openapiv2_protoopaque.pb.go
index 1f0e0c26..5316ed61 100644
--- a/protoc-gen-openapiv2/options/openapiv2_protoopaque.pb.go
+++ b/protoc-gen-openapiv2/options/openapiv2_protoopaque.pb.go
@@ -3268,6 +3268,7 @@ func (b0 Scopes_builder) Build() *Scopes {
 type JSONSchema_FieldConfiguration struct {
 	state                    protoimpl.MessageState `protogen:"opaque.v1"`
 	xxx_hidden_PathParamName string                 `protobuf:"bytes,47,opt,name=path_param_name,json=pathParamName,proto3" json:"path_param_name,omitempty"`
+	xxx_hidden_Deprecated    bool                   `protobuf:"varint,49,opt,name=deprecated,proto3" json:"deprecated,omitempty"`
 	unknownFields            protoimpl.UnknownFields
 	sizeCache                protoimpl.SizeCache
 }
@@ -3304,10 +3305,21 @@ func (x *JSONSchema_FieldConfiguration) GetPathParamName() string {
 	return ""
 }
 
+func (x *JSONSchema_FieldConfiguration) GetDeprecated() bool {
+	if x != nil {
+		return x.xxx_hidden_Deprecated
+	}
+	return false
+}
+
 func (x *JSONSchema_FieldConfiguration) SetPathParamName(v string) {
 	x.xxx_hidden_PathParamName = v
 }
 
+func (x *JSONSchema_FieldConfiguration) SetDeprecated(v bool) {
+	x.xxx_hidden_Deprecated = v
+}
+
 type JSONSchema_FieldConfiguration_builder struct {
 	_ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
 
@@ -3316,6 +3328,9 @@ type JSONSchema_FieldConfiguration_builder struct {
 	// parameter. Use this to avoid having auto generated path parameter names
 	// for overlapping paths.
 	PathParamName string
+	// Declares this field to be deprecated. Allows for the generated OpenAPI
+	// parameter to be marked as deprecated without affecting the proto field.
+	Deprecated bool
 }
 
 func (b0 JSONSchema_FieldConfiguration_builder) Build() *JSONSchema_FieldConfiguration {
@@ -3323,6 +3338,7 @@ func (b0 JSONSchema_FieldConfiguration_builder) Build() *JSONSchema_FieldConfigu
 	b, x := &b0, m0
 	_, _ = b, x
 	x.xxx_hidden_PathParamName = b.PathParamName
+	x.xxx_hidden_Deprecated = b.Deprecated
 	return m0
 }
 
@@ -3696,7 +3712,7 @@ var file_protoc_gen_openapiv2_options_openapiv2_proto_rawDesc = []byte{
 	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x05,
 	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f,
 	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x56, 0x61,
-	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd7,
+	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf7,
 	0x0a, 0x0a, 0x0a, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x10, 0x0a,
 	0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12,
 	0x14, 0x0a, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
@@ -3760,11 +3776,13 @@ var file_protoc_gen_openapiv2_options_openapiv2_proto_rawDesc = []byte{
 	0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2e, 0x6f, 0x70,
 	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61,
 	0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
-	0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x3c, 0x0a, 0x12,
+	0x52, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x5c, 0x0a, 0x12,
 	0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69,
 	0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d,
 	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x2f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x74,
-	0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x55, 0x0a, 0x0f, 0x45, 0x78,
+	0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65,
+	0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x31, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a,
+	0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x0f, 0x45, 0x78,
 	0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 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,
 	0x2c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,



Test patch:
diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go
index a766a852..a79730ae 100644
--- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go
+++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go
@@ -1326,6 +1326,181 @@ func TestMessageToQueryParametersWithRequiredField(t *testing.T) {
 	}
 }
 
+func TestMessageToQueryParametersWithDeprecatedField(t *testing.T) {
+	annotationOptions := func() *descriptorpb.FieldOptions {
+		opts := &descriptorpb.FieldOptions{}
+		proto.SetExtension(opts, openapi_options.E_Openapiv2Field, &openapi_options.JSONSchema{
+			FieldConfiguration: &openapi_options.JSONSchema_FieldConfiguration{
+				Deprecated: true,
+			},
+		})
+		return opts
+	}
+
+	type test struct {
+		MsgDescs               []*descriptorpb.DescriptorProto
+		Message                string
+		Params                 []openapiParameterObject
+		enableFieldDeprecation bool
+	}
+
+	tests := []test{
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("deprecated_via_proto"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+							Options: &descriptorpb.FieldOptions{
+								Deprecated: proto.Bool(true),
+							},
+						},
+						{
+							Name:    proto.String("deprecated_via_annotation"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(2),
+							Options: annotationOptions(),
+						},
+						{
+							Name:   proto.String("active_field"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(3),
+						},
+					},
+				},
+			},
+			Message:                "ExampleMessage",
+			enableFieldDeprecation: true,
+			Params: []openapiParameterObject{
+				{
+					Name:       "deprecated_via_proto",
+					In:         "query",
+					Required:   false,
+					Type:       "string",
+					Deprecated: true,
+				},
+				{
+					Name:       "deprecated_via_annotation",
+					In:         "query",
+					Required:   false,
+					Type:       "string",
+					Deprecated: true,
+				},
+				{
+					Name:     "active_field",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+			},
+		},
+		{
+			MsgDescs: []*descriptorpb.DescriptorProto{
+				{
+					Name: proto.String("ExampleMessage"),
+					Field: []*descriptorpb.FieldDescriptorProto{
+						{
+							Name:   proto.String("deprecated_via_proto"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(1),
+							Options: &descriptorpb.FieldOptions{
+								Deprecated: proto.Bool(true),
+							},
+						},
+						{
+							Name:    proto.String("deprecated_via_annotation"),
+							Type:    descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number:  proto.Int32(2),
+							Options: annotationOptions(),
+						},
+						{
+							Name:   proto.String("active_field"),
+							Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+							Number: proto.Int32(3),
+						},
+					},
+				},
+			},
+			Message:                "ExampleMessage",
+			enableFieldDeprecation: false,
+			Params: []openapiParameterObject{
+				{
+					Name:       "deprecated_via_proto",
+					In:         "query",
+					Required:   false,
+					Type:       "string",
+					Deprecated: false,
+				},
+				{
+					Name:       "deprecated_via_annotation",
+					In:         "query",
+					Required:   false,
+					Type:       "string",
+					Deprecated: true,
+				},
+				{
+					Name:     "active_field",
+					In:       "query",
+					Required: false,
+					Type:     "string",
+				},
+			},
+		},
+	}
+
+	// Enable or disable proto-field deprecation per test case before loading descriptors.
+	for _, test := range tests {
+		reg := descriptor.NewRegistry()
+		reg.SetEnableFieldDeprecation(test.enableFieldDeprecation)
+		msgs := []*descriptor.Message{}
+		for _, msgdesc := range test.MsgDescs {
+			msgs = append(msgs, &descriptor.Message{DescriptorProto: msgdesc})
+		}
+		file := descriptor.File{
+			FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+				SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+				Name:           proto.String("example.proto"),
+				Package:        proto.String("example"),
+				Dependency:     []string{},
+				MessageType:    test.MsgDescs,
+				Service:        []*descriptorpb.ServiceDescriptorProto{},
+				Options: &descriptorpb.FileOptions{
+					GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+				},
+			},
+			GoPkg: descriptor.GoPackage{
+				Path: "example.com/path/to/example/example.pb",
+				Name: "example_pb",
+			},
+			Messages: msgs,
+		}
+		err := reg.Load(&pluginpb.CodeGeneratorRequest{
+			ProtoFile: []*descriptorpb.FileDescriptorProto{file.FileDescriptorProto},
+		})
+		if err != nil {
+			t.Fatalf("failed to load code generator request: %v", err)
+		}
+
+		message, err := reg.LookupMsg("", ".example."+test.Message)
+		if err != nil {
+			t.Fatalf("failed to lookup message: %s", err)
+		}
+		params, err := messageToQueryParameters(message, reg, []descriptor.Parameter{}, nil, "")
+		if err != nil {
+			t.Fatalf("failed to convert message to query parameters: %s", err)
+		}
+		for i := range params {
+			params[i].Items = nil
+		}
+		if !reflect.DeepEqual(params, test.Params) {
+			t.Errorf("expected %v, got %v", test.Params, params)
+		}
+	}
+}
+
 func TestMessageToQueryParametersWithEnumFieldOption(t *testing.T) {
 	type test struct {
 		MsgDescs []*descriptorpb.DescriptorProto
@@ -11357,6 +11532,178 @@ func TestRenderServicesOptionDeprecated(t *testing.T) {
 	}
 }
 
+func TestRenderServicesMarksDeprecatedParameters(t *testing.T) {
+	cases := []struct {
+		name                   string
+		fieldDeprecated        bool
+		enableFieldDeprecation bool
+		fieldConfigDeprecated  bool
+		expectedDeprecated     bool
+	}{
+		{
+			name:                   "proto field deprecated but feature disabled",
+			fieldDeprecated:        true,
+			enableFieldDeprecation: false,
+			expectedDeprecated:     false,
+		},
+		{
+			name:                   "proto field deprecated with feature enabled",
+			fieldDeprecated:        true,
+			enableFieldDeprecation: true,
+			expectedDeprecated:     true,
+		},
+
+		{
+			name:                  "field config annotation deprecated",
+			fieldConfigDeprecated: true,
+			expectedDeprecated:    true,
+		},
+		{
+			name:               "non-deprecated field",
+			expectedDeprecated: false,
+		},
+	}
+
+	for _, tc := range cases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			var fieldOptions *descriptorpb.FieldOptions
+			if tc.fieldDeprecated || tc.fieldConfigDeprecated {
+				fieldOptions = &descriptorpb.FieldOptions{}
+				if tc.fieldDeprecated {
+					fieldOptions.Deprecated = proto.Bool(true)
+				}
+				if tc.fieldConfigDeprecated {
+					proto.SetExtension(fieldOptions, openapi_options.E_Openapiv2Field, &openapi_options.JSONSchema{
+						FieldConfiguration: &openapi_options.JSONSchema_FieldConfiguration{
+							Deprecated: true,
+						},
+					})
+				}
+			}
+			fieldDesc := &descriptorpb.FieldDescriptorProto{
+				Name:     proto.String("name"),
+				Type:     descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+				Number:   proto.Int32(1),
+				Options:  fieldOptions,
+				JsonName: proto.String("name"),
+			}
+			reqMsgDesc := &descriptorpb.DescriptorProto{
+				Name:  proto.String("ExampleRequest"),
+				Field: []*descriptorpb.FieldDescriptorProto{fieldDesc},
+			}
+			respMsgDesc := &descriptorpb.DescriptorProto{
+				Name: proto.String("ExampleResponse"),
+			}
+
+			reqMsg := &descriptor.Message{
+				DescriptorProto: reqMsgDesc,
+			}
+			respMsg := &descriptor.Message{
+				DescriptorProto: respMsgDesc,
+			}
+			field := &descriptor.Field{
+				FieldDescriptorProto: fieldDesc,
+				Message:              reqMsg,
+			}
+			reqMsg.Fields = []*descriptor.Field{field}
+
+			method := &descriptorpb.MethodDescriptorProto{
+				Name:       proto.String("GetExample"),
+				InputType:  proto.String(".example.ExampleRequest"),
+				OutputType: proto.String(".example.ExampleResponse"),
+			}
+
+			compiler, err := httprule.Parse("/v1/{name}")
+			if err != nil {
+				t.Fatalf("failed to parse path template: %v", err)
+			}
+			pathTemplate := compiler.Compile()
+
+			binding := &descriptor.Binding{
+				HTTPMethod: "GET",
+				PathTmpl:   pathTemplate,
+				PathParams: []descriptor.Parameter{
+					{
+						Target: field,
+						FieldPath: descriptor.FieldPath{
+							{
+								Name:   "name",
+								Target: field,
+							},
+						},
+					},
+				},
+			}
+
+			methodDesc := &descriptor.Method{
+				MethodDescriptorProto: method,
+				RequestType:           reqMsg,
+				ResponseType:          respMsg,
+				Bindings:              []*descriptor.Binding{binding},
+			}
+
+			service := &descriptorpb.ServiceDescriptorProto{
+				Name:   proto.String("ExampleService"),
+				Method: []*descriptorpb.MethodDescriptorProto{method},
+			}
+
+			serviceDesc := &descriptor.Service{
+				ServiceDescriptorProto: service,
+				Methods:                []*descriptor.Method{methodDesc},
+			}
+
+			file := descriptor.File{
+				FileDescriptorProto: &descriptorpb.FileDescriptorProto{
+					SourceCodeInfo: &descriptorpb.SourceCodeInfo{},
+					Name:           proto.String("example.proto"),
+					Package:        proto.String("example"),
+					MessageType:    []*descriptorpb.DescriptorProto{reqMsgDesc, respMsgDesc},
+					Service:        []*descriptorpb.ServiceDescriptorProto{service},
+					Options: &descriptorpb.FileOptions{
+						GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"),
+					},
+				},
+				GoPkg: descriptor.GoPackage{
+					Path: "example.com/path/to/example/example.pb",
+					Name: "example_pb",
+				},
+				Messages: []*descriptor.Message{reqMsg, respMsg},
+				Services: []*descriptor.Service{serviceDesc},
+			}
+
+			reg := descriptor.NewRegistry()
+			reg.SetEnableFieldDeprecation(tc.enableFieldDeprecation)
+			fileCL := crossLinkFixture(&file)
+			if err := reg.Load(reqFromFile(fileCL)); err != nil {
+				t.Fatalf("failed to load code generator request: %v", err)
+			}
+
+			result, err := applyTemplate(param{File: fileCL, reg: reg})
+			if err != nil {
+				t.Fatalf("applyTemplate(%#v) failed with %v; want success", file, err)
+			}
+
+			operation := result.getPathItemObject("/v1/{name}").Get
+			if operation == nil {
+				t.Fatalf("expected GET operation to be generated for /v1/{name}")
+			}
+			found := false
+			for _, param := range operation.Parameters {
+				if param.In == "path" && param.Name == "name" {
+					found = true
+					if param.Deprecated != tc.expectedDeprecated {
+						t.Fatalf("expected deprecated flag to be %v, got %v", tc.expectedDeprecated, param.Deprecated)
+					}
+				}
+			}
+			if !found {
+				t.Fatalf("expected to find path parameter named 'name'")
+			}
+		})
+	}
+}
+
 func Test_updateSwaggerObjectFromFieldBehavior(t *testing.T) {
 	type args struct {
 		s     *openapiSchemaObject