Repository: openapi-ts/openapi-typescript (2025-06-29T15:48:15Z)

Issue:
Parameters storage is not fulfilled by parameters provided in ApiOperation
https://github.com/openapi-ts/openapi-typescript/blob/99c346e06bb41511338d2a8c3752748e5d52a742/packages/openapi-metadata/src/decorators/api-operation.ts#L13

thus the parameters are not generated when specified

```typescript
 @ApiOperation({
    summary: 'Gets user by id',
    methods: ['get'],
    path: '/users/{id}',
    parameters: [
      {
        name: 'id',
        in: 'path',
        required: true,
        example: 1,
        schema: {
          type: 'integer',
          minimum: 1
        }
      }
    ]
  })
  show(id: string): User {
    return {
      id,
      name: '',
      email: ''
    };
  }
```

Actual Result:
```json
{
 "paths": {
    "/users/{id}": {
      "get": {
        "summary": "Gets user by id",
        "parameters": [],
        "responses": {},
        "security": []
      }
    }
  }
}
```

Expected Result: 
```json
{
{
 "paths": {
    "/users/{id}": {
      "get": {
        "summary": "Gets user by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "example": 1,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {},
        "security": []
      }
    }
  }
}
```


Discussion:

Patch:
diff --git a/.changeset/itchy-jobs-create.md b/.changeset/itchy-jobs-create.md
new file mode 100644
index 000000000..2b9fe311c
--- /dev/null
+++ b/.changeset/itchy-jobs-create.md
@@ -0,0 +1,5 @@
+---
+"openapi-metadata": patch
+---
+
+fix(#2364) for allowing to add params via ApiOperation tag
diff --git a/packages/openapi-metadata/src/decorators/api-operation.ts b/packages/openapi-metadata/src/decorators/api-operation.ts
index 99d210fc8..58995aa14 100644
--- a/packages/openapi-metadata/src/decorators/api-operation.ts
+++ b/packages/openapi-metadata/src/decorators/api-operation.ts
@@ -1,4 +1,8 @@
-import { type OperationMetadata, OperationMetadataStorage } from "../metadata/operation.js";
+import {
+  type OperationMetadata,
+  OperationMetadataStorage,
+  OperationParameterMetadataStorage,
+} from "../metadata/index.js";
 
 export type ApiOperationOptions = OperationMetadata;
 
@@ -11,5 +15,8 @@ export type ApiOperationOptions = OperationMetadata;
 export function ApiOperation(options: ApiOperationOptions): MethodDecorator {
   return (target, propertyKey) => {
     OperationMetadataStorage.defineMetadata(target, options, propertyKey);
+    if (Array.isArray(options.parameters)) {
+      OperationParameterMetadataStorage.mergeMetadata(target, options.parameters, propertyKey);
+    }
   };
 }
diff --git a/packages/openapi-metadata/src/metadata/operation.ts b/packages/openapi-metadata/src/metadata/operation.ts
index a2aafe188..4363a41b8 100644
--- a/packages/openapi-metadata/src/metadata/operation.ts
+++ b/packages/openapi-metadata/src/metadata/operation.ts
@@ -1,8 +1,9 @@
 import type { OpenAPIV3 } from "openapi-types";
 import type { HttpMethods } from "../types.js";
 import { createMetadataStorage } from "./factory.js";
+import type { OperationParameterMetadata } from "./operation-parameter.js";
 
-export type OperationMetadata = Omit<OpenAPIV3.OperationObject, "responses"> & {
+export type OperationMetadata = Omit<OpenAPIV3.OperationObject, "responses" | "parameters"> & {
   /**
    * Operation path.
    * Can include parameters.
@@ -13,6 +14,11 @@ export type OperationMetadata = Omit<OpenAPIV3.OperationObject, "responses"> & {
    * Available methods for this operation.
    */
   methods?: HttpMethods[];
+
+  /**
+   * Represents metadata about an operation parameter.
+   */
+  parameters?: OperationParameterMetadata[];
 };
 
 export const OperationMetadataKey = Symbol("Operation");


Test patch:
diff --git a/packages/openapi-metadata/test/decorators.test.ts b/packages/openapi-metadata/test/decorators.test.ts
index b193be263..87633bb82 100644
--- a/packages/openapi-metadata/test/decorators.test.ts
+++ b/packages/openapi-metadata/test/decorators.test.ts
@@ -1,11 +1,15 @@
 import "reflect-metadata";
 import {
+  ApiBasicAuth,
+  ApiBearerAuth,
   ApiBody,
   ApiCookie,
+  ApiCookieAuth,
   ApiExcludeController,
   ApiExcludeOperation,
   ApiExtraModels,
   ApiHeader,
+  ApiOauth2,
   ApiOperation,
   ApiParam,
   ApiProperty,
@@ -19,26 +23,40 @@ import {
   ExtraModelsMetadataStorage,
   OperationBodyMetadataStorage,
   OperationMetadataStorage,
+  type OperationParameterMetadata,
   OperationParameterMetadataStorage,
   OperationResponseMetadataStorage,
   OperationSecurityMetadataStorage,
   PropertyMetadataStorage,
 } from "../src/metadata/index.js";
-import { ApiBasicAuth, ApiBearerAuth, ApiCookieAuth, ApiOauth2 } from "../src/decorators/api-security.js";
 
 test("@ApiOperation", () => {
+  const parameters: OperationParameterMetadata[] = [
+    {
+      in: "path",
+      name: "id",
+    },
+  ] as const;
+
   class MyController {
-    @ApiOperation({ summary: "Hello", path: "/test", methods: ["get"] })
+    @ApiOperation({
+      summary: "Hello",
+      path: "/test",
+      methods: ["get"],
+      parameters,
+    })
     operation() {}
   }
 
-  const metadata = OperationMetadataStorage.getMetadata(MyController.prototype, "operation");
-
-  expect(metadata).toEqual({
+  const operationMetadata = OperationMetadataStorage.getMetadata(MyController.prototype, "operation");
+  const parameterMetadata = OperationParameterMetadataStorage.getMetadata(MyController.prototype, "operation");
+  expect(operationMetadata).toEqual({
     summary: "Hello",
     path: "/test",
     methods: ["get"],
+    parameters,
   });
+  expect(parameterMetadata).toEqual(parameters);
 });
 
 test("@ApiBody", () => {