Parameters#

Path Parameters#

Path parameters are parameters declared as part of the path component of the URL. They are declared using a simple syntax {param_name:param_type} :

Defining a path parameter in a route handler#
from pydantic import BaseModel

from litestar import Litestar, get

USER_DB = {1: {"id": 1, "name": "John Doe"}}


class User(BaseModel):
    id: int
    name: str


@get("/user/{user_id:int}", sync_to_thread=False)
def get_user(user_id: int) -> User:
    return User.model_validate(USER_DB[user_id])


app = Litestar(route_handlers=[get_user])

In the above there are two components:

  1. The path parameter is defined in the @get() decorator, which declares both the parameter’s name (user_id) and type (int).

  2. The decorated function get_user defines a parameter with the same name as the parameter defined in the path kwarg.

The correlation of parameter name ensures that the value of the path parameter will be injected into the function when it is called.

Supported Path Parameter Types#

Currently, the following types are supported:

  • 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.

The types declared in the path parameter and the function do not need to match 1:1 - as long as parameter inside the function declaration is typed with a “higher” type to which the lower type can be coerced, this is fine. For example, consider this:

Coercing path parameters into different types#
from datetime import datetime, timezone
from typing import List

from pydantic import BaseModel

from litestar import Litestar, get


class Order(BaseModel):
    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: datetime) -> List[Order]:
    return ORDERS_BY_DATETIME[from_date]


app = Litestar(route_handlers=[get_orders])
Coercing path parameters into different types#
from datetime import datetime, timezone

from pydantic import BaseModel

from litestar import Litestar, get


class Order(BaseModel):
    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: datetime) -> list[Order]:
    return ORDERS_BY_DATETIME[from_date]


app = Litestar(route_handlers=[get_orders])
Coercing path parameters into different types#
from datetime import datetime, UTC

from pydantic import BaseModel

from litestar import Litestar, get


class Order(BaseModel):
    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: datetime) -> list[Order]:
    return ORDERS_BY_DATETIME[from_date]


app = Litestar(route_handlers=[get_orders])

The parameter defined inside the path kwarg is typed as int , because the value passed as part of the request will be a timestamp in milliseconds without any decimals. The parameter in the function declaration though is typed as datetime.datetime.

This works because the int value will be automatically coerced from an int into a datetime.

Thus, when the function is called it will be called with a datetime-typed parameter.

Note

You only need to define the parameter in the function declaration if it is actually used inside the function. If the path parameter is part of the path, but the function does not use it, it is fine to omit it. It will still be validated and added to the OpenAPI schema correctly.

The Parameter function#

Parameter() is a helper function wrapping a parameter with extra information to be added to the OpenAPI schema.

Extra validation and documentation for path params#

If you want to add validation or enhance the OpenAPI documentation generated for a given path parameter, you can do so using the the parameter function:

Adding extra validation and documentation to a path parameter#
from pydantic import BaseModel, Json, conint
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 Parameter


class Version(BaseModel):
    id: conint(ge=1, le=10)  # type: ignore[valid-type]
    specs: Json


VERSIONS = {1: Version(id=1, specs='{"some": "value"}')}


