diff --git a/docs/en/docs/how-to/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md
index 884c8ed04..5c283fe9d 100644
--- a/docs/en/docs/how-to/custom-request-and-route.md
+++ b/docs/en/docs/how-to/custom-request-and-route.md
@@ -107,3 +107,41 @@ You can also set the `route_class` parameter of an `APIRouter`:
In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
+
+## Experimental: HTTP QUERY method { #experimental-http-query-method }
+
+/// warning
+
+This is an experimental feature for the non-standard HTTP QUERY method. Use with caution.
+
+///
+
+FastAPI and `APIRouter` expose a `.query()` decorator for the experimental HTTP QUERY method, as defined in the IETF draft for "safe method with body".
+
+The QUERY method works at runtime and can be used like any other HTTP method decorator:
+
+```python
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+class SearchQuery(BaseModel):
+ text: str
+ limit: int = 10
+
+@app.query("/search")
+def search_items(query: SearchQuery):
+ return {"results": f"Searching for: {query.text}"}
+```
+
+### Limitations { #query-method-limitations }
+
+* **Not shown in interactive docs**: The QUERY method will not appear in the OpenAPI schema or interactive documentation (Swagger UI, ReDoc) because the OpenAPI specification does not define "query" operations.
+* **Limited client support**: Not all HTTP clients and proxies support the QUERY method.
+
+/// tip
+
+For maximum interoperability, prefer using **POST** for operations that require a request body. The QUERY method is only useful in specialized scenarios where you need to follow the IETF draft specification.
+
+///
diff --git a/docs/es/docs/how-to/custom-request-and-route.md b/docs/es/docs/how-to/custom-request-and-route.md
index a6ea657d1..896a913a5 100644
--- a/docs/es/docs/how-to/custom-request-and-route.md
+++ b/docs/es/docs/how-to/custom-request-and-route.md
@@ -107,3 +107,41 @@ También puedes establecer el parámetro `route_class` de un `APIRouter`:
En este ejemplo, las *path operations* bajo el `router` usarán la clase personalizada `TimedRoute`, y tendrán un header `X-Response-Time` extra en el response con el tiempo que tomó generar el response:
{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *}
+
+## Experimental: Método HTTP QUERY { #experimental-http-query-method }
+
+/// warning | Advertencia
+
+Esta es una funcionalidad experimental para el método HTTP QUERY no estándar. Úsalo con precaución.
+
+///
+
+FastAPI y `APIRouter` exponen un decorador `.query()` para el método HTTP QUERY experimental, tal como se define en el borrador del IETF para "método seguro con cuerpo".
+
+El método QUERY funciona en tiempo de ejecución y puede usarse como cualquier otro decorador de método HTTP:
+
+```python
+from fastapi import FastAPI
+from pydantic import BaseModel
+
+app = FastAPI()
+
+class SearchQuery(BaseModel):
+ text: str
+ limit: int = 10
+
+@app.query("/search")
+def search_items(query: SearchQuery):
+ return {"results": f"Searching for: {query.text}"}
+```
+
+### Limitaciones { #query-method-limitations }
+
+* **No se muestra en la documentación interactiva**: El método QUERY no aparecerá en el esquema OpenAPI ni en la documentación interactiva (Swagger UI, ReDoc) porque la especificación OpenAPI no define operaciones "query".
+* **Soporte limitado de clientes**: No todos los clientes HTTP y proxies soportan el método QUERY.
+
+/// tip | Consejo
+
+Para máxima interoperabilidad, prefiere usar **POST** para operaciones que requieren un cuerpo de request. El método QUERY solo es útil en escenarios especializados donde necesitas seguir la especificación del borrador del IETF.
+
+///
diff --git a/fastapi/applications.py b/fastapi/applications.py
index 02193312b..49d393cef 100644
--- a/fastapi/applications.py
+++ b/fastapi/applications.py
@@ -4541,6 +4541,17 @@ class FastAPI(Starlette):
generate_unique_id_function=generate_unique_id_function,
)
+ def query(
+ self,
+ path: str,
+ *args: Any,
+ **kwargs: Any,
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
+ """
+ Experimental: HTTP QUERY method. See APIRouter.query for caveats.
+ """
+ return self.router.query(path, *args, **kwargs)
+
def websocket_route(
self, path: str, name: Union[str, None] = None
) -> Callable[[DecoratedCallable], DecoratedCallable]:
diff --git a/fastapi/routing.py b/fastapi/routing.py
index 9be2b44bc..59065a112 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -4538,6 +4538,66 @@ class APIRouter(routing.Router):
generate_unique_id_function=generate_unique_id_function,
)
+ def query(
+ self,
+ path: str,
+ *,
+ response_model: Any = Default(None),
+ status_code: Optional[int] = None,
+ tags: Optional[List[Union[str, Enum]]] = None,
+ dependencies: Optional[Sequence[params.Depends]] = None,
+ summary: Optional[str] = None,
+ description: Optional[str] = None,
+ response_description: str = "Successful Response",
+ responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
+ deprecated: Optional[bool] = None,
+ operation_id: Optional[str] = None,
+ response_model_include: Optional[IncEx] = None,
+ response_model_exclude: Optional[IncEx] = None,
+ response_model_by_alias: bool = True,
+ response_model_exclude_unset: bool = False,
+ response_model_exclude_defaults: bool = False,
+ response_model_exclude_none: bool = False,
+ include_in_schema: bool = False,
+ response_class: Type[Response] = Default(JSONResponse),
+ name: Optional[str] = None,
+ callbacks: Optional[List[BaseRoute]] = None,
+ openapi_extra: Optional[Dict[str, Any]] = None,
+ generate_unique_id_function: Callable[[APIRoute], str] = Default(
+ generate_unique_id
+ ),
+ ) -> Callable[[DecoratedCallable], DecoratedCallable]:
+ """
+ Experimental: register a handler for the non-standard HTTP QUERY method.
+ Works at runtime, but not represented in OpenAPI (spec doesn't support it).
+ """
+ return self.api_route(
+ path=path,
+ response_model=response_model,
+ status_code=status_code,
+ tags=tags,
+ dependencies=dependencies,
+ summary=summary,
+ description=description,
+ response_description=response_description,
+ responses=responses,
+ deprecated=deprecated,
+ methods=["QUERY"],
+ operation_id=operation_id,
+ response_model_include=response_model_include,
+ response_model_exclude=response_model_exclude,
+ response_model_by_alias=response_model_by_alias,
+ response_model_exclude_unset=response_model_exclude_unset,
+ response_model_exclude_defaults=response_model_exclude_defaults,
+ response_model_exclude_none=response_model_exclude_none,
+ include_in_schema=include_in_schema,
+ response_class=response_class,
+ name=name,
+ callbacks=callbacks,
+ openapi_extra=openapi_extra,
+ generate_unique_id_function=generate_unique_id_function,
+ )
+
@deprecated(
"""
on_event is deprecated, use lifespan event handlers instead.
diff --git a/tests/test_query_method.py b/tests/test_query_method.py
new file mode 100644
index 000000000..e83f7f3aa
--- /dev/null
+++ b/tests/test_query_method.py
@@ -0,0 +1,27 @@
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class Payload(BaseModel):
+ x: int
+
+
+def test_query_route_executes_and_openapi_survives():
+ app = FastAPI()
+
+ @app.query("/items")
+ def query_items(payload: Payload):
+ return {"ok": payload.x}
+
+ client = TestClient(app)
+
+ # Runtime: the route is callable with QUERY
+ r = client.request("QUERY", "/items", json={"x": 42})
+ assert r.status_code == 200
+ assert r.json() == {"ok": 42}
+
+ # OpenAPI: does not include the query route (excluded by default), and must not error
+ schema = app.openapi()
+ # The path is excluded from OpenAPI because include_in_schema=False by default
+ assert "/items" not in schema["paths"]