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

Annotated Form

query

params.FromQuery

Annotated[<type>, QueryParameter()]

path

params.FromPath

Annotated[<type>, PathParameter()]

header

params.FromHeader

Annotated[<type>, HeaderParameter()]

cookie

params.FromCookie

Annotated[<type>, CookieParameter()]

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 | None are nullable, i.e. the may take a None value; 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:

Defining a default value for a query parameter#
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])
Defining a default value for a query parameter#
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:

Defining an optional query parameter#
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])
Defining an optional query parameter#
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])
Defining an optional query parameter#
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:

Defining a path parameter in a route handler#
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:

  1. In the @get() decorator, the path component declares both the parameter’s name (user_id) and type (int)

  2. In the handler signature, user_id is declared as FromPath[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 :type suffix (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()].

Defining a query parameter in a route handler#
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])
Defining a query parameter in a route handler#
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:

Coercing query parameters into different types#
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])
Coercing query parameters into different types#
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:

Coercing a path parameter into a different type#
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])
Coercing a path parameter into a different type#
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])
Coercing a path parameter into a different type#
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:

Remapping a query parameter to a different URL name#
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])
Remapping a query parameter to a different URL name#
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:

Constraining a query parameter to integers larger than 5#
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])
Constraining a query parameter to integers larger than 5#
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:

Adding OpenAPI metadata to a path parameter#
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])
Adding OpenAPI metadata to a path parameter#
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:

Distinct OpenAPI components for parameters sharing the same enum type#
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])
Distinct OpenAPI components for parameters sharing the same enum type#
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:

Declaring parameters on different 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),
    },
)
Declaring parameters on different layers of the application#
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),
    },
)
Declaring parameters on different layers of the application#
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_param is a cookie parameter with the key special-cookie, declared via CookieParameter on the Litestar app with annotation=str. required=False makes it optional; without that argument it would be required by default.

    Because the route handler function does not declare app_param at all, the parameter is still extracted and validated at the application level even though the handler never sees it.

  • router_param is a header parameter with the key MyHeader, declared via HeaderParameter on the router. It is declared required=False on 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 than str | None), making it required at the handler level. If a value is provided, it is also tested against the router-declared regex.

  • controller_param is a query parameter with the key controller_param, declared via QueryParameter on the controller with lt=100 (value must be less than 100). The handler redeclares it with Annotated[int, QueryParameter(lt=50)], tightening the constraint to less than 50 for this particular route.

  • local_param is a route-handler-local query parameter (FromQuery[str]), and path_param is 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

def h(name: str) (implicit query parameter)

def h(name: FromQuery[str])

def h(user_id: int) (implicit path parameter)

def h(user_id: FromPath[int])

Annotated[str, Parameter(query="alias")]

Annotated[str, QueryParameter(name="alias")]

Annotated[str, Parameter(header="X-API-KEY")]

Annotated[str, HeaderParameter(name="X-API-KEY")]

Annotated[str, Parameter(cookie="session")]

Annotated[str, CookieParameter(name="session")]

name: str = Parameter(...) (default-value style)

name: Annotated[str, QueryParameter(...)]

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.