Parameters#
Request parameters are parts of the request that can be injected into a handler function or dependency. They allow type-coercion, validation, and will show up in the generated OpenAPI schema.
There are for request parameter types supported, which can be specified in two different forms:
Type |
Short form |
|
|---|---|---|
query |
||
path |
||
header |
||
cookie |
Parameter declarations#
Each parameter source has two equivalent declaration forms: a marker type alias for the
common case, and an Annotated form for when extra configuration is
needed:
from litestar import get
from litestar.params import FromCookie, FromHeader, FromPath, FromQuery
@get("/{user_id:int}")
async def handler(
user_id: FromPath[int],
limit: FromQuery[int],
token: FromHeader[str],
session: FromCookie[str],
) -> None: ...
from typing_extensions import Annotated
from litestar import get
from litestar.params import (
CookieParameter,
HeaderParameter,
PathParameter,
QueryParameter,
)
@get("/{user_id:int}")
async def handler(
user_id: Annotated[int, PathParameter(ge=1)],
limit: Annotated[int, QueryParameter(gt=0, le=100)],
token: Annotated[str, HeaderParameter(name="X-API-KEY")],
session: Annotated[str, CookieParameter(name="session-id")],
) -> None: ...
from typing import Annotated
from litestar import get
from litestar.params import (
CookieParameter,
HeaderParameter,
PathParameter,
QueryParameter,
)
@get("/{user_id:int}")
async def handler(
user_id: Annotated[int, PathParameter(ge=1)],
limit: Annotated[int, QueryParameter(gt=0, le=100)],
token: Annotated[str, HeaderParameter(name="X-API-KEY")],
session: Annotated[str, CookieParameter(name="session-id")],
) -> None: ...
The two forms are equivalent: FromQuery[T] is just an alias for
Annotated[T, QueryParameter()]. Prefer the marker form when no extra configuration
is needed.
Technical details
These parameters will be parsed from the function signature and used to generate an internal data model. This model in turn will be used to validate the parameters and generate the OpenAPI schema
Optional, required and nullable#
Just like function parameters in Python, request parameters can be required, optional or have a default value.
Parameters declared without any default are required
Parameters declared with a default are optional
Parameters declared with
| Noneare nullable, i.e. the may take aNonevalue; they are still required, unless they are given a default value
Defaults work exactly like defaults in regular function parameters: If the value is missing from the request, the handler is called with the default:
from typing import Dict
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[str] = "hello") -> Dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[str] = "hello") -> dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/
{"param":"hello"}
> curl http://127.0.0.1:8000/?param=john
{"param":"john"}
Marking a parameter optional means the type itself permits None:
from typing import Dict, Optional
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[Optional[str]] = None) -> Dict[str, Optional[str]]:
return {"param": param}
app = Litestar(route_handlers=[index])
from typing import Optional
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[Optional[str]] = None) -> dict[str, Optional[str]]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[str | None] = None) -> dict[str, str | None]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/
{"param":null}
> curl http://127.0.0.1:8000/?param=goodbye
{"param":"goodbye"}
Important
Path parameters are always required because they are part of the URL itself. You can
only get a “missing” path parameter when the same handler is registered against
multiple paths
(e.g. ["/items", "/items/{id:int}"]), in which case the handler-side default
fills in for the path without the slot.
Parameter types#
Path parameters#
Path parameters are declared as part of the path component of
the URL using the syntax {param_name:param_type}. The handler function receives the
value via a parameter of the same name, declared with FromPath:
import dataclasses
from litestar import Litestar, get
from litestar.params import FromPath
@dataclasses.dataclass
class User:
id: int
name: str
USER_DB = {1: User(id=1, name="John Doe")}
@get("/user/{user_id:int}", sync_to_thread=False)
def get_user(user_id: FromPath[int]) -> User:
return USER_DB[user_id]
app = Litestar(route_handlers=[get_user])
There are two components to declaring a path parameter:
In the
@get()decorator, the path component declares both the parameter’s name (user_id) and type (int)In the handler signature,
user_idis declared asFromPath[int], which tells Litestar to inject the matching path value
Two characteristics are unique to path parameters:
The URL slot defines the structurally required type via the
:typesuffix (see Supported path parameter types below). Litestar coerces the captured string into this type before passing it to the handler, and the handler-side type annotation can request a further coercion (see Type coercion).Path parameters cannot be declared on application layers
Tip
You only need to declare the path parameter in the function signature if the handler actually uses it. If the path parameter is part of the path but the function does not consume it, you can omit it from the signature; it will still be validated and added to the OpenAPI schema.
Supported path parameter types#
The following types are supported in the {name:type} slot:
date: Accepts date strings and time stamps.datetime: Accepts date-time strings and time stamps.decimal: Accepts decimal values and floats.float: Accepts ints and floats.int: Accepts ints and floats.path: Accepts valid POSIX paths.str: Accepts all string values.time: Accepts time strings with optional timezone compatible with standard (Pydantic/Msgspec) datetime formats.timedelta: Accepts duration strings compatible with the standard (Pydantic/Msgspec) timedelta formats.uuid: Accepts all uuid values.
Query parameters#
Query parameters are declared as parameters on
the handler function, typed with FromQuery or
Annotated[<type>, QueryParameter()].
from typing import Dict
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[str]) -> Dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(param: FromQuery[str]) -> dict[str, str]:
return {"param": param}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?param=foo
{"param":"foo"}
> curl http://127.0.0.1:8000/?param=bar
{"param":"bar"}
Header parameters#
Header parameters are declared with FromHeader or
Annotated[<type>, HeaderParameter()]. The handler-side parameter name is used as the
header name by default; HTTP headers are matched case-insensitively, so
token: FromHeader[str] matches both Token and token on the wire.
In practice, most headers you care about have names that are not valid Python
identifiers (e.g. X-API-KEY, Content-Type). In those cases, switch to
Annotated and set name= on HeaderParameter:
from typing_extensions import Annotated
from litestar.params import HeaderParameter
async def handler(token: Annotated[str, HeaderParameter(name="X-API-KEY")]) -> None: ...
from typing import Annotated
from litestar.params import HeaderParameter
async def handler(token: Annotated[str, HeaderParameter(name="X-API-KEY")]) -> None: ...
Type coercion#
Every parameter starts life on the wire as a string (or for path parameters, a string captured by the URL slot).
Litestar coerces that raw value into the inner type of the marker (FromQuery[int], FromHeader[float],
FromPath[datetime], …) before passing it to the handler:
from datetime import datetime, timedelta
from typing import Any, Dict, List
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(
date: FromQuery[datetime],
number: FromQuery[int],
floating_number: FromQuery[float],
strings: FromQuery[List[str]],
) -> Dict[str, Any]:
return {
"datetime": date + timedelta(days=1),
"int": number,
"float": floating_number,
"list": strings,
}
app = Litestar(route_handlers=[index])
from datetime import datetime, timedelta
from typing import Any
from litestar import Litestar, get
from litestar.params import FromQuery
@get("/", sync_to_thread=False)
def index(
date: FromQuery[datetime],
number: FromQuery[int],
floating_number: FromQuery[float],
strings: FromQuery[list[str]],
) -> dict[str, Any]:
return {
"datetime": date + timedelta(days=1),
"int": number,
"float": floating_number,
"list": strings,
}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?date=2022-11-28T13:22:06.916540&floating_number=0.1&number=42&strings=1&strings=2
{"datetime":"2022-11-29T13:22:06.916540","int":42,"float":0.1,"list":["1","2"]}
Path parameters additionally support coercion via the path-slot type itself. The handler-side type does not need to
match the slot type one-to-one — if the slot captures an int and the handler asks for a
datetime, Litestar applies the second conversion:
import dataclasses
from datetime import datetime, timezone
from typing import List
from litestar import Litestar, get
from litestar.params import FromPath
@dataclasses.dataclass
class Order:
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=timezone.utc): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: FromPath[datetime]) -> List[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
import dataclasses
from datetime import datetime, timezone
from litestar import Litestar, get
from litestar.params import FromPath
@dataclasses.dataclass
class Order:
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=timezone.utc): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: FromPath[datetime]) -> list[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
import dataclasses
from datetime import datetime, UTC
from litestar import Litestar, get
from litestar.params import FromPath
@dataclasses.dataclass
class Order:
id: int
customer_id: int
ORDERS_BY_DATETIME = {
datetime.fromtimestamp(1667924386, tz=UTC): [
Order(id=1, customer_id=2),
Order(id=2, customer_id=2),
]
}
@get(path="/orders/{from_date:int}", sync_to_thread=False)
def get_orders(from_date: FromPath[datetime]) -> list[Order]:
return ORDERS_BY_DATETIME[from_date]
app = Litestar(route_handlers=[get_orders])
The same conversion rules apply to header and cookie parameters: FromHeader[int] will parse the header value as
an integer, FromCookie[datetime] will parse the cookie as a datetime, and so on.
Aliasing#
By default, the name of the function parameter is used to retrieve the parameter data
from the request. To decouple the two, for example to receive camelCase query keys
while keeping snake_case Python parameter names, or to declare an X--prefixed
header, pass name="..." to the matching specifier class:
from typing import Dict
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.params import QueryParameter
@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, QueryParameter(name="camelCase")]) -> Dict[str, str]:
return {"param": snake_case}
app = Litestar(route_handlers=[index])
from typing import Annotated
from litestar import Litestar, get
from litestar.params import QueryParameter
@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, QueryParameter(name="camelCase")]) -> dict[str, str]:
return {"param": snake_case}
app = Litestar(route_handlers=[index])
Run it
> curl http://127.0.0.1:8000/?camelCase=foo
{"param":"foo"}
A request to http://127.0.0.1:8000?camelCase=foo will be received as
snake_case="foo" inside the handler.
The same pattern applies to
HeaderParameter,:class:~.params.CookieParameter and
PathParameter.
Validation constraints#
All parameters can be given certain validation constraints, that will be validated when a request is received, and represented in the OpenAPI schema.
Currently supported constraints are: gt, ge, lt, le, multiple_of,
min_length, max_length, min_items, max_items, and pattern. A
value that does not satisfy the constraint raises
ValidationException:
from typing import Dict
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.params import QueryParameter
@get("/", sync_to_thread=False)
def index(param: Annotated[int, QueryParameter(gt=5)]) -> Dict[str, int]:
return {"param": param}
app = Litestar(route_handlers=[index])
from typing import Annotated
from litestar import Litestar, get
from litestar.params import QueryParameter
@get("/", sync_to_thread=False)
def index(param: Annotated[int, QueryParameter(gt=5)]) -> dict[str, int]:
return {"param": param}
app = Litestar(route_handlers=[index])
OpenAPI metadata#
Parameters can extend or alter their OpenAPI schema, e.g. to customize their title, add a description or examples:
import dataclasses
from typing import Any, Dict
from typing_extensions import Annotated
from litestar import Litestar, get
from litestar.openapi.spec.example import Example
from litestar.openapi.spec.external_documentation import ExternalDocumentation
from litestar.params import PathParameter
@dataclasses.dataclass
class Version:
id: int
specs: Dict[str, Any]
VERSIONS = {1: Version(id=1, specs={"some": "value"})}
@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
version: Annotated[
int,
PathParameter(
title="Available Product Versions",
description="Get a specific version spec from the available specs",
examples=[Example(value=1)],
external_docs=ExternalDocumentation(
url="https://mywebsite.com/documentation/product#versions", # type: ignore[arg-type]
),
),
],
) -> Version:
return VERSIONS[version]
app = Litestar(route_handlers=[get_product_version])
import dataclasses
from typing import Any
from typing import Annotated
from litestar import Litestar, get
from litestar.openapi.spec.example import Example
from litestar.openapi.spec.external_documentation import ExternalDocumentation
from litestar.params import PathParameter
@dataclasses.dataclass
class Version:
id: int
specs: dict[str, Any]
VERSIONS = {1: Version(id=1, specs={"some": "value"})}
@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
version: Annotated[
int,
PathParameter(
title="Available Product Versions",
description="Get a specific version spec from the available specs",
examples=[Example(value=1)],
external_docs=ExternalDocumentation(
url="https://mywebsite.com/documentation/product#versions", # type: ignore[arg-type]
),
),
],
) -> Version:
return VERSIONS[version]
app = Litestar(route_handlers=[get_product_version])
Customizing enum schemas#
By default, the OpenAPI schema generated for an enum-typed parameter uses the enum’s
docstring for the description section of the schema. Overriding the description via
description would change it for every parameter sharing
that enum, because only one schema is generated per enum. To get distinct descriptions,
also pass schema_component_key so a separate schema
component is generated per parameter:
from enum import Enum
from typing import Annotated
from litestar import Litestar, get
from litestar.params import FromQuery, QueryParameter
class MyEnum(str, Enum):
"""My enum accepts two values"""
A = "a"
B = "b"
@get("/")
async def index(
q1: Annotated[MyEnum, QueryParameter(description="This is q1", schema_component_key="q1")],
q2: FromQuery[MyEnum],
q3: Annotated[MyEnum, QueryParameter(description="This is q3", schema_component_key="q3")],
) -> None: ...
app = Litestar([index])
from enum import StrEnum
from typing import Annotated
from litestar import Litestar, get
from litestar.params import FromQuery, QueryParameter
class MyEnum(StrEnum):
"""My enum accepts two values"""
A = "a"
B = "b"
@get("/")
async def index(
q1: Annotated[MyEnum, QueryParameter(description="This is q1", schema_component_key="q1")],
q2: FromQuery[MyEnum],
q3: Annotated[MyEnum, QueryParameter(description="This is q3", schema_component_key="q3")],
) -> None: ...
app = Litestar([index])
In the above example, the schema for q1 references a “q1” schema component with
description “This is q1”; the schema for q2 references the shared “MyEnum” component
with description “My enum accepts two values”; and the schema for q3 references a
“q3” component with description “This is q3”.
Without the schema_component_key arguments on
q1 and q3, all three would share the same “MyEnum” component with description
“This is q1” — whichever description was processed first wins.
Layered Parameters#
As part of Litestar’s layered architecture, you can declare parameters not only on individual route handler functions, but also on other layers of the application:
from typing import Dict, Union
from typing_extensions import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import (
CookieParameter,
FromHeader,
FromPath,
FromQuery,
HeaderParameter,
QueryParameter,
)
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": QueryParameter(annotation=int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: FromPath[int],
local_param: FromQuery[str],
router_param: FromHeader[str],
controller_param: Annotated[int, QueryParameter(lt=50)],
) -> Dict[str, Union[str, int]]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": HeaderParameter(annotation=str, name="MyHeader", pattern="^[a-zA-Z]$", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": CookieParameter(annotation=str, name="special-cookie", required=False),
},
)
from typing import Union
from typing import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import (
CookieParameter,
FromHeader,
FromPath,
FromQuery,
HeaderParameter,
QueryParameter,
)
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": QueryParameter(annotation=int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: FromPath[int],
local_param: FromQuery[str],
router_param: FromHeader[str],
controller_param: Annotated[int, QueryParameter(lt=50)],
) -> dict[str, Union[str, int]]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": HeaderParameter(annotation=str, name="MyHeader", pattern="^[a-zA-Z]$", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": CookieParameter(annotation=str, name="special-cookie", required=False),
},
)
from typing import Annotated
from litestar import Controller, Litestar, Router, get
from litestar.params import (
CookieParameter,
FromHeader,
FromPath,
FromQuery,
HeaderParameter,
QueryParameter,
)
class MyController(Controller):
path = "/controller"
parameters = {
"controller_param": QueryParameter(annotation=int, lt=100),
}
@get("/{path_param:int}", sync_to_thread=False)
def my_handler(
self,
path_param: FromPath[int],
local_param: FromQuery[str],
router_param: FromHeader[str],
controller_param: Annotated[int, QueryParameter(lt=50)],
) -> dict[str, str | int]:
return {
"path_param": path_param,
"local_param": local_param,
"router_param": router_param,
"controller_param": controller_param,
}
router = Router(
path="/router",
route_handlers=[MyController],
parameters={
"router_param": HeaderParameter(annotation=str, name="MyHeader", pattern="^[a-zA-Z]$", required=False),
},
)
app = Litestar(
route_handlers=[router],
parameters={
"app_param": CookieParameter(annotation=str, name="special-cookie", required=False),
},
)
In the above we declare parameters on the Litestar app,
router, and controller layers in addition to those
declared in the route handler. Examine these more closely:
app_paramis a cookie parameter with the keyspecial-cookie, declared viaCookieParameteron theLitestar appwithannotation=str.required=Falsemakes it optional; without that argument it would be required by default.Because the route handler function does not declare
app_paramat all, the parameter is still extracted and validated at the application level even though the handler never sees it.router_paramis a header parameter with the keyMyHeader, declared viaHeaderParameteron the router. It is declaredrequired=Falseon the router, so it does not fail validation if absent — unless the handler explicitly opts in by re-declaring it (as this one does).The handler types it as
FromHeader[str](rather thanstr | None), making it required at the handler level. If a value is provided, it is also tested against the router-declared regex.controller_paramis a query parameter with the keycontroller_param, declared viaQueryParameteron the controller withlt=100(value must be less than 100). The handler redeclares it withAnnotated[int, QueryParameter(lt=50)], tightening the constraint to less than 50 for this particular route.local_paramis a route-handler-local query parameter (FromQuery[str]), andpath_paramis a path parameter (FromPath[int]).
Deprecated declaration styles#
Earlier 2.x releases accepted several other ways of declaring handler parameters. These continue to work for the
remainder of the 2.x line, but they now emit a LitestarDeprecationWarning and will be removed
in 3.0.
Deprecated |
Use instead |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
The Parameter() function itself is not removed and can still be used to attach pure metadata
(description, gt, etc.) when wrapped in Annotated. Only its header, cookie, and
query keyword arguments are deprecated — passing any of those emits a DeprecationWarning independently of
the handler-level warning.