@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
    version: Annotated[
        int,
        Parameter(
            ge=1,
            le=10,
            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 extra validation and documentation to a path parameter#
from pydantic import BaseModel, Json, conint
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 Parameter


class Version(BaseModel):
    id: conint(ge=1, le=10)  # type: ignore[valid-type]
    specs: Json


VERSIONS = {1: Version(id=1, specs='{"some": "value"}')}


@get(path="/versions/{version:int}", sync_to_thread=False)
def get_product_version(
    version: Annotated[
        int,
        Parameter(
            ge=1,
            le=10,
            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])

In the above example, Parameter() is used to restrict the value of version to a range between 1 and 10, and then set the title, description, examples, and externalDocs sections of the OpenAPI schema.

Query Parameters#

Query parameters are defined as keyword arguments to handler functions. Every keyword argument that is not otherwise specified (for example as a path parameter) will be interpreted as a query parameter.

Defining query parameters in a route handler#
from typing import Dict

from litestar import Litestar, get


@get("/", sync_to_thread=False)
def index(param: str) -> Dict[str, str]:
    return {"param": param}


app = Litestar(route_handlers=[index])
Defining query parameters in a route handler#
from litestar import Litestar, get


@get("/", sync_to_thread=False)
def index(param: 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"}

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.

This ability allows you to use any number of schema/modelling libraries, including Pydantic, Msgspec, Attrs, and Dataclasses, and it will follow the same kind of validation and parsing as you would get from these libraries.

Query parameters come in three basic types:

  • Required

  • Required with a default value

  • Optional with a default value

Query parameters are required by default. If one such a parameter has no value, a ValidationException will be raised.

Default values#

In this example, param will have the value "hello" if it is not specified in the request. If it is passed as a query parameter however, it will be overwritten:

Defining a default value for a query parameter#
from typing import Dict

from litestar import Litestar, get


@get("/", sync_to_thread=False)
def index(param: 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


@get("/", sync_to_thread=False)
def index(param: 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"}

Optional parameters#

Instead of only setting a default value, it is also possible to make a query parameter entirely optional.

Here, we give a default value of None , but still declare the type of the query parameter to be a string. This means that this parameter is not required.

If it is given, it has to be a string. If it is not given, it will have a default value of None

Defining an optional query parameter#
from typing import Dict, Optional

from litestar import Litestar, get


@get("/", sync_to_thread=False)
def index(param: 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


@get("/", sync_to_thread=False)
def index(param: 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


@get("/", sync_to_thread=False)
def index(param: 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"}

Type coercion#

It is possible to coerce query parameters into different types. A query starts out as a string, but its values can be parsed into all kinds of types.

Coercing query parameters into different types#
from datetime import datetime, timedelta
from typing import Any, Dict, List

from litestar import Litestar, get


@get("/", sync_to_thread=False)
def index(date: datetime, number: int, floating_number: float, strings: 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


@get("/", sync_to_thread=False)
def index(date: datetime, number: int, floating_number: float, strings: 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"]}

Alternative names and constraints#

Sometimes you might want to “remap” query parameters to allow a different name in the URL than what is being used in the handler function. This can be done by making use of Parameter().

Remapping query parameters to different names#
from typing import Dict

from typing_extensions import Annotated

from litestar import Litestar, get
from litestar.params import Parameter


@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, Parameter(query="camelCase")]) -> Dict[str, str]:
    return {"param": snake_case}


app = Litestar(route_handlers=[index])
Remapping query parameters to different names#
from typing import Annotated

from litestar import Litestar, get
from litestar.params import Parameter


@get("/", sync_to_thread=False)
def index(snake_case: Annotated[str, Parameter(query="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"}

Here, we remap from snake_case in the handler function to camelCase in the URL. This means that for the URL http://127.0.0.1:8000?camelCase=foo , the value of camelCase will be used for the value of the snake_case parameter.

Parameter() also allows us to define additional constraints:

Constraints on query parameters#
from typing import Dict

from typing_extensions import Annotated

from litestar import Litestar, get
from litestar.params import Parameter


@get("/", sync_to_thread=False)
def index(param: Annotated[int, Parameter(gt=5)]) -> Dict[str, int]:
    return {"param": param}


app = Litestar(route_handlers=[index])
Constraints on query parameters#
from typing import Annotated

from litestar import Litestar, get
from litestar.params import Parameter


@get("/", sync_to_thread=False)
def index(param: Annotated[int, Parameter(gt=5)]) -> dict[str, int]:
    return {"param": param}


app = Litestar(route_handlers=[index])

In this case, param is validated to be an integer larger than 5.

Layered Parameters#

As part of Litestar’s layered architecture, you can declare parameters not only as part of 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 Parameter


class MyController(Controller):
    path = "/controller"
    parameters = {
        "controller_param": Parameter(int, lt=100),
    }

    @get("/{path_param:int}", sync_to_thread=False)
    def my_handler(
        self,
        path_param: int,
        local_param: str,
        router_param: str,
        controller_param: Annotated[int, Parameter(int, 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": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
    },
)

app = Litestar(
    route_handlers=[router],
    parameters={
        "app_param": Parameter(str, cookie="special-cookie"),
    },
)
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 Parameter


class MyController(Controller):
    path = "/controller"
    parameters = {
        "controller_param": Parameter(int, lt=100),
    }

    @get("/{path_param:int}", sync_to_thread=False)
    def my_handler(
        self,
        path_param: int,
        local_param: str,
        router_param: str,
        controller_param: Annotated[int, Parameter(int, 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": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
    },
)

app = Litestar(
    route_handlers=[router],
    parameters={
        "app_param": Parameter(str, cookie="special-cookie"),
    },
)
Declaring parameters on different layers of the application#
from typing import Annotated

from litestar import Controller, Litestar, Router, get
from litestar.params import Parameter


class MyController(Controller):
    path = "/controller"
    parameters = {
        "controller_param": Parameter(int, lt=100),
    }

    @get("/{path_param:int}", sync_to_thread=False)
    def my_handler(
        self,
        path_param: int,
        local_param: str,
        router_param: str,
        controller_param: Annotated[int, Parameter(int, 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": Parameter(str, pattern="^[a-zA-Z]$", header="MyHeader", required=False),
    },
)

app = Litestar(
    route_handlers=[router],
    parameters={
        "app_param": Parameter(str, cookie="special-cookie"),
    },
)

In the above we declare parameters on the Litestar app, router, and controller layers in addition to those declared in the route handler. Now, examine these more closely.

  • app_param is a cookie parameter with the key special-cookie. We type it as str by passing this as an arg to the Parameter() function. This is required for us to get typing in the OpenAPI doc. Additionally, this parameter is assumed to be required because it is not explicitly set as False on required.

    This is important because the route handler function does not declare a parameter called app_param at all, but it will still require this param to be sent as part of the request of validation will fail.

  • router_param is a header parameter with the key MyHeader. Because it is set as False on required, it will not fail validation if not present unless explicitly declared by a route handler - and in this case it is.

    Thus, it is actually required for the router handler function that declares it as an str and not an str | None. If a string value is provided, it will be tested against the provided regex.

  • controller_param is a query param with the key controller_param. It has an lt set to 100 defined on the controller, which means the provided value must be less than 100.

    Yet the route handler redeclares it with an lt set to 50, which means for the route handler this value must be less than 50.

  • local_param is a route handler local query parameter, and path_param is a path parameter.

Note

You cannot declare path parameters in different application layers. The reason for this is to ensure simplicity - otherwise parameter resolution becomes very difficult to do correctly.